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, LanguageSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
140 all_language_settings, 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 ThemeSettings, observe_buffer_font_size_adjustment,
208};
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 = language_settings::language_settings(None, None, cx)
600 .inlay_hints
601 .show_background;
602
603 let mut style = cx.theme().syntax().get("hint");
604
605 if style.color.is_none() {
606 style.color = Some(cx.theme().status().hint);
607 }
608
609 if !show_background {
610 style.background_color = None;
611 return style;
612 }
613
614 if style.background_color.is_none() {
615 style.background_color = Some(cx.theme().status().hint_background);
616 }
617
618 style
619}
620
621pub fn make_suggestion_styles(cx: &App) -> EditPredictionStyles {
622 EditPredictionStyles {
623 insertion: HighlightStyle {
624 color: Some(cx.theme().status().predictive),
625 ..HighlightStyle::default()
626 },
627 whitespace: HighlightStyle {
628 background_color: Some(cx.theme().status().created_background),
629 ..HighlightStyle::default()
630 },
631 }
632}
633
634type CompletionId = usize;
635
636pub(crate) enum EditDisplayMode {
637 TabAccept,
638 DiffPopover,
639 Inline,
640}
641
642enum EditPrediction {
643 Edit {
644 edits: Vec<(Range<Anchor>, Arc<str>)>,
645 /// Predicted cursor position as (anchor, offset_from_anchor).
646 /// The anchor is in multibuffer coordinates; after applying edits,
647 /// resolve the anchor and add the offset to get the final cursor position.
648 cursor_position: Option<(Anchor, usize)>,
649 edit_preview: Option<EditPreview>,
650 display_mode: EditDisplayMode,
651 snapshot: BufferSnapshot,
652 },
653 /// Move to a specific location in the active editor
654 MoveWithin {
655 target: Anchor,
656 snapshot: BufferSnapshot,
657 },
658 /// Move to a specific location in a different editor (not the active one)
659 MoveOutside {
660 target: language::Anchor,
661 snapshot: BufferSnapshot,
662 },
663}
664
665struct EditPredictionState {
666 inlay_ids: Vec<InlayId>,
667 completion: EditPrediction,
668 completion_id: Option<SharedString>,
669 invalidation_range: Option<Range<Anchor>>,
670}
671
672enum EditPredictionSettings {
673 Disabled,
674 Enabled {
675 show_in_menu: bool,
676 preview_requires_modifier: bool,
677 },
678}
679
680#[derive(Debug, Clone)]
681struct InlineDiagnostic {
682 message: SharedString,
683 group_id: usize,
684 is_primary: bool,
685 start: Point,
686 severity: lsp::DiagnosticSeverity,
687}
688
689pub enum MenuEditPredictionsPolicy {
690 Never,
691 ByProvider,
692}
693
694pub enum EditPredictionPreview {
695 /// Modifier is not pressed
696 Inactive { released_too_fast: bool },
697 /// Modifier pressed
698 Active {
699 since: Instant,
700 previous_scroll_position: Option<SharedScrollAnchor>,
701 },
702}
703
704#[derive(Copy, Clone, Eq, PartialEq)]
705enum EditPredictionKeybindSurface {
706 Inline,
707 CursorPopoverCompact,
708 CursorPopoverExpanded,
709}
710
711#[derive(Copy, Clone, Eq, PartialEq, Debug)]
712enum EditPredictionKeybindAction {
713 Accept,
714 Preview,
715}
716
717struct EditPredictionKeybindDisplay {
718 #[cfg(test)]
719 accept_keystroke: Option<gpui::KeybindingKeystroke>,
720 #[cfg(test)]
721 preview_keystroke: Option<gpui::KeybindingKeystroke>,
722 displayed_keystroke: Option<gpui::KeybindingKeystroke>,
723 action: EditPredictionKeybindAction,
724 missing_accept_keystroke: bool,
725 show_hold_label: bool,
726}
727
728impl EditPredictionPreview {
729 pub fn released_too_fast(&self) -> bool {
730 match self {
731 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
732 EditPredictionPreview::Active { .. } => false,
733 }
734 }
735
736 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<SharedScrollAnchor>) {
737 if let EditPredictionPreview::Active {
738 previous_scroll_position,
739 ..
740 } = self
741 {
742 *previous_scroll_position = scroll_position;
743 }
744 }
745}
746
747pub struct ContextMenuOptions {
748 pub min_entries_visible: usize,
749 pub max_entries_visible: usize,
750 pub placement: Option<ContextMenuPlacement>,
751}
752
753#[derive(Debug, Clone, PartialEq, Eq)]
754pub enum ContextMenuPlacement {
755 Above,
756 Below,
757}
758
759#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
760struct EditorActionId(usize);
761
762impl EditorActionId {
763 pub fn post_inc(&mut self) -> Self {
764 let answer = self.0;
765
766 *self = Self(answer + 1);
767
768 Self(answer)
769 }
770}
771
772// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
773// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
774
775type BackgroundHighlight = (
776 Arc<dyn Fn(&usize, &Theme) -> Hsla + Send + Sync>,
777 Arc<[Range<Anchor>]>,
778);
779type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
780
781#[derive(Default)]
782struct ScrollbarMarkerState {
783 scrollbar_size: Size<Pixels>,
784 dirty: bool,
785 markers: Arc<[PaintQuad]>,
786 pending_refresh: Option<Task<Result<()>>>,
787}
788
789impl ScrollbarMarkerState {
790 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
791 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
792 }
793}
794
795#[derive(Clone, Copy, PartialEq, Eq)]
796pub enum MinimapVisibility {
797 Disabled,
798 Enabled {
799 /// The configuration currently present in the users settings.
800 setting_configuration: bool,
801 /// Whether to override the currently set visibility from the users setting.
802 toggle_override: bool,
803 },
804}
805
806impl MinimapVisibility {
807 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
808 if mode.is_full() {
809 Self::Enabled {
810 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
811 toggle_override: false,
812 }
813 } else {
814 Self::Disabled
815 }
816 }
817
818 fn hidden(&self) -> Self {
819 match *self {
820 Self::Enabled {
821 setting_configuration,
822 ..
823 } => Self::Enabled {
824 setting_configuration,
825 toggle_override: setting_configuration,
826 },
827 Self::Disabled => Self::Disabled,
828 }
829 }
830
831 fn disabled(&self) -> bool {
832 matches!(*self, Self::Disabled)
833 }
834
835 fn settings_visibility(&self) -> bool {
836 match *self {
837 Self::Enabled {
838 setting_configuration,
839 ..
840 } => setting_configuration,
841 _ => false,
842 }
843 }
844
845 fn visible(&self) -> bool {
846 match *self {
847 Self::Enabled {
848 setting_configuration,
849 toggle_override,
850 } => setting_configuration ^ toggle_override,
851 _ => false,
852 }
853 }
854
855 fn toggle_visibility(&self) -> Self {
856 match *self {
857 Self::Enabled {
858 toggle_override,
859 setting_configuration,
860 } => Self::Enabled {
861 setting_configuration,
862 toggle_override: !toggle_override,
863 },
864 Self::Disabled => Self::Disabled,
865 }
866 }
867}
868
869#[derive(Debug, Clone, Copy, PartialEq, Eq)]
870pub enum BufferSerialization {
871 All,
872 NonDirtyBuffers,
873}
874
875impl BufferSerialization {
876 fn new(restore_unsaved_buffers: bool) -> Self {
877 if restore_unsaved_buffers {
878 Self::All
879 } else {
880 Self::NonDirtyBuffers
881 }
882 }
883}
884
885/// Addons allow storing per-editor state in other crates (e.g. Vim)
886pub trait Addon: 'static {
887 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
888
889 fn render_buffer_header_controls(
890 &self,
891 _: &ExcerptInfo,
892 _: &Window,
893 _: &App,
894 ) -> Option<AnyElement> {
895 None
896 }
897
898 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
899 None
900 }
901
902 fn to_any(&self) -> &dyn std::any::Any;
903
904 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
905 None
906 }
907}
908
909struct ChangeLocation {
910 current: Option<Vec<Anchor>>,
911 original: Vec<Anchor>,
912}
913impl ChangeLocation {
914 fn locations(&self) -> &[Anchor] {
915 self.current.as_ref().unwrap_or(&self.original)
916 }
917}
918
919/// A set of caret positions, registered when the editor was edited.
920pub struct ChangeList {
921 changes: Vec<ChangeLocation>,
922 /// Currently "selected" change.
923 position: Option<usize>,
924}
925
926impl ChangeList {
927 pub fn new() -> Self {
928 Self {
929 changes: Vec::new(),
930 position: None,
931 }
932 }
933
934 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
935 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
936 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
937 if self.changes.is_empty() {
938 return None;
939 }
940
941 let prev = self.position.unwrap_or(self.changes.len());
942 let next = if direction == Direction::Prev {
943 prev.saturating_sub(count)
944 } else {
945 (prev + count).min(self.changes.len() - 1)
946 };
947 self.position = Some(next);
948 self.changes.get(next).map(|change| change.locations())
949 }
950
951 /// Adds a new change to the list, resetting the change list position.
952 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
953 self.position.take();
954 if let Some(last) = self.changes.last_mut()
955 && group
956 {
957 last.current = Some(new_positions)
958 } else {
959 self.changes.push(ChangeLocation {
960 original: new_positions,
961 current: None,
962 });
963 }
964 }
965
966 pub fn last(&self) -> Option<&[Anchor]> {
967 self.changes.last().map(|change| change.locations())
968 }
969
970 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
971 self.changes.last().map(|change| change.original.as_slice())
972 }
973
974 pub fn invert_last_group(&mut self) {
975 if let Some(last) = self.changes.last_mut()
976 && let Some(current) = last.current.as_mut()
977 {
978 mem::swap(&mut last.original, current);
979 }
980 }
981}
982
983#[derive(Clone)]
984struct InlineBlamePopoverState {
985 scroll_handle: ScrollHandle,
986 commit_message: Option<ParsedCommitMessage>,
987 markdown: Entity<Markdown>,
988}
989
990struct InlineBlamePopover {
991 position: gpui::Point<Pixels>,
992 hide_task: Option<Task<()>>,
993 popover_bounds: Option<Bounds<Pixels>>,
994 popover_state: InlineBlamePopoverState,
995 keyboard_grace: bool,
996}
997
998enum SelectionDragState {
999 /// State when no drag related activity is detected.
1000 None,
1001 /// State when the mouse is down on a selection that is about to be dragged.
1002 ReadyToDrag {
1003 selection: Selection<Anchor>,
1004 click_position: gpui::Point<Pixels>,
1005 mouse_down_time: Instant,
1006 },
1007 /// State when the mouse is dragging the selection in the editor.
1008 Dragging {
1009 selection: Selection<Anchor>,
1010 drop_cursor: Selection<Anchor>,
1011 hide_drop_cursor: bool,
1012 },
1013}
1014
1015enum ColumnarSelectionState {
1016 FromMouse {
1017 selection_tail: Anchor,
1018 display_point: Option<DisplayPoint>,
1019 },
1020 FromSelection {
1021 selection_tail: Anchor,
1022 },
1023}
1024
1025/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1026/// a breakpoint on them.
1027#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1028struct PhantomBreakpointIndicator {
1029 display_row: DisplayRow,
1030 /// There's a small debounce between hovering over the line and showing the indicator.
1031 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1032 is_active: bool,
1033 collides_with_existing_breakpoint: bool,
1034}
1035
1036/// Represents a diff review button indicator that shows up when hovering over lines in the gutter
1037/// in diff view mode.
1038#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1039pub(crate) struct PhantomDiffReviewIndicator {
1040 /// The starting anchor of the selection (or the only row if not dragging).
1041 pub start: Anchor,
1042 /// The ending anchor of the selection. Equal to start_anchor for single-line selection.
1043 pub end: Anchor,
1044 /// There's a small debounce between hovering over the line and showing the indicator.
1045 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1046 pub is_active: bool,
1047}
1048
1049#[derive(Clone, Debug)]
1050pub(crate) struct DiffReviewDragState {
1051 pub start_anchor: Anchor,
1052 pub current_anchor: Anchor,
1053}
1054
1055impl DiffReviewDragState {
1056 pub fn row_range(&self, snapshot: &DisplaySnapshot) -> std::ops::RangeInclusive<DisplayRow> {
1057 let start = self.start_anchor.to_display_point(snapshot).row();
1058 let current = self.current_anchor.to_display_point(snapshot).row();
1059
1060 (start..=current).sorted()
1061 }
1062}
1063
1064/// Identifies a specific hunk in the diff buffer.
1065/// Used as a key to group comments by their location.
1066#[derive(Clone, Debug)]
1067pub struct DiffHunkKey {
1068 /// The file path (relative to worktree) this hunk belongs to.
1069 pub file_path: Arc<util::rel_path::RelPath>,
1070 /// An anchor at the start of the hunk. This tracks position as the buffer changes.
1071 pub hunk_start_anchor: Anchor,
1072}
1073
1074/// A review comment stored locally before being sent to the Agent panel.
1075#[derive(Clone)]
1076pub struct StoredReviewComment {
1077 /// Unique identifier for this comment (for edit/delete operations).
1078 pub id: usize,
1079 /// The comment text entered by the user.
1080 pub comment: String,
1081 /// Anchors for the code range being reviewed.
1082 pub range: Range<Anchor>,
1083 /// Timestamp when the comment was created (for chronological ordering).
1084 pub created_at: Instant,
1085 /// Whether this comment is currently being edited inline.
1086 pub is_editing: bool,
1087}
1088
1089impl StoredReviewComment {
1090 pub fn new(id: usize, comment: String, anchor_range: Range<Anchor>) -> Self {
1091 Self {
1092 id,
1093 comment,
1094 range: anchor_range,
1095 created_at: Instant::now(),
1096 is_editing: false,
1097 }
1098 }
1099}
1100
1101/// Represents an active diff review overlay that appears when clicking the "Add Review" button.
1102pub(crate) struct DiffReviewOverlay {
1103 pub anchor_range: Range<Anchor>,
1104 /// The block ID for the overlay.
1105 pub block_id: CustomBlockId,
1106 /// The editor entity for the review input.
1107 pub prompt_editor: Entity<Editor>,
1108 /// The hunk key this overlay belongs to.
1109 pub hunk_key: DiffHunkKey,
1110 /// Whether the comments section is expanded.
1111 pub comments_expanded: bool,
1112 /// Editors for comments currently being edited inline.
1113 /// Key: comment ID, Value: Editor entity for inline editing.
1114 pub inline_edit_editors: HashMap<usize, Entity<Editor>>,
1115 /// Subscriptions for inline edit editors' action handlers.
1116 /// Key: comment ID, Value: Subscription keeping the Newline action handler alive.
1117 pub inline_edit_subscriptions: HashMap<usize, Subscription>,
1118 /// The current user's avatar URI for display in comment rows.
1119 pub user_avatar_uri: Option<SharedUri>,
1120 /// Subscription to keep the action handler alive.
1121 _subscription: Subscription,
1122}
1123
1124/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1125///
1126/// See the [module level documentation](self) for more information.
1127pub struct Editor {
1128 focus_handle: FocusHandle,
1129 last_focused_descendant: Option<WeakFocusHandle>,
1130 /// The text buffer being edited
1131 buffer: Entity<MultiBuffer>,
1132 /// Map of how text in the buffer should be displayed.
1133 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1134 pub display_map: Entity<DisplayMap>,
1135 placeholder_display_map: Option<Entity<DisplayMap>>,
1136 pub selections: SelectionsCollection,
1137 pub scroll_manager: ScrollManager,
1138 /// When inline assist editors are linked, they all render cursors because
1139 /// typing enters text into each of them, even the ones that aren't focused.
1140 pub(crate) show_cursor_when_unfocused: bool,
1141 columnar_selection_state: Option<ColumnarSelectionState>,
1142 add_selections_state: Option<AddSelectionsState>,
1143 select_next_state: Option<SelectNextState>,
1144 select_prev_state: Option<SelectNextState>,
1145 selection_history: SelectionHistory,
1146 defer_selection_effects: bool,
1147 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1148 autoclose_regions: Vec<AutocloseRegion>,
1149 snippet_stack: InvalidationStack<SnippetState>,
1150 select_syntax_node_history: SelectSyntaxNodeHistory,
1151 ime_transaction: Option<TransactionId>,
1152 pub diagnostics_max_severity: DiagnosticSeverity,
1153 active_diagnostics: ActiveDiagnostic,
1154 show_inline_diagnostics: bool,
1155 inline_diagnostics_update: Task<()>,
1156 inline_diagnostics_enabled: bool,
1157 diagnostics_enabled: bool,
1158 word_completions_enabled: bool,
1159 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1160 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1161 hard_wrap: Option<usize>,
1162 project: Option<Entity<Project>>,
1163 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1164 completion_provider: Option<Rc<dyn CompletionProvider>>,
1165 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1166 blink_manager: Entity<BlinkManager>,
1167 show_cursor_names: bool,
1168 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1169 pub show_local_selections: bool,
1170 mode: EditorMode,
1171 show_breadcrumbs: bool,
1172 show_gutter: bool,
1173 show_scrollbars: ScrollbarAxes,
1174 minimap_visibility: MinimapVisibility,
1175 offset_content: bool,
1176 disable_expand_excerpt_buttons: bool,
1177 delegate_expand_excerpts: bool,
1178 delegate_stage_and_restore: bool,
1179 delegate_open_excerpts: bool,
1180 enable_lsp_data: bool,
1181 enable_runnables: bool,
1182 show_line_numbers: Option<bool>,
1183 use_relative_line_numbers: Option<bool>,
1184 show_git_diff_gutter: Option<bool>,
1185 show_code_actions: Option<bool>,
1186 show_runnables: Option<bool>,
1187 show_breakpoints: Option<bool>,
1188 show_diff_review_button: bool,
1189 show_wrap_guides: Option<bool>,
1190 show_indent_guides: Option<bool>,
1191 buffers_with_disabled_indent_guides: HashSet<BufferId>,
1192 highlight_order: usize,
1193 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1194 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1195 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1196 scrollbar_marker_state: ScrollbarMarkerState,
1197 active_indent_guides_state: ActiveIndentGuidesState,
1198 nav_history: Option<ItemNavHistory>,
1199 context_menu: RefCell<Option<CodeContextMenu>>,
1200 context_menu_options: Option<ContextMenuOptions>,
1201 mouse_context_menu: Option<MouseContextMenu>,
1202 completion_tasks: Vec<(CompletionId, Task<()>)>,
1203 inline_blame_popover: Option<InlineBlamePopover>,
1204 inline_blame_popover_show_task: Option<Task<()>>,
1205 signature_help_state: SignatureHelpState,
1206 auto_signature_help: Option<bool>,
1207 find_all_references_task_sources: Vec<Anchor>,
1208 next_completion_id: CompletionId,
1209 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1210 code_actions_task: Option<Task<Result<()>>>,
1211 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1212 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1213 debounced_selection_highlight_complete: bool,
1214 document_highlights_task: Option<Task<()>>,
1215 linked_editing_range_task: Option<Task<Option<()>>>,
1216 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1217 pending_rename: Option<RenameState>,
1218 searchable: bool,
1219 cursor_shape: CursorShape,
1220 /// Whether the cursor is offset one character to the left when something is
1221 /// selected (needed for vim visual mode)
1222 cursor_offset_on_selection: bool,
1223 current_line_highlight: Option<CurrentLineHighlight>,
1224 /// Whether to collapse search match ranges to just their start position.
1225 /// When true, navigating to a match positions the cursor at the match
1226 /// without selecting the matched text.
1227 collapse_matches: bool,
1228 autoindent_mode: Option<AutoindentMode>,
1229 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1230 input_enabled: bool,
1231 expects_character_input: bool,
1232 use_modal_editing: bool,
1233 read_only: bool,
1234 leader_id: Option<CollaboratorId>,
1235 remote_id: Option<ViewId>,
1236 pub hover_state: HoverState,
1237 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1238 prev_pressure_stage: Option<PressureStage>,
1239 gutter_hovered: bool,
1240 hovered_link_state: Option<HoveredLinkState>,
1241 edit_prediction_provider: Option<RegisteredEditPredictionDelegate>,
1242 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1243 active_edit_prediction: Option<EditPredictionState>,
1244 /// Used to prevent flickering as the user types while the menu is open
1245 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1246 edit_prediction_settings: EditPredictionSettings,
1247 edit_predictions_hidden_for_vim_mode: bool,
1248 show_edit_predictions_override: Option<bool>,
1249 show_completions_on_input_override: Option<bool>,
1250 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1251 edit_prediction_preview: EditPredictionPreview,
1252 in_leading_whitespace: bool,
1253 next_inlay_id: usize,
1254 next_color_inlay_id: usize,
1255 _subscriptions: Vec<Subscription>,
1256 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1257 gutter_dimensions: GutterDimensions,
1258 style: Option<EditorStyle>,
1259 text_style_refinement: Option<TextStyleRefinement>,
1260 next_editor_action_id: EditorActionId,
1261 editor_actions: Rc<
1262 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1263 >,
1264 use_autoclose: bool,
1265 use_auto_surround: bool,
1266 auto_replace_emoji_shortcode: bool,
1267 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1268 show_git_blame_gutter: bool,
1269 show_git_blame_inline: bool,
1270 show_git_blame_inline_delay_task: Option<Task<()>>,
1271 git_blame_inline_enabled: bool,
1272 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1273 buffer_serialization: Option<BufferSerialization>,
1274 show_selection_menu: Option<bool>,
1275 blame: Option<Entity<GitBlame>>,
1276 blame_subscription: Option<Subscription>,
1277 custom_context_menu: Option<
1278 Box<
1279 dyn 'static
1280 + Fn(
1281 &mut Self,
1282 DisplayPoint,
1283 &mut Window,
1284 &mut Context<Self>,
1285 ) -> Option<Entity<ui::ContextMenu>>,
1286 >,
1287 >,
1288 last_bounds: Option<Bounds<Pixels>>,
1289 last_position_map: Option<Rc<PositionMap>>,
1290 expect_bounds_change: Option<Bounds<Pixels>>,
1291 runnables: RunnableData,
1292 breakpoint_store: Option<Entity<BreakpointStore>>,
1293 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1294 pub(crate) gutter_diff_review_indicator: (Option<PhantomDiffReviewIndicator>, Option<Task<()>>),
1295 pub(crate) diff_review_drag_state: Option<DiffReviewDragState>,
1296 /// Active diff review overlays. Multiple overlays can be open simultaneously
1297 /// when hunks have comments stored.
1298 pub(crate) diff_review_overlays: Vec<DiffReviewOverlay>,
1299 /// Stored review comments grouped by hunk.
1300 /// Uses a Vec instead of HashMap because DiffHunkKey contains an Anchor
1301 /// which doesn't implement Hash/Eq in a way suitable for HashMap keys.
1302 stored_review_comments: Vec<(DiffHunkKey, Vec<StoredReviewComment>)>,
1303 /// Counter for generating unique comment IDs.
1304 next_review_comment_id: usize,
1305 hovered_diff_hunk_row: Option<DisplayRow>,
1306 pull_diagnostics_task: Task<()>,
1307 in_project_search: bool,
1308 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1309 breadcrumb_header: Option<String>,
1310 focused_block: Option<FocusedBlock>,
1311 next_scroll_position: NextScrollCursorCenterTopBottom,
1312 addons: HashMap<TypeId, Box<dyn Addon>>,
1313 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1314 load_diff_task: Option<Shared<Task<()>>>,
1315 /// Whether we are temporarily displaying a diff other than git's
1316 temporary_diff_override: bool,
1317 selection_mark_mode: bool,
1318 toggle_fold_multiple_buffers: Task<()>,
1319 _scroll_cursor_center_top_bottom_task: Task<()>,
1320 serialize_selections: Task<()>,
1321 serialize_folds: Task<()>,
1322 mouse_cursor_hidden: bool,
1323 minimap: Option<Entity<Self>>,
1324 hide_mouse_mode: HideMouseMode,
1325 pub change_list: ChangeList,
1326 inline_value_cache: InlineValueCache,
1327 number_deleted_lines: bool,
1328
1329 selection_drag_state: SelectionDragState,
1330 colors: Option<LspColorData>,
1331 post_scroll_update: Task<()>,
1332 refresh_colors_task: Task<()>,
1333 use_document_folding_ranges: bool,
1334 refresh_folding_ranges_task: Task<()>,
1335 inlay_hints: Option<LspInlayHintData>,
1336 folding_newlines: Task<()>,
1337 select_next_is_case_sensitive: Option<bool>,
1338 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1339 on_local_selections_changed:
1340 Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
1341 suppress_selection_callback: bool,
1342 applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
1343 accent_data: Option<AccentData>,
1344 bracket_fetched_tree_sitter_chunks: HashMap<ExcerptId, HashSet<Range<BufferRow>>>,
1345 semantic_token_state: SemanticTokenState,
1346 pub(crate) refresh_matching_bracket_highlights_task: Task<()>,
1347 refresh_document_symbols_task: Shared<Task<()>>,
1348 lsp_document_symbols: HashMap<BufferId, Vec<OutlineItem<text::Anchor>>>,
1349 refresh_outline_symbols_at_cursor_at_cursor_task: Task<()>,
1350 outline_symbols_at_cursor: Option<(BufferId, Vec<OutlineItem<Anchor>>)>,
1351 sticky_headers_task: Task<()>,
1352 sticky_headers: Option<Vec<OutlineItem<Anchor>>>,
1353 pub(crate) colorize_brackets_task: Task<()>,
1354}
1355
1356#[derive(Debug, PartialEq)]
1357struct AccentData {
1358 colors: AccentColors,
1359 overrides: Vec<SharedString>,
1360}
1361
1362fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1363 if debounce_ms > 0 {
1364 Some(Duration::from_millis(debounce_ms))
1365 } else {
1366 None
1367 }
1368}
1369
1370#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1371enum NextScrollCursorCenterTopBottom {
1372 #[default]
1373 Center,
1374 Top,
1375 Bottom,
1376}
1377
1378impl NextScrollCursorCenterTopBottom {
1379 fn next(&self) -> Self {
1380 match self {
1381 Self::Center => Self::Top,
1382 Self::Top => Self::Bottom,
1383 Self::Bottom => Self::Center,
1384 }
1385 }
1386}
1387
1388#[derive(Clone)]
1389pub struct EditorSnapshot {
1390 pub mode: EditorMode,
1391 show_gutter: bool,
1392 offset_content: bool,
1393 show_line_numbers: Option<bool>,
1394 number_deleted_lines: bool,
1395 show_git_diff_gutter: Option<bool>,
1396 show_code_actions: Option<bool>,
1397 show_runnables: Option<bool>,
1398 show_breakpoints: Option<bool>,
1399 git_blame_gutter_max_author_length: Option<usize>,
1400 pub display_snapshot: DisplaySnapshot,
1401 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1402 is_focused: bool,
1403 scroll_anchor: SharedScrollAnchor,
1404 ongoing_scroll: OngoingScroll,
1405 current_line_highlight: CurrentLineHighlight,
1406 gutter_hovered: bool,
1407 semantic_tokens_enabled: bool,
1408}
1409
1410#[derive(Default, Debug, Clone, Copy)]
1411pub struct GutterDimensions {
1412 pub left_padding: Pixels,
1413 pub right_padding: Pixels,
1414 pub width: Pixels,
1415 pub margin: Pixels,
1416 pub git_blame_entries_width: Option<Pixels>,
1417}
1418
1419impl GutterDimensions {
1420 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1421 Self {
1422 margin: Self::default_gutter_margin(font_id, font_size, cx),
1423 ..Default::default()
1424 }
1425 }
1426
1427 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1428 -cx.text_system().descent(font_id, font_size)
1429 }
1430 /// The full width of the space taken up by the gutter.
1431 pub fn full_width(&self) -> Pixels {
1432 self.margin + self.width
1433 }
1434
1435 /// The width of the space reserved for the fold indicators,
1436 /// use alongside 'justify_end' and `gutter_width` to
1437 /// right align content with the line numbers
1438 pub fn fold_area_width(&self) -> Pixels {
1439 self.margin + self.right_padding
1440 }
1441}
1442
1443struct CharacterDimensions {
1444 em_width: Pixels,
1445 em_advance: Pixels,
1446 line_height: Pixels,
1447}
1448
1449#[derive(Debug)]
1450pub struct RemoteSelection {
1451 pub replica_id: ReplicaId,
1452 pub selection: Selection<Anchor>,
1453 pub cursor_shape: CursorShape,
1454 pub collaborator_id: CollaboratorId,
1455 pub line_mode: bool,
1456 pub user_name: Option<SharedString>,
1457 pub color: PlayerColor,
1458}
1459
1460#[derive(Clone, Debug)]
1461struct SelectionHistoryEntry {
1462 selections: Arc<[Selection<Anchor>]>,
1463 select_next_state: Option<SelectNextState>,
1464 select_prev_state: Option<SelectNextState>,
1465 add_selections_state: Option<AddSelectionsState>,
1466}
1467
1468#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1469enum SelectionHistoryMode {
1470 #[default]
1471 Normal,
1472 Undoing,
1473 Redoing,
1474 Skipping,
1475}
1476
1477#[derive(Clone, PartialEq, Eq, Hash)]
1478struct HoveredCursor {
1479 replica_id: ReplicaId,
1480 selection_id: usize,
1481}
1482
1483#[derive(Debug)]
1484/// SelectionEffects controls the side-effects of updating the selection.
1485///
1486/// The default behaviour does "what you mostly want":
1487/// - it pushes to the nav history if the cursor moved by >10 lines
1488/// - it re-triggers completion requests
1489/// - it scrolls to fit
1490///
1491/// You might want to modify these behaviours. For example when doing a "jump"
1492/// like go to definition, we always want to add to nav history; but when scrolling
1493/// in vim mode we never do.
1494///
1495/// Similarly, you might want to disable scrolling if you don't want the viewport to
1496/// move.
1497#[derive(Clone)]
1498pub struct SelectionEffects {
1499 nav_history: Option<bool>,
1500 completions: bool,
1501 scroll: Option<Autoscroll>,
1502}
1503
1504impl Default for SelectionEffects {
1505 fn default() -> Self {
1506 Self {
1507 nav_history: None,
1508 completions: true,
1509 scroll: Some(Autoscroll::fit()),
1510 }
1511 }
1512}
1513impl SelectionEffects {
1514 pub fn scroll(scroll: Autoscroll) -> Self {
1515 Self {
1516 scroll: Some(scroll),
1517 ..Default::default()
1518 }
1519 }
1520
1521 pub fn no_scroll() -> Self {
1522 Self {
1523 scroll: None,
1524 ..Default::default()
1525 }
1526 }
1527
1528 pub fn completions(self, completions: bool) -> Self {
1529 Self {
1530 completions,
1531 ..self
1532 }
1533 }
1534
1535 pub fn nav_history(self, nav_history: bool) -> Self {
1536 Self {
1537 nav_history: Some(nav_history),
1538 ..self
1539 }
1540 }
1541}
1542
1543struct DeferredSelectionEffectsState {
1544 changed: bool,
1545 effects: SelectionEffects,
1546 old_cursor_position: Anchor,
1547 history_entry: SelectionHistoryEntry,
1548}
1549
1550#[derive(Default)]
1551struct SelectionHistory {
1552 #[allow(clippy::type_complexity)]
1553 selections_by_transaction:
1554 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1555 mode: SelectionHistoryMode,
1556 undo_stack: VecDeque<SelectionHistoryEntry>,
1557 redo_stack: VecDeque<SelectionHistoryEntry>,
1558}
1559
1560impl SelectionHistory {
1561 #[track_caller]
1562 fn insert_transaction(
1563 &mut self,
1564 transaction_id: TransactionId,
1565 selections: Arc<[Selection<Anchor>]>,
1566 ) {
1567 if selections.is_empty() {
1568 log::error!(
1569 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1570 std::panic::Location::caller()
1571 );
1572 return;
1573 }
1574 self.selections_by_transaction
1575 .insert(transaction_id, (selections, None));
1576 }
1577
1578 #[allow(clippy::type_complexity)]
1579 fn transaction(
1580 &self,
1581 transaction_id: TransactionId,
1582 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1583 self.selections_by_transaction.get(&transaction_id)
1584 }
1585
1586 #[allow(clippy::type_complexity)]
1587 fn transaction_mut(
1588 &mut self,
1589 transaction_id: TransactionId,
1590 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1591 self.selections_by_transaction.get_mut(&transaction_id)
1592 }
1593
1594 fn push(&mut self, entry: SelectionHistoryEntry) {
1595 if !entry.selections.is_empty() {
1596 match self.mode {
1597 SelectionHistoryMode::Normal => {
1598 self.push_undo(entry);
1599 self.redo_stack.clear();
1600 }
1601 SelectionHistoryMode::Undoing => self.push_redo(entry),
1602 SelectionHistoryMode::Redoing => self.push_undo(entry),
1603 SelectionHistoryMode::Skipping => {}
1604 }
1605 }
1606 }
1607
1608 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1609 if self
1610 .undo_stack
1611 .back()
1612 .is_none_or(|e| e.selections != entry.selections)
1613 {
1614 self.undo_stack.push_back(entry);
1615 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1616 self.undo_stack.pop_front();
1617 }
1618 }
1619 }
1620
1621 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1622 if self
1623 .redo_stack
1624 .back()
1625 .is_none_or(|e| e.selections != entry.selections)
1626 {
1627 self.redo_stack.push_back(entry);
1628 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1629 self.redo_stack.pop_front();
1630 }
1631 }
1632 }
1633}
1634
1635#[derive(Clone, Copy)]
1636pub struct RowHighlightOptions {
1637 pub autoscroll: bool,
1638 pub include_gutter: bool,
1639}
1640
1641impl Default for RowHighlightOptions {
1642 fn default() -> Self {
1643 Self {
1644 autoscroll: Default::default(),
1645 include_gutter: true,
1646 }
1647 }
1648}
1649
1650struct RowHighlight {
1651 index: usize,
1652 range: Range<Anchor>,
1653 color: Hsla,
1654 options: RowHighlightOptions,
1655 type_id: TypeId,
1656}
1657
1658#[derive(Clone, Debug)]
1659struct AddSelectionsState {
1660 groups: Vec<AddSelectionsGroup>,
1661}
1662
1663#[derive(Clone, Debug)]
1664struct AddSelectionsGroup {
1665 above: bool,
1666 stack: Vec<usize>,
1667}
1668
1669#[derive(Clone)]
1670struct SelectNextState {
1671 query: AhoCorasick,
1672 wordwise: bool,
1673 done: bool,
1674}
1675
1676impl std::fmt::Debug for SelectNextState {
1677 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1678 f.debug_struct(std::any::type_name::<Self>())
1679 .field("wordwise", &self.wordwise)
1680 .field("done", &self.done)
1681 .finish()
1682 }
1683}
1684
1685#[derive(Debug)]
1686struct AutocloseRegion {
1687 selection_id: usize,
1688 range: Range<Anchor>,
1689 pair: BracketPair,
1690}
1691
1692#[derive(Debug)]
1693struct SnippetState {
1694 ranges: Vec<Vec<Range<Anchor>>>,
1695 active_index: usize,
1696 choices: Vec<Option<Vec<String>>>,
1697}
1698
1699#[doc(hidden)]
1700pub struct RenameState {
1701 pub range: Range<Anchor>,
1702 pub old_name: Arc<str>,
1703 pub editor: Entity<Editor>,
1704 block_id: CustomBlockId,
1705}
1706
1707struct InvalidationStack<T>(Vec<T>);
1708
1709struct RegisteredEditPredictionDelegate {
1710 provider: Arc<dyn EditPredictionDelegateHandle>,
1711 _subscription: Subscription,
1712}
1713
1714#[derive(Debug, PartialEq, Eq)]
1715pub struct ActiveDiagnosticGroup {
1716 pub active_range: Range<Anchor>,
1717 pub active_message: String,
1718 pub group_id: usize,
1719 pub blocks: HashSet<CustomBlockId>,
1720}
1721
1722#[derive(Debug, PartialEq, Eq)]
1723
1724pub(crate) enum ActiveDiagnostic {
1725 None,
1726 All,
1727 Group(ActiveDiagnosticGroup),
1728}
1729
1730#[derive(Serialize, Deserialize, Clone, Debug)]
1731pub struct ClipboardSelection {
1732 /// The number of bytes in this selection.
1733 pub len: usize,
1734 /// Whether this was a full-line selection.
1735 pub is_entire_line: bool,
1736 /// The indentation of the first line when this content was originally copied.
1737 pub first_line_indent: u32,
1738 #[serde(default)]
1739 pub file_path: Option<PathBuf>,
1740 #[serde(default)]
1741 pub line_range: Option<RangeInclusive<u32>>,
1742}
1743
1744impl ClipboardSelection {
1745 pub fn for_buffer(
1746 len: usize,
1747 is_entire_line: bool,
1748 range: Range<Point>,
1749 buffer: &MultiBufferSnapshot,
1750 project: Option<&Entity<Project>>,
1751 cx: &App,
1752 ) -> Self {
1753 let first_line_indent = buffer
1754 .indent_size_for_line(MultiBufferRow(range.start.row))
1755 .len;
1756
1757 let file_path = util::maybe!({
1758 let project = project?.read(cx);
1759 let file = buffer.file_at(range.start)?;
1760 let project_path = ProjectPath {
1761 worktree_id: file.worktree_id(cx),
1762 path: file.path().clone(),
1763 };
1764 project.absolute_path(&project_path, cx)
1765 });
1766
1767 let line_range = file_path.as_ref().and_then(|_| {
1768 let (_, start_point, start_excerpt_id) = buffer.point_to_buffer_point(range.start)?;
1769 let (_, end_point, end_excerpt_id) = buffer.point_to_buffer_point(range.end)?;
1770 if start_excerpt_id == end_excerpt_id {
1771 Some(start_point.row..=end_point.row)
1772 } else {
1773 None
1774 }
1775 });
1776
1777 Self {
1778 len,
1779 is_entire_line,
1780 first_line_indent,
1781 file_path,
1782 line_range,
1783 }
1784 }
1785}
1786
1787// selections, scroll behavior, was newest selection reversed
1788type SelectSyntaxNodeHistoryState = (
1789 Box<[Selection<Anchor>]>,
1790 SelectSyntaxNodeScrollBehavior,
1791 bool,
1792);
1793
1794#[derive(Default)]
1795struct SelectSyntaxNodeHistory {
1796 stack: Vec<SelectSyntaxNodeHistoryState>,
1797 // disable temporarily to allow changing selections without losing the stack
1798 pub disable_clearing: bool,
1799}
1800
1801impl SelectSyntaxNodeHistory {
1802 pub fn try_clear(&mut self) {
1803 if !self.disable_clearing {
1804 self.stack.clear();
1805 }
1806 }
1807
1808 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1809 self.stack.push(selection);
1810 }
1811
1812 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1813 self.stack.pop()
1814 }
1815}
1816
1817enum SelectSyntaxNodeScrollBehavior {
1818 CursorTop,
1819 FitSelection,
1820 CursorBottom,
1821}
1822
1823#[derive(Debug, Clone, Copy)]
1824pub(crate) struct NavigationData {
1825 cursor_anchor: Anchor,
1826 cursor_position: Point,
1827 scroll_anchor: ScrollAnchor,
1828 scroll_top_row: u32,
1829}
1830
1831#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1832pub enum GotoDefinitionKind {
1833 Symbol,
1834 Declaration,
1835 Type,
1836 Implementation,
1837}
1838
1839pub enum FormatTarget {
1840 Buffers(HashSet<Entity<Buffer>>),
1841 Ranges(Vec<Range<MultiBufferPoint>>),
1842}
1843
1844pub(crate) struct FocusedBlock {
1845 id: BlockId,
1846 focus_handle: WeakFocusHandle,
1847}
1848
1849#[derive(Clone, Debug)]
1850pub enum JumpData {
1851 MultiBufferRow {
1852 row: MultiBufferRow,
1853 line_offset_from_top: u32,
1854 },
1855 MultiBufferPoint {
1856 excerpt_id: ExcerptId,
1857 position: Point,
1858 anchor: text::Anchor,
1859 line_offset_from_top: u32,
1860 },
1861}
1862
1863pub enum MultibufferSelectionMode {
1864 First,
1865 All,
1866}
1867
1868#[derive(Clone, Copy, Debug, Default)]
1869pub struct RewrapOptions {
1870 pub override_language_settings: bool,
1871 pub preserve_existing_whitespace: bool,
1872 pub line_length: Option<usize>,
1873}
1874
1875impl Editor {
1876 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1877 let buffer = cx.new(|cx| Buffer::local("", cx));
1878 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1879 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1880 }
1881
1882 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1883 let buffer = cx.new(|cx| Buffer::local("", cx));
1884 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1885 Self::new(EditorMode::full(), buffer, None, window, cx)
1886 }
1887
1888 pub fn auto_height(
1889 min_lines: usize,
1890 max_lines: usize,
1891 window: &mut Window,
1892 cx: &mut Context<Self>,
1893 ) -> Self {
1894 let buffer = cx.new(|cx| Buffer::local("", cx));
1895 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1896 Self::new(
1897 EditorMode::AutoHeight {
1898 min_lines,
1899 max_lines: Some(max_lines),
1900 },
1901 buffer,
1902 None,
1903 window,
1904 cx,
1905 )
1906 }
1907
1908 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1909 /// The editor grows as tall as needed to fit its content.
1910 pub fn auto_height_unbounded(
1911 min_lines: usize,
1912 window: &mut Window,
1913 cx: &mut Context<Self>,
1914 ) -> Self {
1915 let buffer = cx.new(|cx| Buffer::local("", cx));
1916 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1917 Self::new(
1918 EditorMode::AutoHeight {
1919 min_lines,
1920 max_lines: None,
1921 },
1922 buffer,
1923 None,
1924 window,
1925 cx,
1926 )
1927 }
1928
1929 pub fn for_buffer(
1930 buffer: Entity<Buffer>,
1931 project: Option<Entity<Project>>,
1932 window: &mut Window,
1933 cx: &mut Context<Self>,
1934 ) -> Self {
1935 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1936 Self::new(EditorMode::full(), buffer, project, window, cx)
1937 }
1938
1939 pub fn for_multibuffer(
1940 buffer: Entity<MultiBuffer>,
1941 project: Option<Entity<Project>>,
1942 window: &mut Window,
1943 cx: &mut Context<Self>,
1944 ) -> Self {
1945 Self::new(EditorMode::full(), buffer, project, window, cx)
1946 }
1947
1948 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1949 let mut clone = Self::new(
1950 self.mode.clone(),
1951 self.buffer.clone(),
1952 self.project.clone(),
1953 window,
1954 cx,
1955 );
1956 let my_snapshot = self.display_map.update(cx, |display_map, cx| {
1957 let snapshot = display_map.snapshot(cx);
1958 clone.display_map.update(cx, |display_map, cx| {
1959 display_map.set_state(&snapshot, cx);
1960 });
1961 snapshot
1962 });
1963 let clone_snapshot = clone.display_map.update(cx, |map, cx| map.snapshot(cx));
1964 clone.folds_did_change(cx);
1965 clone.selections.clone_state(&self.selections);
1966 clone
1967 .scroll_manager
1968 .clone_state(&self.scroll_manager, &my_snapshot, &clone_snapshot, cx);
1969 clone.searchable = self.searchable;
1970 clone.read_only = self.read_only;
1971 clone.buffers_with_disabled_indent_guides =
1972 self.buffers_with_disabled_indent_guides.clone();
1973 clone
1974 }
1975
1976 pub fn new(
1977 mode: EditorMode,
1978 buffer: Entity<MultiBuffer>,
1979 project: Option<Entity<Project>>,
1980 window: &mut Window,
1981 cx: &mut Context<Self>,
1982 ) -> Self {
1983 Editor::new_internal(mode, buffer, project, None, window, cx)
1984 }
1985
1986 pub fn refresh_sticky_headers(
1987 &mut self,
1988 display_snapshot: &DisplaySnapshot,
1989 cx: &mut Context<Editor>,
1990 ) {
1991 if !self.mode.is_full() {
1992 return;
1993 }
1994 let multi_buffer = display_snapshot.buffer_snapshot();
1995 let scroll_anchor = self
1996 .scroll_manager
1997 .native_anchor(display_snapshot, cx)
1998 .anchor;
1999 let Some((excerpt_id, _, buffer)) = multi_buffer.as_singleton() else {
2000 return;
2001 };
2002 let buffer = buffer.clone();
2003
2004 let buffer_visible_start = scroll_anchor.text_anchor.to_point(&buffer);
2005 let max_row = buffer.max_point().row;
2006 let start_row = buffer_visible_start.row.min(max_row);
2007 let end_row = (buffer_visible_start.row + 10).min(max_row);
2008
2009 let syntax = self.style(cx).syntax.clone();
2010 let background_task = cx.background_spawn(async move {
2011 buffer
2012 .outline_items_containing(
2013 Point::new(start_row, 0)..Point::new(end_row, 0),
2014 true,
2015 Some(syntax.as_ref()),
2016 )
2017 .into_iter()
2018 .map(|outline_item| OutlineItem {
2019 depth: outline_item.depth,
2020 range: Anchor::range_in_buffer(excerpt_id, outline_item.range),
2021 source_range_for_text: Anchor::range_in_buffer(
2022 excerpt_id,
2023 outline_item.source_range_for_text,
2024 ),
2025 text: outline_item.text,
2026 highlight_ranges: outline_item.highlight_ranges,
2027 name_ranges: outline_item.name_ranges,
2028 body_range: outline_item
2029 .body_range
2030 .map(|range| Anchor::range_in_buffer(excerpt_id, range)),
2031 annotation_range: outline_item
2032 .annotation_range
2033 .map(|range| Anchor::range_in_buffer(excerpt_id, range)),
2034 })
2035 .collect()
2036 });
2037 self.sticky_headers_task = cx.spawn(async move |this, cx| {
2038 let sticky_headers = background_task.await;
2039 this.update(cx, |this, cx| {
2040 this.sticky_headers = Some(sticky_headers);
2041 cx.notify();
2042 })
2043 .ok();
2044 });
2045 }
2046
2047 fn new_internal(
2048 mode: EditorMode,
2049 multi_buffer: Entity<MultiBuffer>,
2050 project: Option<Entity<Project>>,
2051 display_map: Option<Entity<DisplayMap>>,
2052 window: &mut Window,
2053 cx: &mut Context<Self>,
2054 ) -> Self {
2055 debug_assert!(
2056 display_map.is_none() || mode.is_minimap(),
2057 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
2058 );
2059
2060 let full_mode = mode.is_full();
2061 let is_minimap = mode.is_minimap();
2062 let diagnostics_max_severity = if full_mode {
2063 EditorSettings::get_global(cx)
2064 .diagnostics_max_severity
2065 .unwrap_or(DiagnosticSeverity::Hint)
2066 } else {
2067 DiagnosticSeverity::Off
2068 };
2069 let style = window.text_style();
2070 let font_size = style.font_size.to_pixels(window.rem_size());
2071 let editor = cx.entity().downgrade();
2072 let fold_placeholder = FoldPlaceholder {
2073 constrain_width: false,
2074 render: Arc::new(move |fold_id, fold_range, cx| {
2075 let editor = editor.clone();
2076 FoldPlaceholder::fold_element(fold_id, cx)
2077 .cursor_pointer()
2078 .child("⋯")
2079 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
2080 .on_click(move |_, _window, cx| {
2081 editor
2082 .update(cx, |editor, cx| {
2083 editor.unfold_ranges(
2084 &[fold_range.start..fold_range.end],
2085 true,
2086 false,
2087 cx,
2088 );
2089 cx.stop_propagation();
2090 })
2091 .ok();
2092 })
2093 .into_any()
2094 }),
2095 merge_adjacent: true,
2096 ..FoldPlaceholder::default()
2097 };
2098 let display_map = display_map.unwrap_or_else(|| {
2099 cx.new(|cx| {
2100 DisplayMap::new(
2101 multi_buffer.clone(),
2102 style.font(),
2103 font_size,
2104 None,
2105 FILE_HEADER_HEIGHT,
2106 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2107 fold_placeholder,
2108 diagnostics_max_severity,
2109 cx,
2110 )
2111 })
2112 });
2113
2114 let selections = SelectionsCollection::new();
2115
2116 let blink_manager = cx.new(|cx| {
2117 let mut blink_manager = BlinkManager::new(
2118 CURSOR_BLINK_INTERVAL,
2119 |cx| EditorSettings::get_global(cx).cursor_blink,
2120 cx,
2121 );
2122 if is_minimap {
2123 blink_manager.disable(cx);
2124 }
2125 blink_manager
2126 });
2127
2128 let soft_wrap_mode_override =
2129 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
2130
2131 let mut project_subscriptions = Vec::new();
2132 if full_mode && let Some(project) = project.as_ref() {
2133 project_subscriptions.push(cx.subscribe_in(
2134 project,
2135 window,
2136 |editor, _, event, window, cx| match event {
2137 project::Event::RefreshCodeLens => {
2138 // we always query lens with actions, without storing them, always refreshing them
2139 }
2140 project::Event::RefreshInlayHints {
2141 server_id,
2142 request_id,
2143 } => {
2144 editor.refresh_inlay_hints(
2145 InlayHintRefreshReason::RefreshRequested {
2146 server_id: *server_id,
2147 request_id: *request_id,
2148 },
2149 cx,
2150 );
2151 }
2152 project::Event::RefreshSemanticTokens {
2153 server_id,
2154 request_id,
2155 } => {
2156 editor.refresh_semantic_tokens(
2157 None,
2158 Some(RefreshForServer {
2159 server_id: *server_id,
2160 request_id: *request_id,
2161 }),
2162 cx,
2163 );
2164 }
2165 project::Event::LanguageServerRemoved(_) => {
2166 editor.registered_buffers.clear();
2167 editor.register_visible_buffers(cx);
2168 editor.invalidate_semantic_tokens(None);
2169 editor.refresh_runnables(None, window, cx);
2170 editor.update_lsp_data(None, window, cx);
2171 editor.refresh_inlay_hints(InlayHintRefreshReason::ServerRemoved, cx);
2172 }
2173 project::Event::SnippetEdit(id, snippet_edits) => {
2174 // todo(lw): Non singletons
2175 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
2176 let snapshot = buffer.read(cx).snapshot();
2177 let focus_handle = editor.focus_handle(cx);
2178 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
2179 for (range, snippet) in snippet_edits {
2180 let buffer_range =
2181 language::range_from_lsp(*range).to_offset(&snapshot);
2182 editor
2183 .insert_snippet(
2184 &[MultiBufferOffset(buffer_range.start)
2185 ..MultiBufferOffset(buffer_range.end)],
2186 snippet.clone(),
2187 window,
2188 cx,
2189 )
2190 .ok();
2191 }
2192 }
2193 }
2194 }
2195 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
2196 let buffer_id = *buffer_id;
2197 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
2198 editor.register_buffer(buffer_id, cx);
2199 editor.refresh_runnables(Some(buffer_id), window, cx);
2200 editor.update_lsp_data(Some(buffer_id), window, cx);
2201 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
2202 refresh_linked_ranges(editor, window, cx);
2203 editor.refresh_code_actions(window, cx);
2204 editor.refresh_document_highlights(cx);
2205 }
2206 }
2207
2208 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
2209 let Some(workspace) = editor.workspace() else {
2210 return;
2211 };
2212 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2213 else {
2214 return;
2215 };
2216
2217 if active_editor.entity_id() == cx.entity_id() {
2218 let entity_id = cx.entity_id();
2219 workspace.update(cx, |this, cx| {
2220 this.panes_mut()
2221 .iter_mut()
2222 .filter(|pane| pane.entity_id() != entity_id)
2223 .for_each(|p| {
2224 p.update(cx, |pane, _| {
2225 pane.nav_history_mut().rename_item(
2226 entity_id,
2227 project_path.clone(),
2228 abs_path.clone().into(),
2229 );
2230 })
2231 });
2232 });
2233
2234 Self::open_transaction_for_hidden_buffers(
2235 workspace,
2236 transaction.clone(),
2237 "Rename".to_string(),
2238 window,
2239 cx,
2240 );
2241 }
2242 }
2243
2244 project::Event::WorkspaceEditApplied(transaction) => {
2245 let Some(workspace) = editor.workspace() else {
2246 return;
2247 };
2248 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2249 else {
2250 return;
2251 };
2252
2253 if active_editor.entity_id() == cx.entity_id() {
2254 Self::open_transaction_for_hidden_buffers(
2255 workspace,
2256 transaction.clone(),
2257 "LSP Edit".to_string(),
2258 window,
2259 cx,
2260 );
2261 }
2262 }
2263
2264 _ => {}
2265 },
2266 ));
2267 if let Some(task_inventory) = project
2268 .read(cx)
2269 .task_store()
2270 .read(cx)
2271 .task_inventory()
2272 .cloned()
2273 {
2274 project_subscriptions.push(cx.observe_in(
2275 &task_inventory,
2276 window,
2277 |editor, _, window, cx| {
2278 editor.refresh_runnables(None, window, cx);
2279 },
2280 ));
2281 };
2282
2283 project_subscriptions.push(cx.subscribe_in(
2284 &project.read(cx).breakpoint_store(),
2285 window,
2286 |editor, _, event, window, cx| match event {
2287 BreakpointStoreEvent::ClearDebugLines => {
2288 editor.clear_row_highlights::<ActiveDebugLine>();
2289 editor.refresh_inline_values(cx);
2290 }
2291 BreakpointStoreEvent::SetDebugLine => {
2292 if editor.go_to_active_debug_line(window, cx) {
2293 cx.stop_propagation();
2294 }
2295
2296 editor.refresh_inline_values(cx);
2297 }
2298 _ => {}
2299 },
2300 ));
2301 let git_store = project.read(cx).git_store().clone();
2302 let project = project.clone();
2303 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2304 if let GitStoreEvent::RepositoryAdded = event {
2305 this.load_diff_task = Some(
2306 update_uncommitted_diff_for_buffer(
2307 cx.entity(),
2308 &project,
2309 this.buffer.read(cx).all_buffers(),
2310 this.buffer.clone(),
2311 cx,
2312 )
2313 .shared(),
2314 );
2315 }
2316 }));
2317 }
2318
2319 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2320
2321 let inlay_hint_settings =
2322 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2323 let focus_handle = cx.focus_handle();
2324 if !is_minimap {
2325 cx.on_focus(&focus_handle, window, Self::handle_focus)
2326 .detach();
2327 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2328 .detach();
2329 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2330 .detach();
2331 cx.on_blur(&focus_handle, window, Self::handle_blur)
2332 .detach();
2333 cx.observe_pending_input(window, Self::observe_pending_input)
2334 .detach();
2335 }
2336
2337 let show_indent_guides =
2338 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2339 Some(false)
2340 } else {
2341 None
2342 };
2343
2344 let breakpoint_store = match (&mode, project.as_ref()) {
2345 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2346 _ => None,
2347 };
2348
2349 let mut code_action_providers = Vec::new();
2350 let mut load_uncommitted_diff = None;
2351 if let Some(project) = project.clone() {
2352 load_uncommitted_diff = Some(
2353 update_uncommitted_diff_for_buffer(
2354 cx.entity(),
2355 &project,
2356 multi_buffer.read(cx).all_buffers(),
2357 multi_buffer.clone(),
2358 cx,
2359 )
2360 .shared(),
2361 );
2362 code_action_providers.push(Rc::new(project) as Rc<_>);
2363 }
2364
2365 let mut editor = Self {
2366 focus_handle,
2367 show_cursor_when_unfocused: false,
2368 last_focused_descendant: None,
2369 buffer: multi_buffer.clone(),
2370 display_map: display_map.clone(),
2371 placeholder_display_map: None,
2372 selections,
2373 scroll_manager: ScrollManager::new(cx),
2374 columnar_selection_state: None,
2375 add_selections_state: None,
2376 select_next_state: None,
2377 select_prev_state: None,
2378 selection_history: SelectionHistory::default(),
2379 defer_selection_effects: false,
2380 deferred_selection_effects_state: None,
2381 autoclose_regions: Vec::new(),
2382 snippet_stack: InvalidationStack::default(),
2383 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2384 ime_transaction: None,
2385 active_diagnostics: ActiveDiagnostic::None,
2386 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2387 inline_diagnostics_update: Task::ready(()),
2388 inline_diagnostics: Vec::new(),
2389 soft_wrap_mode_override,
2390 diagnostics_max_severity,
2391 hard_wrap: None,
2392 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2393 semantics_provider: project
2394 .as_ref()
2395 .map(|project| Rc::new(project.downgrade()) as _),
2396 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2397 project,
2398 blink_manager: blink_manager.clone(),
2399 show_local_selections: true,
2400 show_scrollbars: ScrollbarAxes {
2401 horizontal: full_mode,
2402 vertical: full_mode,
2403 },
2404 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2405 offset_content: !matches!(mode, EditorMode::SingleLine),
2406 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2407 show_gutter: full_mode,
2408 show_line_numbers: (!full_mode).then_some(false),
2409 use_relative_line_numbers: None,
2410 disable_expand_excerpt_buttons: !full_mode,
2411 delegate_expand_excerpts: false,
2412 delegate_stage_and_restore: false,
2413 delegate_open_excerpts: false,
2414 enable_lsp_data: true,
2415 enable_runnables: true,
2416 show_git_diff_gutter: None,
2417 show_code_actions: None,
2418 show_runnables: None,
2419 show_breakpoints: None,
2420 show_diff_review_button: false,
2421 show_wrap_guides: None,
2422 show_indent_guides,
2423 buffers_with_disabled_indent_guides: HashSet::default(),
2424 highlight_order: 0,
2425 highlighted_rows: HashMap::default(),
2426 background_highlights: HashMap::default(),
2427 gutter_highlights: HashMap::default(),
2428 scrollbar_marker_state: ScrollbarMarkerState::default(),
2429 active_indent_guides_state: ActiveIndentGuidesState::default(),
2430 nav_history: None,
2431 context_menu: RefCell::new(None),
2432 context_menu_options: None,
2433 mouse_context_menu: None,
2434 completion_tasks: Vec::new(),
2435 inline_blame_popover: None,
2436 inline_blame_popover_show_task: None,
2437 signature_help_state: SignatureHelpState::default(),
2438 auto_signature_help: None,
2439 find_all_references_task_sources: Vec::new(),
2440 next_completion_id: 0,
2441 next_inlay_id: 0,
2442 code_action_providers,
2443 available_code_actions: None,
2444 code_actions_task: None,
2445 quick_selection_highlight_task: None,
2446 debounced_selection_highlight_task: None,
2447 debounced_selection_highlight_complete: false,
2448 document_highlights_task: None,
2449 linked_editing_range_task: None,
2450 pending_rename: None,
2451 searchable: !is_minimap,
2452 cursor_shape: EditorSettings::get_global(cx)
2453 .cursor_shape
2454 .unwrap_or_default(),
2455 cursor_offset_on_selection: false,
2456 current_line_highlight: None,
2457 autoindent_mode: Some(AutoindentMode::EachLine),
2458 collapse_matches: false,
2459 workspace: None,
2460 input_enabled: !is_minimap,
2461 expects_character_input: !is_minimap,
2462 use_modal_editing: full_mode,
2463 read_only: is_minimap,
2464 use_autoclose: true,
2465 use_auto_surround: true,
2466 auto_replace_emoji_shortcode: false,
2467 jsx_tag_auto_close_enabled_in_any_buffer: false,
2468 leader_id: None,
2469 remote_id: None,
2470 hover_state: HoverState::default(),
2471 pending_mouse_down: None,
2472 prev_pressure_stage: None,
2473 hovered_link_state: None,
2474 edit_prediction_provider: None,
2475 active_edit_prediction: None,
2476 stale_edit_prediction_in_menu: None,
2477 edit_prediction_preview: EditPredictionPreview::Inactive {
2478 released_too_fast: false,
2479 },
2480 inline_diagnostics_enabled: full_mode,
2481 diagnostics_enabled: full_mode,
2482 word_completions_enabled: full_mode,
2483 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2484 gutter_hovered: false,
2485 pixel_position_of_newest_cursor: None,
2486 last_bounds: None,
2487 last_position_map: None,
2488 expect_bounds_change: None,
2489 gutter_dimensions: GutterDimensions::default(),
2490 style: None,
2491 show_cursor_names: false,
2492 hovered_cursors: HashMap::default(),
2493 next_editor_action_id: EditorActionId::default(),
2494 editor_actions: Rc::default(),
2495 edit_predictions_hidden_for_vim_mode: false,
2496 show_edit_predictions_override: None,
2497 show_completions_on_input_override: None,
2498 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2499 edit_prediction_settings: EditPredictionSettings::Disabled,
2500 in_leading_whitespace: false,
2501 custom_context_menu: None,
2502 show_git_blame_gutter: false,
2503 show_git_blame_inline: false,
2504 show_selection_menu: None,
2505 show_git_blame_inline_delay_task: None,
2506 git_blame_inline_enabled: full_mode
2507 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2508 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2509 buffer_serialization: is_minimap.not().then(|| {
2510 BufferSerialization::new(
2511 ProjectSettings::get_global(cx)
2512 .session
2513 .restore_unsaved_buffers,
2514 )
2515 }),
2516 blame: None,
2517 blame_subscription: None,
2518
2519 breakpoint_store,
2520 gutter_breakpoint_indicator: (None, None),
2521 gutter_diff_review_indicator: (None, None),
2522 diff_review_drag_state: None,
2523 diff_review_overlays: Vec::new(),
2524 stored_review_comments: Vec::new(),
2525 next_review_comment_id: 0,
2526 hovered_diff_hunk_row: None,
2527 _subscriptions: (!is_minimap)
2528 .then(|| {
2529 vec![
2530 cx.observe(&multi_buffer, Self::on_buffer_changed),
2531 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2532 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2533 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2534 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2535 cx.observe_global_in::<GlobalTheme>(window, Self::theme_changed),
2536 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2537 cx.observe_window_activation(window, |editor, window, cx| {
2538 let active = window.is_window_active();
2539 editor.blink_manager.update(cx, |blink_manager, cx| {
2540 if active {
2541 blink_manager.enable(cx);
2542 } else {
2543 blink_manager.disable(cx);
2544 }
2545 });
2546 if active {
2547 editor.show_mouse_cursor(cx);
2548 }
2549 }),
2550 ]
2551 })
2552 .unwrap_or_default(),
2553 runnables: RunnableData::new(),
2554 pull_diagnostics_task: Task::ready(()),
2555 colors: None,
2556 refresh_colors_task: Task::ready(()),
2557 use_document_folding_ranges: false,
2558 refresh_folding_ranges_task: Task::ready(()),
2559 inlay_hints: None,
2560 next_color_inlay_id: 0,
2561 post_scroll_update: Task::ready(()),
2562 linked_edit_ranges: Default::default(),
2563 in_project_search: false,
2564 previous_search_ranges: None,
2565 breadcrumb_header: None,
2566 focused_block: None,
2567 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2568 addons: HashMap::default(),
2569 registered_buffers: HashMap::default(),
2570 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2571 selection_mark_mode: false,
2572 toggle_fold_multiple_buffers: Task::ready(()),
2573 serialize_selections: Task::ready(()),
2574 serialize_folds: Task::ready(()),
2575 text_style_refinement: None,
2576 load_diff_task: load_uncommitted_diff,
2577 temporary_diff_override: false,
2578 mouse_cursor_hidden: false,
2579 minimap: None,
2580 hide_mouse_mode: EditorSettings::get_global(cx)
2581 .hide_mouse
2582 .unwrap_or_default(),
2583 change_list: ChangeList::new(),
2584 mode,
2585 selection_drag_state: SelectionDragState::None,
2586 folding_newlines: Task::ready(()),
2587 lookup_key: None,
2588 select_next_is_case_sensitive: None,
2589 on_local_selections_changed: None,
2590 suppress_selection_callback: false,
2591 applicable_language_settings: HashMap::default(),
2592 semantic_token_state: SemanticTokenState::new(cx, full_mode),
2593 accent_data: None,
2594 bracket_fetched_tree_sitter_chunks: HashMap::default(),
2595 number_deleted_lines: false,
2596 refresh_matching_bracket_highlights_task: Task::ready(()),
2597 refresh_document_symbols_task: Task::ready(()).shared(),
2598 lsp_document_symbols: HashMap::default(),
2599 refresh_outline_symbols_at_cursor_at_cursor_task: Task::ready(()),
2600 outline_symbols_at_cursor: None,
2601 sticky_headers_task: Task::ready(()),
2602 sticky_headers: None,
2603 colorize_brackets_task: Task::ready(()),
2604 };
2605
2606 if is_minimap {
2607 return editor;
2608 }
2609
2610 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2611 editor.accent_data = editor.fetch_accent_data(cx);
2612
2613 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2614 editor
2615 ._subscriptions
2616 .push(cx.observe(breakpoints, |_, _, cx| {
2617 cx.notify();
2618 }));
2619 }
2620 editor._subscriptions.extend(project_subscriptions);
2621
2622 editor._subscriptions.push(cx.subscribe_in(
2623 &cx.entity(),
2624 window,
2625 |editor, _, e: &EditorEvent, window, cx| match e {
2626 EditorEvent::ScrollPositionChanged { local, .. } => {
2627 if *local {
2628 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2629 editor.inline_blame_popover.take();
2630 let snapshot = editor.snapshot(window, cx);
2631 let new_anchor = editor
2632 .scroll_manager
2633 .native_anchor(&snapshot.display_snapshot, cx);
2634 editor.update_restoration_data(cx, move |data| {
2635 data.scroll_position = (
2636 new_anchor.top_row(snapshot.buffer_snapshot()),
2637 new_anchor.offset,
2638 );
2639 });
2640
2641 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2642 cx.background_executor()
2643 .timer(Duration::from_millis(50))
2644 .await;
2645 editor
2646 .update_in(cx, |editor, window, cx| {
2647 editor.update_data_on_scroll(window, cx)
2648 })
2649 .ok();
2650 });
2651 }
2652 editor.refresh_sticky_headers(&editor.snapshot(window, cx), cx);
2653 }
2654 EditorEvent::Edited { .. } => {
2655 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2656 .map(|vim_mode| vim_mode.0)
2657 .unwrap_or(false);
2658 if !vim_mode {
2659 let display_map = editor.display_snapshot(cx);
2660 let selections = editor.selections.all_adjusted_display(&display_map);
2661 let pop_state = editor
2662 .change_list
2663 .last()
2664 .map(|previous| {
2665 previous.len() == selections.len()
2666 && previous.iter().enumerate().all(|(ix, p)| {
2667 p.to_display_point(&display_map).row()
2668 == selections[ix].head().row()
2669 })
2670 })
2671 .unwrap_or(false);
2672 let new_positions = selections
2673 .into_iter()
2674 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2675 .collect();
2676 editor
2677 .change_list
2678 .push_to_change_list(pop_state, new_positions);
2679 }
2680 }
2681 _ => (),
2682 },
2683 ));
2684
2685 if let Some(dap_store) = editor
2686 .project
2687 .as_ref()
2688 .map(|project| project.read(cx).dap_store())
2689 {
2690 let weak_editor = cx.weak_entity();
2691
2692 editor
2693 ._subscriptions
2694 .push(
2695 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2696 let session_entity = cx.entity();
2697 weak_editor
2698 .update(cx, |editor, cx| {
2699 editor._subscriptions.push(
2700 cx.subscribe(&session_entity, Self::on_debug_session_event),
2701 );
2702 })
2703 .ok();
2704 }),
2705 );
2706
2707 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2708 editor
2709 ._subscriptions
2710 .push(cx.subscribe(&session, Self::on_debug_session_event));
2711 }
2712 }
2713
2714 // skip adding the initial selection to selection history
2715 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2716 editor.end_selection(window, cx);
2717 editor.selection_history.mode = SelectionHistoryMode::Normal;
2718
2719 editor.scroll_manager.show_scrollbars(window, cx);
2720 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2721
2722 if full_mode {
2723 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2724 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2725
2726 if editor.git_blame_inline_enabled {
2727 editor.start_git_blame_inline(false, window, cx);
2728 }
2729
2730 editor.go_to_active_debug_line(window, cx);
2731
2732 editor.minimap =
2733 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2734 editor.colors = Some(LspColorData::new(cx));
2735 editor.use_document_folding_ranges = true;
2736 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2737
2738 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2739 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2740 }
2741 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2742 }
2743
2744 editor
2745 }
2746
2747 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2748 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2749 }
2750
2751 pub fn deploy_mouse_context_menu(
2752 &mut self,
2753 position: gpui::Point<Pixels>,
2754 context_menu: Entity<ContextMenu>,
2755 window: &mut Window,
2756 cx: &mut Context<Self>,
2757 ) {
2758 self.mouse_context_menu = Some(MouseContextMenu::new(
2759 self,
2760 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2761 context_menu,
2762 window,
2763 cx,
2764 ));
2765 }
2766
2767 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2768 self.mouse_context_menu
2769 .as_ref()
2770 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2771 }
2772
2773 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2774 if self
2775 .selections
2776 .pending_anchor()
2777 .is_some_and(|pending_selection| {
2778 let snapshot = self.buffer().read(cx).snapshot(cx);
2779 pending_selection.range().includes(range, &snapshot)
2780 })
2781 {
2782 return true;
2783 }
2784
2785 self.selections
2786 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2787 .into_iter()
2788 .any(|selection| {
2789 // This is needed to cover a corner case, if we just check for an existing
2790 // selection in the fold range, having a cursor at the start of the fold
2791 // marks it as selected. Non-empty selections don't cause this.
2792 let length = selection.end - selection.start;
2793 length > 0
2794 })
2795 }
2796
2797 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2798 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2799 }
2800
2801 fn key_context_internal(
2802 &self,
2803 has_active_edit_prediction: bool,
2804 window: &mut Window,
2805 cx: &mut App,
2806 ) -> KeyContext {
2807 let mut key_context = KeyContext::new_with_defaults();
2808 key_context.add("Editor");
2809 let mode = match self.mode {
2810 EditorMode::SingleLine => "single_line",
2811 EditorMode::AutoHeight { .. } => "auto_height",
2812 EditorMode::Minimap { .. } => "minimap",
2813 EditorMode::Full { .. } => "full",
2814 };
2815
2816 if EditorSettings::jupyter_enabled(cx) {
2817 key_context.add("jupyter");
2818 }
2819
2820 key_context.set("mode", mode);
2821 if self.pending_rename.is_some() {
2822 key_context.add("renaming");
2823 }
2824
2825 if let Some(snippet_stack) = self.snippet_stack.last() {
2826 key_context.add("in_snippet");
2827
2828 if snippet_stack.active_index > 0 {
2829 key_context.add("has_previous_tabstop");
2830 }
2831
2832 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2833 key_context.add("has_next_tabstop");
2834 }
2835 }
2836
2837 match self.context_menu.borrow().as_ref() {
2838 Some(CodeContextMenu::Completions(menu)) => {
2839 if menu.visible() {
2840 key_context.add("menu");
2841 key_context.add("showing_completions");
2842 }
2843 }
2844 Some(CodeContextMenu::CodeActions(menu)) => {
2845 if menu.visible() {
2846 key_context.add("menu");
2847 key_context.add("showing_code_actions")
2848 }
2849 }
2850 None => {}
2851 }
2852
2853 if self.signature_help_state.has_multiple_signatures() {
2854 key_context.add("showing_signature_help");
2855 }
2856
2857 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2858 if !self.focus_handle(cx).contains_focused(window, cx)
2859 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2860 {
2861 for addon in self.addons.values() {
2862 addon.extend_key_context(&mut key_context, cx)
2863 }
2864 }
2865
2866 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2867 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2868 Some(
2869 file.full_path(cx)
2870 .extension()?
2871 .to_string_lossy()
2872 .to_lowercase(),
2873 )
2874 }) {
2875 key_context.set("extension", extension);
2876 }
2877 } else {
2878 key_context.add("multibuffer");
2879 }
2880
2881 if has_active_edit_prediction {
2882 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2883 key_context.add("copilot_suggestion");
2884 }
2885
2886 if self.in_leading_whitespace {
2887 key_context.add("in_leading_whitespace");
2888 }
2889 if self.edit_prediction_requires_modifier() {
2890 key_context.set("edit_prediction_mode", "subtle")
2891 } else {
2892 key_context.set("edit_prediction_mode", "eager");
2893 }
2894
2895 if self.selection_mark_mode {
2896 key_context.add("selection_mode");
2897 }
2898
2899 let disjoint = self.selections.disjoint_anchors();
2900 if matches!(
2901 &self.mode,
2902 EditorMode::SingleLine | EditorMode::AutoHeight { .. }
2903 ) && let [selection] = disjoint
2904 && selection.start == selection.end
2905 {
2906 let snapshot = self.snapshot(window, cx);
2907 let snapshot = snapshot.buffer_snapshot();
2908 let caret_offset = selection.end.to_offset(snapshot);
2909
2910 if caret_offset == MultiBufferOffset(0) {
2911 key_context.add("start_of_input");
2912 }
2913
2914 if caret_offset == snapshot.len() {
2915 key_context.add("end_of_input");
2916 }
2917 }
2918
2919 if self.has_any_expanded_diff_hunks(cx) {
2920 key_context.add("diffs_expanded");
2921 }
2922
2923 key_context
2924 }
2925
2926 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2927 self.last_bounds.as_ref()
2928 }
2929
2930 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2931 if self.mouse_cursor_hidden {
2932 self.mouse_cursor_hidden = false;
2933 cx.notify();
2934 }
2935 }
2936
2937 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2938 let hide_mouse_cursor = match origin {
2939 HideMouseCursorOrigin::TypingAction => {
2940 matches!(
2941 self.hide_mouse_mode,
2942 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2943 )
2944 }
2945 HideMouseCursorOrigin::MovementAction => {
2946 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2947 }
2948 };
2949 if self.mouse_cursor_hidden != hide_mouse_cursor {
2950 self.mouse_cursor_hidden = hide_mouse_cursor;
2951 cx.notify();
2952 }
2953 }
2954
2955 fn accept_edit_prediction_keystroke(
2956 &self,
2957 granularity: EditPredictionGranularity,
2958 window: &mut Window,
2959 cx: &mut App,
2960 ) -> Option<gpui::KeybindingKeystroke> {
2961 let key_context = self.key_context_internal(true, window, cx);
2962
2963 let bindings =
2964 match granularity {
2965 EditPredictionGranularity::Word => window
2966 .bindings_for_action_in_context(&AcceptNextWordEditPrediction, key_context),
2967 EditPredictionGranularity::Line => window
2968 .bindings_for_action_in_context(&AcceptNextLineEditPrediction, key_context),
2969 EditPredictionGranularity::Full => {
2970 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2971 }
2972 };
2973
2974 bindings
2975 .into_iter()
2976 .rev()
2977 .find_map(|binding| match binding.keystrokes() {
2978 [keystroke, ..] => Some(keystroke.clone()),
2979 _ => None,
2980 })
2981 }
2982
2983 fn preview_edit_prediction_keystroke(
2984 &self,
2985 window: &mut Window,
2986 cx: &mut App,
2987 ) -> Option<gpui::KeybindingKeystroke> {
2988 let key_context = self.key_context_internal(true, window, cx);
2989 let bindings = window.bindings_for_action_in_context(&AcceptEditPrediction, key_context);
2990 bindings
2991 .into_iter()
2992 .rev()
2993 .find_map(|binding| match binding.keystrokes() {
2994 [keystroke, ..] if keystroke.modifiers().modified() => Some(keystroke.clone()),
2995 _ => None,
2996 })
2997 }
2998
2999 fn edit_prediction_preview_modifiers_held(
3000 &self,
3001 modifiers: &Modifiers,
3002 window: &mut Window,
3003 cx: &mut App,
3004 ) -> bool {
3005 let key_context = self.key_context_internal(true, window, cx);
3006 let actions: [&dyn Action; 3] = [
3007 &AcceptEditPrediction,
3008 &AcceptNextWordEditPrediction,
3009 &AcceptNextLineEditPrediction,
3010 ];
3011
3012 actions.into_iter().any(|action| {
3013 window
3014 .bindings_for_action_in_context(action, key_context.clone())
3015 .into_iter()
3016 .rev()
3017 .any(|binding| {
3018 binding.keystrokes().first().is_some_and(|keystroke| {
3019 keystroke.modifiers().modified() && keystroke.modifiers() == modifiers
3020 })
3021 })
3022 })
3023 }
3024
3025 fn edit_prediction_cursor_popover_prefers_preview(
3026 &self,
3027 completion: &EditPredictionState,
3028 ) -> bool {
3029 match &completion.completion {
3030 EditPrediction::Edit {
3031 edits, snapshot, ..
3032 } => {
3033 let mut start_row: Option<u32> = None;
3034 let mut end_row: Option<u32> = None;
3035
3036 for (range, text) in edits {
3037 let edit_start_row = range.start.text_anchor.to_point(snapshot).row;
3038 let old_end_row = range.end.text_anchor.to_point(snapshot).row;
3039 let inserted_newline_count = text
3040 .as_ref()
3041 .chars()
3042 .filter(|character| *character == '\n')
3043 .count() as u32;
3044 let deleted_newline_count = old_end_row - edit_start_row;
3045 let preview_end_row = edit_start_row + inserted_newline_count;
3046
3047 start_row =
3048 Some(start_row.map_or(edit_start_row, |row| row.min(edit_start_row)));
3049 end_row = Some(end_row.map_or(preview_end_row, |row| row.max(preview_end_row)));
3050
3051 if deleted_newline_count > 1 {
3052 end_row = Some(end_row.map_or(old_end_row, |row| row.max(old_end_row)));
3053 }
3054 }
3055
3056 start_row
3057 .zip(end_row)
3058 .is_some_and(|(start_row, end_row)| end_row > start_row)
3059 }
3060 EditPrediction::MoveWithin { .. } | EditPrediction::MoveOutside { .. } => false,
3061 }
3062 }
3063
3064 fn edit_prediction_keybind_display(
3065 &self,
3066 surface: EditPredictionKeybindSurface,
3067 window: &mut Window,
3068 cx: &mut App,
3069 ) -> EditPredictionKeybindDisplay {
3070 let accept_keystroke =
3071 self.accept_edit_prediction_keystroke(EditPredictionGranularity::Full, window, cx);
3072 let preview_keystroke = self.preview_edit_prediction_keystroke(window, cx);
3073
3074 let action = match surface {
3075 EditPredictionKeybindSurface::Inline
3076 | EditPredictionKeybindSurface::CursorPopoverCompact => {
3077 if self.edit_prediction_requires_modifier() {
3078 EditPredictionKeybindAction::Preview
3079 } else {
3080 EditPredictionKeybindAction::Accept
3081 }
3082 }
3083 EditPredictionKeybindSurface::CursorPopoverExpanded => self
3084 .active_edit_prediction
3085 .as_ref()
3086 .filter(|completion| {
3087 self.edit_prediction_cursor_popover_prefers_preview(completion)
3088 })
3089 .map_or(EditPredictionKeybindAction::Accept, |_| {
3090 EditPredictionKeybindAction::Preview
3091 }),
3092 };
3093 #[cfg(test)]
3094 let preview_copy = preview_keystroke.clone();
3095 #[cfg(test)]
3096 let accept_copy = accept_keystroke.clone();
3097
3098 let displayed_keystroke = match surface {
3099 EditPredictionKeybindSurface::Inline => match action {
3100 EditPredictionKeybindAction::Accept => accept_keystroke,
3101 EditPredictionKeybindAction::Preview => preview_keystroke,
3102 },
3103 EditPredictionKeybindSurface::CursorPopoverCompact
3104 | EditPredictionKeybindSurface::CursorPopoverExpanded => match action {
3105 EditPredictionKeybindAction::Accept => accept_keystroke,
3106 EditPredictionKeybindAction::Preview => {
3107 preview_keystroke.or_else(|| accept_keystroke.clone())
3108 }
3109 },
3110 };
3111
3112 let missing_accept_keystroke = displayed_keystroke.is_none();
3113
3114 EditPredictionKeybindDisplay {
3115 #[cfg(test)]
3116 accept_keystroke: accept_copy,
3117 #[cfg(test)]
3118 preview_keystroke: preview_copy,
3119 displayed_keystroke,
3120 action,
3121 missing_accept_keystroke,
3122 show_hold_label: matches!(surface, EditPredictionKeybindSurface::CursorPopoverCompact)
3123 && self.edit_prediction_preview.released_too_fast(),
3124 }
3125 }
3126
3127 pub fn new_file(
3128 workspace: &mut Workspace,
3129 _: &workspace::NewFile,
3130 window: &mut Window,
3131 cx: &mut Context<Workspace>,
3132 ) {
3133 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
3134 "Failed to create buffer",
3135 window,
3136 cx,
3137 |e, _, _| match e.error_code() {
3138 ErrorCode::RemoteUpgradeRequired => Some(format!(
3139 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
3140 e.error_tag("required").unwrap_or("the latest version")
3141 )),
3142 _ => None,
3143 },
3144 );
3145 }
3146
3147 pub fn new_in_workspace(
3148 workspace: &mut Workspace,
3149 window: &mut Window,
3150 cx: &mut Context<Workspace>,
3151 ) -> Task<Result<Entity<Editor>>> {
3152 let project = workspace.project().clone();
3153 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
3154
3155 cx.spawn_in(window, async move |workspace, cx| {
3156 let buffer = create.await?;
3157 workspace.update_in(cx, |workspace, window, cx| {
3158 let editor =
3159 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
3160 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
3161 editor
3162 })
3163 })
3164 }
3165
3166 fn new_file_vertical(
3167 workspace: &mut Workspace,
3168 _: &workspace::NewFileSplitVertical,
3169 window: &mut Window,
3170 cx: &mut Context<Workspace>,
3171 ) {
3172 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
3173 }
3174
3175 fn new_file_horizontal(
3176 workspace: &mut Workspace,
3177 _: &workspace::NewFileSplitHorizontal,
3178 window: &mut Window,
3179 cx: &mut Context<Workspace>,
3180 ) {
3181 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
3182 }
3183
3184 fn new_file_split(
3185 workspace: &mut Workspace,
3186 action: &workspace::NewFileSplit,
3187 window: &mut Window,
3188 cx: &mut Context<Workspace>,
3189 ) {
3190 Self::new_file_in_direction(workspace, action.0, window, cx)
3191 }
3192
3193 fn new_file_in_direction(
3194 workspace: &mut Workspace,
3195 direction: SplitDirection,
3196 window: &mut Window,
3197 cx: &mut Context<Workspace>,
3198 ) {
3199 let project = workspace.project().clone();
3200 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
3201
3202 cx.spawn_in(window, async move |workspace, cx| {
3203 let buffer = create.await?;
3204 workspace.update_in(cx, move |workspace, window, cx| {
3205 workspace.split_item(
3206 direction,
3207 Box::new(
3208 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
3209 ),
3210 window,
3211 cx,
3212 )
3213 })?;
3214 anyhow::Ok(())
3215 })
3216 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
3217 match e.error_code() {
3218 ErrorCode::RemoteUpgradeRequired => Some(format!(
3219 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
3220 e.error_tag("required").unwrap_or("the latest version")
3221 )),
3222 _ => None,
3223 }
3224 });
3225 }
3226
3227 pub fn leader_id(&self) -> Option<CollaboratorId> {
3228 self.leader_id
3229 }
3230
3231 pub fn buffer(&self) -> &Entity<MultiBuffer> {
3232 &self.buffer
3233 }
3234
3235 pub fn project(&self) -> Option<&Entity<Project>> {
3236 self.project.as_ref()
3237 }
3238
3239 pub fn workspace(&self) -> Option<Entity<Workspace>> {
3240 self.workspace.as_ref()?.0.upgrade()
3241 }
3242
3243 /// Detaches a task and shows an error notification in the workspace if available,
3244 /// otherwise just logs the error.
3245 pub fn detach_and_notify_err<R, E>(
3246 &self,
3247 task: Task<Result<R, E>>,
3248 window: &mut Window,
3249 cx: &mut App,
3250 ) where
3251 E: std::fmt::Debug + std::fmt::Display + 'static,
3252 R: 'static,
3253 {
3254 if let Some(workspace) = self.workspace() {
3255 task.detach_and_notify_err(workspace.downgrade(), window, cx);
3256 } else {
3257 task.detach_and_log_err(cx);
3258 }
3259 }
3260
3261 /// Returns the workspace serialization ID if this editor should be serialized.
3262 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
3263 self.workspace
3264 .as_ref()
3265 .filter(|_| self.should_serialize_buffer())
3266 .and_then(|workspace| workspace.1)
3267 }
3268
3269 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
3270 self.buffer().read(cx).title(cx)
3271 }
3272
3273 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
3274 let git_blame_gutter_max_author_length = self
3275 .render_git_blame_gutter(cx)
3276 .then(|| {
3277 if let Some(blame) = self.blame.as_ref() {
3278 let max_author_length =
3279 blame.update(cx, |blame, cx| blame.max_author_length(cx));
3280 Some(max_author_length)
3281 } else {
3282 None
3283 }
3284 })
3285 .flatten();
3286
3287 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3288
3289 EditorSnapshot {
3290 mode: self.mode.clone(),
3291 show_gutter: self.show_gutter,
3292 offset_content: self.offset_content,
3293 show_line_numbers: self.show_line_numbers,
3294 number_deleted_lines: self.number_deleted_lines,
3295 show_git_diff_gutter: self.show_git_diff_gutter,
3296 semantic_tokens_enabled: self.semantic_token_state.enabled(),
3297 show_code_actions: self.show_code_actions,
3298 show_runnables: self.show_runnables,
3299 show_breakpoints: self.show_breakpoints,
3300 git_blame_gutter_max_author_length,
3301 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
3302 display_snapshot,
3303 placeholder_display_snapshot: self
3304 .placeholder_display_map
3305 .as_ref()
3306 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
3307 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
3308 is_focused: self.focus_handle.is_focused(window),
3309 current_line_highlight: self
3310 .current_line_highlight
3311 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
3312 gutter_hovered: self.gutter_hovered,
3313 }
3314 }
3315
3316 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
3317 self.buffer.read(cx).language_at(point, cx)
3318 }
3319
3320 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
3321 self.buffer.read(cx).read(cx).file_at(point).cloned()
3322 }
3323
3324 pub fn active_excerpt(
3325 &self,
3326 cx: &App,
3327 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
3328 self.buffer
3329 .read(cx)
3330 .excerpt_containing(self.selections.newest_anchor().head(), cx)
3331 }
3332
3333 pub fn mode(&self) -> &EditorMode {
3334 &self.mode
3335 }
3336
3337 pub fn set_mode(&mut self, mode: EditorMode) {
3338 self.mode = mode;
3339 }
3340
3341 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
3342 self.collaboration_hub.as_deref()
3343 }
3344
3345 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
3346 self.collaboration_hub = Some(hub);
3347 }
3348
3349 pub fn set_in_project_search(&mut self, in_project_search: bool) {
3350 self.in_project_search = in_project_search;
3351 }
3352
3353 pub fn set_custom_context_menu(
3354 &mut self,
3355 f: impl 'static
3356 + Fn(
3357 &mut Self,
3358 DisplayPoint,
3359 &mut Window,
3360 &mut Context<Self>,
3361 ) -> Option<Entity<ui::ContextMenu>>,
3362 ) {
3363 self.custom_context_menu = Some(Box::new(f))
3364 }
3365
3366 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
3367 self.completion_provider = provider;
3368 }
3369
3370 #[cfg(any(test, feature = "test-support"))]
3371 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
3372 self.completion_provider.clone()
3373 }
3374
3375 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
3376 self.semantics_provider.clone()
3377 }
3378
3379 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
3380 self.semantics_provider = provider;
3381 }
3382
3383 pub fn set_edit_prediction_provider<T>(
3384 &mut self,
3385 provider: Option<Entity<T>>,
3386 window: &mut Window,
3387 cx: &mut Context<Self>,
3388 ) where
3389 T: EditPredictionDelegate,
3390 {
3391 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionDelegate {
3392 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
3393 if this.focus_handle.is_focused(window) {
3394 this.update_visible_edit_prediction(window, cx);
3395 }
3396 }),
3397 provider: Arc::new(provider),
3398 });
3399 self.update_edit_prediction_settings(cx);
3400 self.refresh_edit_prediction(false, false, window, cx);
3401 }
3402
3403 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
3404 self.placeholder_display_map
3405 .as_ref()
3406 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
3407 }
3408
3409 pub fn set_placeholder_text(
3410 &mut self,
3411 placeholder_text: &str,
3412 window: &mut Window,
3413 cx: &mut Context<Self>,
3414 ) {
3415 let multibuffer = cx
3416 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3417
3418 let style = window.text_style();
3419
3420 self.placeholder_display_map = Some(cx.new(|cx| {
3421 DisplayMap::new(
3422 multibuffer,
3423 style.font(),
3424 style.font_size.to_pixels(window.rem_size()),
3425 None,
3426 FILE_HEADER_HEIGHT,
3427 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3428 Default::default(),
3429 DiagnosticSeverity::Off,
3430 cx,
3431 )
3432 }));
3433 cx.notify();
3434 }
3435
3436 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3437 self.cursor_shape = cursor_shape;
3438
3439 // Disrupt blink for immediate user feedback that the cursor shape has changed
3440 self.blink_manager.update(cx, BlinkManager::show_cursor);
3441
3442 cx.notify();
3443 }
3444
3445 pub fn cursor_shape(&self) -> CursorShape {
3446 self.cursor_shape
3447 }
3448
3449 pub fn set_cursor_offset_on_selection(&mut self, set_cursor_offset_on_selection: bool) {
3450 self.cursor_offset_on_selection = set_cursor_offset_on_selection;
3451 }
3452
3453 pub fn set_current_line_highlight(
3454 &mut self,
3455 current_line_highlight: Option<CurrentLineHighlight>,
3456 ) {
3457 self.current_line_highlight = current_line_highlight;
3458 }
3459
3460 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3461 self.collapse_matches = collapse_matches;
3462 }
3463
3464 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3465 if self.collapse_matches {
3466 return range.start..range.start;
3467 }
3468 range.clone()
3469 }
3470
3471 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3472 self.display_map.read(cx).clip_at_line_ends
3473 }
3474
3475 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3476 if self.display_map.read(cx).clip_at_line_ends != clip {
3477 self.display_map
3478 .update(cx, |map, _| map.clip_at_line_ends = clip);
3479 }
3480 }
3481
3482 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3483 self.input_enabled = input_enabled;
3484 }
3485
3486 pub fn set_expects_character_input(&mut self, expects_character_input: bool) {
3487 self.expects_character_input = expects_character_input;
3488 }
3489
3490 pub fn set_edit_predictions_hidden_for_vim_mode(
3491 &mut self,
3492 hidden: bool,
3493 window: &mut Window,
3494 cx: &mut Context<Self>,
3495 ) {
3496 if hidden != self.edit_predictions_hidden_for_vim_mode {
3497 self.edit_predictions_hidden_for_vim_mode = hidden;
3498 if hidden {
3499 self.update_visible_edit_prediction(window, cx);
3500 } else {
3501 self.refresh_edit_prediction(true, false, window, cx);
3502 }
3503 }
3504 }
3505
3506 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3507 self.menu_edit_predictions_policy = value;
3508 }
3509
3510 pub fn set_autoindent(&mut self, autoindent: bool) {
3511 if autoindent {
3512 self.autoindent_mode = Some(AutoindentMode::EachLine);
3513 } else {
3514 self.autoindent_mode = None;
3515 }
3516 }
3517
3518 pub fn capability(&self, cx: &App) -> Capability {
3519 if self.read_only {
3520 Capability::ReadOnly
3521 } else {
3522 self.buffer.read(cx).capability()
3523 }
3524 }
3525
3526 pub fn read_only(&self, cx: &App) -> bool {
3527 self.read_only || self.buffer.read(cx).read_only()
3528 }
3529
3530 pub fn set_read_only(&mut self, read_only: bool) {
3531 self.read_only = read_only;
3532 }
3533
3534 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3535 self.use_autoclose = autoclose;
3536 }
3537
3538 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3539 self.use_auto_surround = auto_surround;
3540 }
3541
3542 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3543 self.auto_replace_emoji_shortcode = auto_replace;
3544 }
3545
3546 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3547 self.buffer_serialization = should_serialize.then(|| {
3548 BufferSerialization::new(
3549 ProjectSettings::get_global(cx)
3550 .session
3551 .restore_unsaved_buffers,
3552 )
3553 })
3554 }
3555
3556 fn should_serialize_buffer(&self) -> bool {
3557 self.buffer_serialization.is_some()
3558 }
3559
3560 pub fn toggle_edit_predictions(
3561 &mut self,
3562 _: &ToggleEditPrediction,
3563 window: &mut Window,
3564 cx: &mut Context<Self>,
3565 ) {
3566 if self.show_edit_predictions_override.is_some() {
3567 self.set_show_edit_predictions(None, window, cx);
3568 } else {
3569 let show_edit_predictions = !self.edit_predictions_enabled();
3570 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3571 }
3572 }
3573
3574 pub fn set_show_completions_on_input(&mut self, show_completions_on_input: Option<bool>) {
3575 self.show_completions_on_input_override = show_completions_on_input;
3576 }
3577
3578 pub fn set_show_edit_predictions(
3579 &mut self,
3580 show_edit_predictions: Option<bool>,
3581 window: &mut Window,
3582 cx: &mut Context<Self>,
3583 ) {
3584 self.show_edit_predictions_override = show_edit_predictions;
3585 self.update_edit_prediction_settings(cx);
3586
3587 if let Some(false) = show_edit_predictions {
3588 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
3589 } else {
3590 self.refresh_edit_prediction(false, true, window, cx);
3591 }
3592 }
3593
3594 fn edit_predictions_disabled_in_scope(
3595 &self,
3596 buffer: &Entity<Buffer>,
3597 buffer_position: language::Anchor,
3598 cx: &App,
3599 ) -> bool {
3600 let snapshot = buffer.read(cx).snapshot();
3601 let settings = snapshot.settings_at(buffer_position, cx);
3602
3603 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3604 return false;
3605 };
3606
3607 scope.override_name().is_some_and(|scope_name| {
3608 settings
3609 .edit_predictions_disabled_in
3610 .iter()
3611 .any(|s| s == scope_name)
3612 })
3613 }
3614
3615 pub fn set_use_modal_editing(&mut self, to: bool) {
3616 self.use_modal_editing = to;
3617 }
3618
3619 pub fn use_modal_editing(&self) -> bool {
3620 self.use_modal_editing
3621 }
3622
3623 fn selections_did_change(
3624 &mut self,
3625 local: bool,
3626 old_cursor_position: &Anchor,
3627 effects: SelectionEffects,
3628 window: &mut Window,
3629 cx: &mut Context<Self>,
3630 ) {
3631 window.invalidate_character_coordinates();
3632
3633 // Copy selections to primary selection buffer
3634 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3635 if local {
3636 let selections = self
3637 .selections
3638 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3639 let buffer_handle = self.buffer.read(cx).read(cx);
3640
3641 let mut text = String::new();
3642 for (index, selection) in selections.iter().enumerate() {
3643 let text_for_selection = buffer_handle
3644 .text_for_range(selection.start..selection.end)
3645 .collect::<String>();
3646
3647 text.push_str(&text_for_selection);
3648 if index != selections.len() - 1 {
3649 text.push('\n');
3650 }
3651 }
3652
3653 if !text.is_empty() {
3654 cx.write_to_primary(ClipboardItem::new_string(text));
3655 }
3656 }
3657
3658 let selection_anchors = self.selections.disjoint_anchors_arc();
3659
3660 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3661 self.buffer.update(cx, |buffer, cx| {
3662 buffer.set_active_selections(
3663 &selection_anchors,
3664 self.selections.line_mode(),
3665 self.cursor_shape,
3666 cx,
3667 )
3668 });
3669 }
3670 let display_map = self
3671 .display_map
3672 .update(cx, |display_map, cx| display_map.snapshot(cx));
3673 let buffer = display_map.buffer_snapshot();
3674 if self.selections.count() == 1 {
3675 self.add_selections_state = None;
3676 }
3677 self.select_next_state = None;
3678 self.select_prev_state = None;
3679 self.select_syntax_node_history.try_clear();
3680 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3681 self.snippet_stack.invalidate(&selection_anchors, buffer);
3682 self.take_rename(false, window, cx);
3683
3684 let newest_selection = self.selections.newest_anchor();
3685 let new_cursor_position = newest_selection.head();
3686 let selection_start = newest_selection.start;
3687
3688 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3689 self.push_to_nav_history(
3690 *old_cursor_position,
3691 Some(new_cursor_position.to_point(buffer)),
3692 false,
3693 effects.nav_history == Some(true),
3694 cx,
3695 );
3696 }
3697
3698 if local {
3699 if let Some(buffer_id) = new_cursor_position.text_anchor.buffer_id {
3700 self.register_buffer(buffer_id, cx);
3701 }
3702
3703 let mut context_menu = self.context_menu.borrow_mut();
3704 let completion_menu = match context_menu.as_ref() {
3705 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3706 Some(CodeContextMenu::CodeActions(_)) => {
3707 *context_menu = None;
3708 None
3709 }
3710 None => None,
3711 };
3712 let completion_position = completion_menu.map(|menu| menu.initial_position);
3713 drop(context_menu);
3714
3715 if effects.completions
3716 && let Some(completion_position) = completion_position
3717 {
3718 let start_offset = selection_start.to_offset(buffer);
3719 let position_matches = start_offset == completion_position.to_offset(buffer);
3720 let continue_showing = if let Some((snap, ..)) =
3721 buffer.point_to_buffer_offset(completion_position)
3722 && !snap.capability.editable()
3723 {
3724 false
3725 } else if position_matches {
3726 if self.snippet_stack.is_empty() {
3727 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3728 == Some(CharKind::Word)
3729 } else {
3730 // Snippet choices can be shown even when the cursor is in whitespace.
3731 // Dismissing the menu with actions like backspace is handled by
3732 // invalidation regions.
3733 true
3734 }
3735 } else {
3736 false
3737 };
3738
3739 if continue_showing {
3740 self.open_or_update_completions_menu(None, None, false, window, cx);
3741 } else {
3742 self.hide_context_menu(window, cx);
3743 }
3744 }
3745
3746 hide_hover(self, cx);
3747
3748 if old_cursor_position.to_display_point(&display_map).row()
3749 != new_cursor_position.to_display_point(&display_map).row()
3750 {
3751 self.available_code_actions.take();
3752 }
3753 self.refresh_code_actions(window, cx);
3754 self.refresh_document_highlights(cx);
3755 refresh_linked_ranges(self, window, cx);
3756
3757 self.refresh_selected_text_highlights(&display_map, false, window, cx);
3758 self.refresh_matching_bracket_highlights(&display_map, cx);
3759 self.refresh_outline_symbols_at_cursor(cx);
3760 self.update_visible_edit_prediction(window, cx);
3761 self.inline_blame_popover.take();
3762 if self.git_blame_inline_enabled {
3763 self.start_inline_blame_timer(window, cx);
3764 }
3765 }
3766
3767 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3768
3769 if local && !self.suppress_selection_callback {
3770 if let Some(callback) = self.on_local_selections_changed.as_ref() {
3771 let cursor_position = self.selections.newest::<Point>(&display_map).head();
3772 callback(cursor_position, window, cx);
3773 }
3774 }
3775
3776 cx.emit(EditorEvent::SelectionsChanged { local });
3777
3778 let selections = &self.selections.disjoint_anchors_arc();
3779 if selections.len() == 1 {
3780 cx.emit(SearchEvent::ActiveMatchChanged)
3781 }
3782 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3783 let inmemory_selections = selections
3784 .iter()
3785 .map(|s| {
3786 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3787 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3788 })
3789 .collect();
3790 self.update_restoration_data(cx, |data| {
3791 data.selections = inmemory_selections;
3792 });
3793
3794 if WorkspaceSettings::get(None, cx).restore_on_startup
3795 != RestoreOnStartupBehavior::EmptyTab
3796 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3797 {
3798 let snapshot = self.buffer().read(cx).snapshot(cx);
3799 let selections = selections.clone();
3800 let background_executor = cx.background_executor().clone();
3801 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3802 let db = EditorDb::global(cx);
3803 self.serialize_selections = cx.background_spawn(async move {
3804 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3805 let db_selections = selections
3806 .iter()
3807 .map(|selection| {
3808 (
3809 selection.start.to_offset(&snapshot).0,
3810 selection.end.to_offset(&snapshot).0,
3811 )
3812 })
3813 .collect();
3814
3815 db.save_editor_selections(editor_id, workspace_id, db_selections)
3816 .await
3817 .with_context(|| {
3818 format!(
3819 "persisting editor selections for editor {editor_id}, \
3820 workspace {workspace_id:?}"
3821 )
3822 })
3823 .log_err();
3824 });
3825 }
3826 }
3827
3828 cx.notify();
3829 }
3830
3831 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3832 use text::ToOffset as _;
3833 use text::ToPoint as _;
3834
3835 if self.mode.is_minimap()
3836 || WorkspaceSettings::get(None, cx).restore_on_startup
3837 == RestoreOnStartupBehavior::EmptyTab
3838 {
3839 return;
3840 }
3841
3842 if !self.buffer().read(cx).is_singleton() {
3843 return;
3844 }
3845
3846 let display_snapshot = self
3847 .display_map
3848 .update(cx, |display_map, cx| display_map.snapshot(cx));
3849 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3850 return;
3851 };
3852 let inmemory_folds = display_snapshot
3853 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3854 .map(|fold| {
3855 fold.range.start.text_anchor.to_point(&snapshot)
3856 ..fold.range.end.text_anchor.to_point(&snapshot)
3857 })
3858 .collect();
3859 self.update_restoration_data(cx, |data| {
3860 data.folds = inmemory_folds;
3861 });
3862
3863 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3864 return;
3865 };
3866
3867 // Get file path for path-based fold storage (survives tab close)
3868 let Some(file_path) = self.buffer().read(cx).as_singleton().and_then(|buffer| {
3869 project::File::from_dyn(buffer.read(cx).file())
3870 .map(|file| Arc::<Path>::from(file.abs_path(cx)))
3871 }) else {
3872 return;
3873 };
3874
3875 let background_executor = cx.background_executor().clone();
3876 const FINGERPRINT_LEN: usize = 32;
3877 let db_folds = display_snapshot
3878 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3879 .map(|fold| {
3880 let start = fold.range.start.text_anchor.to_offset(&snapshot);
3881 let end = fold.range.end.text_anchor.to_offset(&snapshot);
3882
3883 // Extract fingerprints - content at fold boundaries for validation on restore
3884 // Both fingerprints must be INSIDE the fold to avoid capturing surrounding
3885 // content that might change independently.
3886 // start_fp: first min(32, fold_len) bytes of fold content
3887 // end_fp: last min(32, fold_len) bytes of fold content
3888 // Clip to character boundaries to handle multibyte UTF-8 characters.
3889 let fold_len = end - start;
3890 let start_fp_end = snapshot
3891 .clip_offset(start + std::cmp::min(FINGERPRINT_LEN, fold_len), Bias::Left);
3892 let start_fp: String = snapshot.text_for_range(start..start_fp_end).collect();
3893 let end_fp_start = snapshot
3894 .clip_offset(end.saturating_sub(FINGERPRINT_LEN).max(start), Bias::Right);
3895 let end_fp: String = snapshot.text_for_range(end_fp_start..end).collect();
3896
3897 (start, end, start_fp, end_fp)
3898 })
3899 .collect::<Vec<_>>();
3900 let db = EditorDb::global(cx);
3901 self.serialize_folds = cx.background_spawn(async move {
3902 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3903 if db_folds.is_empty() {
3904 // No folds - delete any persisted folds for this file
3905 db.delete_file_folds(workspace_id, file_path)
3906 .await
3907 .with_context(|| format!("deleting file folds for workspace {workspace_id:?}"))
3908 .log_err();
3909 } else {
3910 db.save_file_folds(workspace_id, file_path, db_folds)
3911 .await
3912 .with_context(|| {
3913 format!("persisting file folds for workspace {workspace_id:?}")
3914 })
3915 .log_err();
3916 }
3917 });
3918 }
3919
3920 pub fn sync_selections(
3921 &mut self,
3922 other: Entity<Editor>,
3923 cx: &mut Context<Self>,
3924 ) -> gpui::Subscription {
3925 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3926 if !other_selections.is_empty() {
3927 self.selections
3928 .change_with(&self.display_snapshot(cx), |selections| {
3929 selections.select_anchors(other_selections);
3930 });
3931 }
3932
3933 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3934 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3935 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3936 if other_selections.is_empty() {
3937 return;
3938 }
3939 let snapshot = this.display_snapshot(cx);
3940 this.selections.change_with(&snapshot, |selections| {
3941 selections.select_anchors(other_selections);
3942 });
3943 }
3944 });
3945
3946 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3947 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3948 let these_selections = this.selections.disjoint_anchors().to_vec();
3949 if these_selections.is_empty() {
3950 return;
3951 }
3952 other.update(cx, |other_editor, cx| {
3953 let snapshot = other_editor.display_snapshot(cx);
3954 other_editor
3955 .selections
3956 .change_with(&snapshot, |selections| {
3957 selections.select_anchors(these_selections);
3958 })
3959 });
3960 }
3961 });
3962
3963 Subscription::join(other_subscription, this_subscription)
3964 }
3965
3966 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3967 if self.buffer().read(cx).is_singleton() {
3968 return;
3969 }
3970 let snapshot = self.buffer.read(cx).snapshot(cx);
3971 let buffer_ids: HashSet<BufferId> = self
3972 .selections
3973 .disjoint_anchor_ranges()
3974 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3975 .collect();
3976 for buffer_id in buffer_ids {
3977 self.unfold_buffer(buffer_id, cx);
3978 }
3979 }
3980
3981 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3982 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3983 /// effects of selection change occur at the end of the transaction.
3984 pub fn change_selections<R>(
3985 &mut self,
3986 effects: SelectionEffects,
3987 window: &mut Window,
3988 cx: &mut Context<Self>,
3989 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3990 ) -> R {
3991 let snapshot = self.display_snapshot(cx);
3992 if let Some(state) = &mut self.deferred_selection_effects_state {
3993 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3994 state.effects.completions = effects.completions;
3995 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3996 let (changed, result) = self.selections.change_with(&snapshot, change);
3997 state.changed |= changed;
3998 return result;
3999 }
4000 let mut state = DeferredSelectionEffectsState {
4001 changed: false,
4002 effects,
4003 old_cursor_position: self.selections.newest_anchor().head(),
4004 history_entry: SelectionHistoryEntry {
4005 selections: self.selections.disjoint_anchors_arc(),
4006 select_next_state: self.select_next_state.clone(),
4007 select_prev_state: self.select_prev_state.clone(),
4008 add_selections_state: self.add_selections_state.clone(),
4009 },
4010 };
4011 let (changed, result) = self.selections.change_with(&snapshot, change);
4012 state.changed = state.changed || changed;
4013 if self.defer_selection_effects {
4014 self.deferred_selection_effects_state = Some(state);
4015 } else {
4016 self.apply_selection_effects(state, window, cx);
4017 }
4018 result
4019 }
4020
4021 /// Defers the effects of selection change, so that the effects of multiple calls to
4022 /// `change_selections` are applied at the end. This way these intermediate states aren't added
4023 /// to selection history and the state of popovers based on selection position aren't
4024 /// erroneously updated.
4025 pub fn with_selection_effects_deferred<R>(
4026 &mut self,
4027 window: &mut Window,
4028 cx: &mut Context<Self>,
4029 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
4030 ) -> R {
4031 let already_deferred = self.defer_selection_effects;
4032 self.defer_selection_effects = true;
4033 let result = update(self, window, cx);
4034 if !already_deferred {
4035 self.defer_selection_effects = false;
4036 if let Some(state) = self.deferred_selection_effects_state.take() {
4037 self.apply_selection_effects(state, window, cx);
4038 }
4039 }
4040 result
4041 }
4042
4043 fn apply_selection_effects(
4044 &mut self,
4045 state: DeferredSelectionEffectsState,
4046 window: &mut Window,
4047 cx: &mut Context<Self>,
4048 ) {
4049 if state.changed {
4050 self.selection_history.push(state.history_entry);
4051
4052 if let Some(autoscroll) = state.effects.scroll {
4053 self.request_autoscroll(autoscroll, cx);
4054 }
4055
4056 let old_cursor_position = &state.old_cursor_position;
4057
4058 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
4059
4060 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
4061 self.show_signature_help_auto(window, cx);
4062 }
4063 }
4064 }
4065
4066 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
4067 where
4068 I: IntoIterator<Item = (Range<S>, T)>,
4069 S: ToOffset,
4070 T: Into<Arc<str>>,
4071 {
4072 if self.read_only(cx) {
4073 return;
4074 }
4075
4076 self.buffer
4077 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
4078 }
4079
4080 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
4081 where
4082 I: IntoIterator<Item = (Range<S>, T)>,
4083 S: ToOffset,
4084 T: Into<Arc<str>>,
4085 {
4086 if self.read_only(cx) {
4087 return;
4088 }
4089
4090 self.buffer.update(cx, |buffer, cx| {
4091 buffer.edit(edits, self.autoindent_mode.clone(), cx)
4092 });
4093 }
4094
4095 pub fn edit_with_block_indent<I, S, T>(
4096 &mut self,
4097 edits: I,
4098 original_indent_columns: Vec<Option<u32>>,
4099 cx: &mut Context<Self>,
4100 ) where
4101 I: IntoIterator<Item = (Range<S>, T)>,
4102 S: ToOffset,
4103 T: Into<Arc<str>>,
4104 {
4105 if self.read_only(cx) {
4106 return;
4107 }
4108
4109 self.buffer.update(cx, |buffer, cx| {
4110 buffer.edit(
4111 edits,
4112 Some(AutoindentMode::Block {
4113 original_indent_columns,
4114 }),
4115 cx,
4116 )
4117 });
4118 }
4119
4120 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
4121 self.hide_context_menu(window, cx);
4122
4123 match phase {
4124 SelectPhase::Begin {
4125 position,
4126 add,
4127 click_count,
4128 } => self.begin_selection(position, add, click_count, window, cx),
4129 SelectPhase::BeginColumnar {
4130 position,
4131 goal_column,
4132 reset,
4133 mode,
4134 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
4135 SelectPhase::Extend {
4136 position,
4137 click_count,
4138 } => self.extend_selection(position, click_count, window, cx),
4139 SelectPhase::Update {
4140 position,
4141 goal_column,
4142 scroll_delta,
4143 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
4144 SelectPhase::End => self.end_selection(window, cx),
4145 }
4146 }
4147
4148 fn extend_selection(
4149 &mut self,
4150 position: DisplayPoint,
4151 click_count: usize,
4152 window: &mut Window,
4153 cx: &mut Context<Self>,
4154 ) {
4155 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4156 let tail = self
4157 .selections
4158 .newest::<MultiBufferOffset>(&display_map)
4159 .tail();
4160 let click_count = click_count.max(match self.selections.select_mode() {
4161 SelectMode::Character => 1,
4162 SelectMode::Word(_) => 2,
4163 SelectMode::Line(_) => 3,
4164 SelectMode::All => 4,
4165 });
4166 self.begin_selection(position, false, click_count, window, cx);
4167
4168 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
4169
4170 let current_selection = match self.selections.select_mode() {
4171 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
4172 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
4173 };
4174
4175 let mut pending_selection = self
4176 .selections
4177 .pending_anchor()
4178 .cloned()
4179 .expect("extend_selection not called with pending selection");
4180
4181 if pending_selection
4182 .start
4183 .cmp(¤t_selection.start, display_map.buffer_snapshot())
4184 == Ordering::Greater
4185 {
4186 pending_selection.start = current_selection.start;
4187 }
4188 if pending_selection
4189 .end
4190 .cmp(¤t_selection.end, display_map.buffer_snapshot())
4191 == Ordering::Less
4192 {
4193 pending_selection.end = current_selection.end;
4194 pending_selection.reversed = true;
4195 }
4196
4197 let mut pending_mode = self.selections.pending_mode().unwrap();
4198 match &mut pending_mode {
4199 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
4200 _ => {}
4201 }
4202
4203 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
4204 SelectionEffects::scroll(Autoscroll::fit())
4205 } else {
4206 SelectionEffects::no_scroll()
4207 };
4208
4209 self.change_selections(effects, window, cx, |s| {
4210 s.set_pending(pending_selection.clone(), pending_mode);
4211 s.set_is_extending(true);
4212 });
4213 }
4214
4215 fn begin_selection(
4216 &mut self,
4217 position: DisplayPoint,
4218 add: bool,
4219 click_count: usize,
4220 window: &mut Window,
4221 cx: &mut Context<Self>,
4222 ) {
4223 if !self.focus_handle.is_focused(window) {
4224 self.last_focused_descendant = None;
4225 window.focus(&self.focus_handle, cx);
4226 }
4227
4228 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4229 let buffer = display_map.buffer_snapshot();
4230 let position = display_map.clip_point(position, Bias::Left);
4231
4232 let start;
4233 let end;
4234 let mode;
4235 let mut auto_scroll;
4236 match click_count {
4237 1 => {
4238 start = buffer.anchor_before(position.to_point(&display_map));
4239 end = start;
4240 mode = SelectMode::Character;
4241 auto_scroll = true;
4242 }
4243 2 => {
4244 let position = display_map
4245 .clip_point(position, Bias::Left)
4246 .to_offset(&display_map, Bias::Left);
4247 let (range, _) = buffer.surrounding_word(position, None);
4248 start = buffer.anchor_before(range.start);
4249 end = buffer.anchor_before(range.end);
4250 mode = SelectMode::Word(start..end);
4251 auto_scroll = true;
4252 }
4253 3 => {
4254 let position = display_map
4255 .clip_point(position, Bias::Left)
4256 .to_point(&display_map);
4257 let line_start = display_map.prev_line_boundary(position).0;
4258 let next_line_start = buffer.clip_point(
4259 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4260 Bias::Left,
4261 );
4262 start = buffer.anchor_before(line_start);
4263 end = buffer.anchor_before(next_line_start);
4264 mode = SelectMode::Line(start..end);
4265 auto_scroll = true;
4266 }
4267 _ => {
4268 start = buffer.anchor_before(MultiBufferOffset(0));
4269 end = buffer.anchor_before(buffer.len());
4270 mode = SelectMode::All;
4271 auto_scroll = false;
4272 }
4273 }
4274 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
4275
4276 let point_to_delete: Option<usize> = {
4277 let selected_points: Vec<Selection<Point>> =
4278 self.selections.disjoint_in_range(start..end, &display_map);
4279
4280 if !add || click_count > 1 {
4281 None
4282 } else if !selected_points.is_empty() {
4283 Some(selected_points[0].id)
4284 } else {
4285 let clicked_point_already_selected =
4286 self.selections.disjoint_anchors().iter().find(|selection| {
4287 selection.start.to_point(buffer) == start.to_point(buffer)
4288 || selection.end.to_point(buffer) == end.to_point(buffer)
4289 });
4290
4291 clicked_point_already_selected.map(|selection| selection.id)
4292 }
4293 };
4294
4295 let selections_count = self.selections.count();
4296 let effects = if auto_scroll {
4297 SelectionEffects::default()
4298 } else {
4299 SelectionEffects::no_scroll()
4300 };
4301
4302 self.change_selections(effects, window, cx, |s| {
4303 if let Some(point_to_delete) = point_to_delete {
4304 s.delete(point_to_delete);
4305
4306 if selections_count == 1 {
4307 s.set_pending_anchor_range(start..end, mode);
4308 }
4309 } else {
4310 if !add {
4311 s.clear_disjoint();
4312 }
4313
4314 s.set_pending_anchor_range(start..end, mode);
4315 }
4316 });
4317 }
4318
4319 fn begin_columnar_selection(
4320 &mut self,
4321 position: DisplayPoint,
4322 goal_column: u32,
4323 reset: bool,
4324 mode: ColumnarMode,
4325 window: &mut Window,
4326 cx: &mut Context<Self>,
4327 ) {
4328 if !self.focus_handle.is_focused(window) {
4329 self.last_focused_descendant = None;
4330 window.focus(&self.focus_handle, cx);
4331 }
4332
4333 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4334
4335 if reset {
4336 let pointer_position = display_map
4337 .buffer_snapshot()
4338 .anchor_before(position.to_point(&display_map));
4339
4340 self.change_selections(
4341 SelectionEffects::scroll(Autoscroll::newest()),
4342 window,
4343 cx,
4344 |s| {
4345 s.clear_disjoint();
4346 s.set_pending_anchor_range(
4347 pointer_position..pointer_position,
4348 SelectMode::Character,
4349 );
4350 },
4351 );
4352 };
4353
4354 let tail = self.selections.newest::<Point>(&display_map).tail();
4355 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
4356 self.columnar_selection_state = match mode {
4357 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
4358 selection_tail: selection_anchor,
4359 display_point: if reset {
4360 if position.column() != goal_column {
4361 Some(DisplayPoint::new(position.row(), goal_column))
4362 } else {
4363 None
4364 }
4365 } else {
4366 None
4367 },
4368 }),
4369 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
4370 selection_tail: selection_anchor,
4371 }),
4372 };
4373
4374 if !reset {
4375 self.select_columns(position, goal_column, &display_map, window, cx);
4376 }
4377 }
4378
4379 fn update_selection(
4380 &mut self,
4381 position: DisplayPoint,
4382 goal_column: u32,
4383 scroll_delta: gpui::Point<f32>,
4384 window: &mut Window,
4385 cx: &mut Context<Self>,
4386 ) {
4387 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4388
4389 if self.columnar_selection_state.is_some() {
4390 self.select_columns(position, goal_column, &display_map, window, cx);
4391 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
4392 let buffer = display_map.buffer_snapshot();
4393 let head;
4394 let tail;
4395 let mode = self.selections.pending_mode().unwrap();
4396 match &mode {
4397 SelectMode::Character => {
4398 head = position.to_point(&display_map);
4399 tail = pending.tail().to_point(buffer);
4400 }
4401 SelectMode::Word(original_range) => {
4402 let offset = display_map
4403 .clip_point(position, Bias::Left)
4404 .to_offset(&display_map, Bias::Left);
4405 let original_range = original_range.to_offset(buffer);
4406
4407 let head_offset = if buffer.is_inside_word(offset, None)
4408 || original_range.contains(&offset)
4409 {
4410 let (word_range, _) = buffer.surrounding_word(offset, None);
4411 if word_range.start < original_range.start {
4412 word_range.start
4413 } else {
4414 word_range.end
4415 }
4416 } else {
4417 offset
4418 };
4419
4420 head = head_offset.to_point(buffer);
4421 if head_offset <= original_range.start {
4422 tail = original_range.end.to_point(buffer);
4423 } else {
4424 tail = original_range.start.to_point(buffer);
4425 }
4426 }
4427 SelectMode::Line(original_range) => {
4428 let original_range = original_range.to_point(display_map.buffer_snapshot());
4429
4430 let position = display_map
4431 .clip_point(position, Bias::Left)
4432 .to_point(&display_map);
4433 let line_start = display_map.prev_line_boundary(position).0;
4434 let next_line_start = buffer.clip_point(
4435 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4436 Bias::Left,
4437 );
4438
4439 if line_start < original_range.start {
4440 head = line_start
4441 } else {
4442 head = next_line_start
4443 }
4444
4445 if head <= original_range.start {
4446 tail = original_range.end;
4447 } else {
4448 tail = original_range.start;
4449 }
4450 }
4451 SelectMode::All => {
4452 return;
4453 }
4454 };
4455
4456 if head < tail {
4457 pending.start = buffer.anchor_before(head);
4458 pending.end = buffer.anchor_before(tail);
4459 pending.reversed = true;
4460 } else {
4461 pending.start = buffer.anchor_before(tail);
4462 pending.end = buffer.anchor_before(head);
4463 pending.reversed = false;
4464 }
4465
4466 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4467 s.set_pending(pending.clone(), mode);
4468 });
4469 } else {
4470 log::error!("update_selection dispatched with no pending selection");
4471 return;
4472 }
4473
4474 self.apply_scroll_delta(scroll_delta, window, cx);
4475 cx.notify();
4476 }
4477
4478 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4479 self.columnar_selection_state.take();
4480 if let Some(pending_mode) = self.selections.pending_mode() {
4481 let selections = self
4482 .selections
4483 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4484 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4485 s.select(selections);
4486 s.clear_pending();
4487 if s.is_extending() {
4488 s.set_is_extending(false);
4489 } else {
4490 s.set_select_mode(pending_mode);
4491 }
4492 });
4493 }
4494 }
4495
4496 fn select_columns(
4497 &mut self,
4498 head: DisplayPoint,
4499 goal_column: u32,
4500 display_map: &DisplaySnapshot,
4501 window: &mut Window,
4502 cx: &mut Context<Self>,
4503 ) {
4504 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4505 return;
4506 };
4507
4508 let tail = match columnar_state {
4509 ColumnarSelectionState::FromMouse {
4510 selection_tail,
4511 display_point,
4512 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4513 ColumnarSelectionState::FromSelection { selection_tail } => {
4514 selection_tail.to_display_point(display_map)
4515 }
4516 };
4517
4518 let start_row = cmp::min(tail.row(), head.row());
4519 let end_row = cmp::max(tail.row(), head.row());
4520 let start_column = cmp::min(tail.column(), goal_column);
4521 let end_column = cmp::max(tail.column(), goal_column);
4522 let reversed = start_column < tail.column();
4523
4524 let selection_ranges = (start_row.0..=end_row.0)
4525 .map(DisplayRow)
4526 .filter_map(|row| {
4527 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4528 || start_column <= display_map.line_len(row))
4529 && !display_map.is_block_line(row)
4530 {
4531 let start = display_map
4532 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4533 .to_point(display_map);
4534 let end = display_map
4535 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4536 .to_point(display_map);
4537 if reversed {
4538 Some(end..start)
4539 } else {
4540 Some(start..end)
4541 }
4542 } else {
4543 None
4544 }
4545 })
4546 .collect::<Vec<_>>();
4547 if selection_ranges.is_empty() {
4548 return;
4549 }
4550
4551 let ranges = match columnar_state {
4552 ColumnarSelectionState::FromMouse { .. } => {
4553 let mut non_empty_ranges = selection_ranges
4554 .iter()
4555 .filter(|selection_range| selection_range.start != selection_range.end)
4556 .peekable();
4557 if non_empty_ranges.peek().is_some() {
4558 non_empty_ranges.cloned().collect()
4559 } else {
4560 selection_ranges
4561 }
4562 }
4563 _ => selection_ranges,
4564 };
4565
4566 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4567 s.select_ranges(ranges);
4568 });
4569 cx.notify();
4570 }
4571
4572 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4573 self.selections
4574 .all_adjusted(snapshot)
4575 .iter()
4576 .any(|selection| !selection.is_empty())
4577 }
4578
4579 pub fn has_pending_nonempty_selection(&self) -> bool {
4580 let pending_nonempty_selection = match self.selections.pending_anchor() {
4581 Some(Selection { start, end, .. }) => start != end,
4582 None => false,
4583 };
4584
4585 pending_nonempty_selection
4586 || (self.columnar_selection_state.is_some()
4587 && self.selections.disjoint_anchors().len() > 1)
4588 }
4589
4590 pub fn has_pending_selection(&self) -> bool {
4591 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4592 }
4593
4594 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4595 self.selection_mark_mode = false;
4596 self.selection_drag_state = SelectionDragState::None;
4597
4598 if self.dismiss_menus_and_popups(true, window, cx) {
4599 cx.notify();
4600 return;
4601 }
4602 if self.clear_expanded_diff_hunks(cx) {
4603 cx.notify();
4604 return;
4605 }
4606 if self.show_git_blame_gutter {
4607 self.show_git_blame_gutter = false;
4608 cx.notify();
4609 return;
4610 }
4611
4612 if self.mode.is_full()
4613 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4614 {
4615 cx.notify();
4616 return;
4617 }
4618
4619 cx.propagate();
4620 }
4621
4622 pub fn dismiss_menus_and_popups(
4623 &mut self,
4624 is_user_requested: bool,
4625 window: &mut Window,
4626 cx: &mut Context<Self>,
4627 ) -> bool {
4628 let mut dismissed = false;
4629
4630 dismissed |= self.take_rename(false, window, cx).is_some();
4631 dismissed |= self.hide_blame_popover(true, cx);
4632 dismissed |= hide_hover(self, cx);
4633 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4634 dismissed |= self.hide_context_menu(window, cx).is_some();
4635 dismissed |= self.mouse_context_menu.take().is_some();
4636 dismissed |= is_user_requested
4637 && self.discard_edit_prediction(EditPredictionDiscardReason::Rejected, cx);
4638 dismissed |= self.snippet_stack.pop().is_some();
4639 if self.diff_review_drag_state.is_some() {
4640 self.cancel_diff_review_drag(cx);
4641 dismissed = true;
4642 }
4643 if !self.diff_review_overlays.is_empty() {
4644 self.dismiss_all_diff_review_overlays(cx);
4645 dismissed = true;
4646 }
4647
4648 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4649 self.dismiss_diagnostics(cx);
4650 dismissed = true;
4651 }
4652
4653 dismissed
4654 }
4655
4656 fn linked_editing_ranges_for(
4657 &self,
4658 selection: Range<text::Anchor>,
4659 cx: &App,
4660 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4661 if self.linked_edit_ranges.is_empty() {
4662 return None;
4663 }
4664 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4665 selection.end.buffer_id.and_then(|end_buffer_id| {
4666 if selection.start.buffer_id != Some(end_buffer_id) {
4667 return None;
4668 }
4669 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4670 let snapshot = buffer.read(cx).snapshot();
4671 self.linked_edit_ranges
4672 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4673 .map(|ranges| (ranges, snapshot, buffer))
4674 })?;
4675 use text::ToOffset as TO;
4676 // find offset from the start of current range to current cursor position
4677 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4678
4679 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4680 let start_difference = start_offset - start_byte_offset;
4681 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4682 let end_difference = end_offset - start_byte_offset;
4683
4684 // Current range has associated linked ranges.
4685 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4686 for range in linked_ranges.iter() {
4687 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4688 let end_offset = start_offset + end_difference;
4689 let start_offset = start_offset + start_difference;
4690 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4691 continue;
4692 }
4693 if self.selections.disjoint_anchor_ranges().any(|s| {
4694 if s.start.text_anchor.buffer_id != selection.start.buffer_id
4695 || s.end.text_anchor.buffer_id != selection.end.buffer_id
4696 {
4697 return false;
4698 }
4699 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4700 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4701 }) {
4702 continue;
4703 }
4704 let start = buffer_snapshot.anchor_after(start_offset);
4705 let end = buffer_snapshot.anchor_after(end_offset);
4706 linked_edits
4707 .entry(buffer.clone())
4708 .or_default()
4709 .push(start..end);
4710 }
4711 Some(linked_edits)
4712 }
4713
4714 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4715 let text: Arc<str> = text.into();
4716
4717 if self.read_only(cx) {
4718 return;
4719 }
4720
4721 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4722
4723 self.unfold_buffers_with_selections(cx);
4724
4725 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4726 let mut bracket_inserted = false;
4727 let mut edits = Vec::new();
4728 let mut linked_edits = LinkedEdits::new();
4729 let mut new_selections = Vec::with_capacity(selections.len());
4730 let mut new_autoclose_regions = Vec::new();
4731 let snapshot = self.buffer.read(cx).read(cx);
4732 let mut clear_linked_edit_ranges = false;
4733 let mut all_selections_read_only = true;
4734 let mut has_adjacent_edits = false;
4735 let mut in_adjacent_group = false;
4736
4737 let mut regions = self
4738 .selections_with_autoclose_regions(selections, &snapshot)
4739 .peekable();
4740
4741 while let Some((selection, autoclose_region)) = regions.next() {
4742 if snapshot
4743 .point_to_buffer_point(selection.head())
4744 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4745 {
4746 continue;
4747 }
4748 if snapshot
4749 .point_to_buffer_point(selection.tail())
4750 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4751 {
4752 // note, ideally we'd clip the tail to the closest writeable region towards the head
4753 continue;
4754 }
4755 all_selections_read_only = false;
4756
4757 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4758 // Determine if the inserted text matches the opening or closing
4759 // bracket of any of this language's bracket pairs.
4760 let mut bracket_pair = None;
4761 let mut is_bracket_pair_start = false;
4762 let mut is_bracket_pair_end = false;
4763 if !text.is_empty() {
4764 let mut bracket_pair_matching_end = None;
4765 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4766 // and they are removing the character that triggered IME popup.
4767 for (pair, enabled) in scope.brackets() {
4768 if !pair.close && !pair.surround {
4769 continue;
4770 }
4771
4772 if enabled && pair.start.ends_with(text.as_ref()) {
4773 let prefix_len = pair.start.len() - text.len();
4774 let preceding_text_matches_prefix = prefix_len == 0
4775 || (selection.start.column >= (prefix_len as u32)
4776 && snapshot.contains_str_at(
4777 Point::new(
4778 selection.start.row,
4779 selection.start.column - (prefix_len as u32),
4780 ),
4781 &pair.start[..prefix_len],
4782 ));
4783 if preceding_text_matches_prefix {
4784 bracket_pair = Some(pair.clone());
4785 is_bracket_pair_start = true;
4786 break;
4787 }
4788 }
4789 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4790 {
4791 // take first bracket pair matching end, but don't break in case a later bracket
4792 // pair matches start
4793 bracket_pair_matching_end = Some(pair.clone());
4794 }
4795 }
4796 if let Some(end) = bracket_pair_matching_end
4797 && bracket_pair.is_none()
4798 {
4799 bracket_pair = Some(end);
4800 is_bracket_pair_end = true;
4801 }
4802 }
4803
4804 if let Some(bracket_pair) = bracket_pair {
4805 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4806 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4807 let auto_surround =
4808 self.use_auto_surround && snapshot_settings.use_auto_surround;
4809 if selection.is_empty() {
4810 if is_bracket_pair_start {
4811 // If the inserted text is a suffix of an opening bracket and the
4812 // selection is preceded by the rest of the opening bracket, then
4813 // insert the closing bracket.
4814 let following_text_allows_autoclose = snapshot
4815 .chars_at(selection.start)
4816 .next()
4817 .is_none_or(|c| scope.should_autoclose_before(c));
4818
4819 let preceding_text_allows_autoclose = selection.start.column == 0
4820 || snapshot
4821 .reversed_chars_at(selection.start)
4822 .next()
4823 .is_none_or(|c| {
4824 bracket_pair.start != bracket_pair.end
4825 || !snapshot
4826 .char_classifier_at(selection.start)
4827 .is_word(c)
4828 });
4829
4830 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4831 && bracket_pair.start.len() == 1
4832 {
4833 let target = bracket_pair.start.chars().next().unwrap();
4834 let mut byte_offset = 0u32;
4835 let current_line_count = snapshot
4836 .reversed_chars_at(selection.start)
4837 .take_while(|&c| c != '\n')
4838 .filter(|c| {
4839 byte_offset += c.len_utf8() as u32;
4840 if *c != target {
4841 return false;
4842 }
4843
4844 let point = Point::new(
4845 selection.start.row,
4846 selection.start.column.saturating_sub(byte_offset),
4847 );
4848
4849 let is_enabled = snapshot
4850 .language_scope_at(point)
4851 .and_then(|scope| {
4852 scope
4853 .brackets()
4854 .find(|(pair, _)| {
4855 pair.start == bracket_pair.start
4856 })
4857 .map(|(_, enabled)| enabled)
4858 })
4859 .unwrap_or(true);
4860
4861 let is_delimiter = snapshot
4862 .language_scope_at(Point::new(
4863 point.row,
4864 point.column + 1,
4865 ))
4866 .and_then(|scope| {
4867 scope
4868 .brackets()
4869 .find(|(pair, _)| {
4870 pair.start == bracket_pair.start
4871 })
4872 .map(|(_, enabled)| !enabled)
4873 })
4874 .unwrap_or(false);
4875
4876 is_enabled && !is_delimiter
4877 })
4878 .count();
4879 current_line_count % 2 == 1
4880 } else {
4881 false
4882 };
4883
4884 if autoclose
4885 && bracket_pair.close
4886 && following_text_allows_autoclose
4887 && preceding_text_allows_autoclose
4888 && !is_closing_quote
4889 {
4890 let anchor = snapshot.anchor_before(selection.end);
4891 new_selections.push((selection.map(|_| anchor), text.len()));
4892 new_autoclose_regions.push((
4893 anchor,
4894 text.len(),
4895 selection.id,
4896 bracket_pair.clone(),
4897 ));
4898 edits.push((
4899 selection.range(),
4900 format!("{}{}", text, bracket_pair.end).into(),
4901 ));
4902 bracket_inserted = true;
4903 continue;
4904 }
4905 }
4906
4907 if let Some(region) = autoclose_region {
4908 // If the selection is followed by an auto-inserted closing bracket,
4909 // then don't insert that closing bracket again; just move the selection
4910 // past the closing bracket.
4911 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4912 && text.as_ref() == region.pair.end.as_str()
4913 && snapshot.contains_str_at(region.range.end, text.as_ref());
4914 if should_skip {
4915 let anchor = snapshot.anchor_after(selection.end);
4916 new_selections
4917 .push((selection.map(|_| anchor), region.pair.end.len()));
4918 continue;
4919 }
4920 }
4921
4922 let always_treat_brackets_as_autoclosed = snapshot
4923 .language_settings_at(selection.start, cx)
4924 .always_treat_brackets_as_autoclosed;
4925 if always_treat_brackets_as_autoclosed
4926 && is_bracket_pair_end
4927 && snapshot.contains_str_at(selection.end, text.as_ref())
4928 {
4929 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4930 // and the inserted text is a closing bracket and the selection is followed
4931 // by the closing bracket then move the selection past the closing bracket.
4932 let anchor = snapshot.anchor_after(selection.end);
4933 new_selections.push((selection.map(|_| anchor), text.len()));
4934 continue;
4935 }
4936 }
4937 // If an opening bracket is 1 character long and is typed while
4938 // text is selected, then surround that text with the bracket pair.
4939 else if auto_surround
4940 && bracket_pair.surround
4941 && is_bracket_pair_start
4942 && bracket_pair.start.chars().count() == 1
4943 {
4944 edits.push((selection.start..selection.start, text.clone()));
4945 edits.push((
4946 selection.end..selection.end,
4947 bracket_pair.end.as_str().into(),
4948 ));
4949 bracket_inserted = true;
4950 new_selections.push((
4951 Selection {
4952 id: selection.id,
4953 start: snapshot.anchor_after(selection.start),
4954 end: snapshot.anchor_before(selection.end),
4955 reversed: selection.reversed,
4956 goal: selection.goal,
4957 },
4958 0,
4959 ));
4960 continue;
4961 }
4962 }
4963 }
4964
4965 if self.auto_replace_emoji_shortcode
4966 && selection.is_empty()
4967 && text.as_ref().ends_with(':')
4968 && let Some(possible_emoji_short_code) =
4969 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4970 && !possible_emoji_short_code.is_empty()
4971 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4972 {
4973 let emoji_shortcode_start = Point::new(
4974 selection.start.row,
4975 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4976 );
4977
4978 // Remove shortcode from buffer
4979 edits.push((
4980 emoji_shortcode_start..selection.start,
4981 "".to_string().into(),
4982 ));
4983 new_selections.push((
4984 Selection {
4985 id: selection.id,
4986 start: snapshot.anchor_after(emoji_shortcode_start),
4987 end: snapshot.anchor_before(selection.start),
4988 reversed: selection.reversed,
4989 goal: selection.goal,
4990 },
4991 0,
4992 ));
4993
4994 // Insert emoji
4995 let selection_start_anchor = snapshot.anchor_after(selection.start);
4996 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4997 edits.push((selection.start..selection.end, emoji.to_string().into()));
4998
4999 continue;
5000 }
5001
5002 let next_is_adjacent = regions
5003 .peek()
5004 .is_some_and(|(next, _)| selection.end == next.start);
5005
5006 // If not handling any auto-close operation, then just replace the selected
5007 // text with the given input and move the selection to the end of the
5008 // newly inserted text.
5009 let anchor = if in_adjacent_group || next_is_adjacent {
5010 // After edits the right bias would shift those anchor to the next visible fragment
5011 // but we want to resolve to the previous one
5012 snapshot.anchor_before(selection.end)
5013 } else {
5014 snapshot.anchor_after(selection.end)
5015 };
5016
5017 if !self.linked_edit_ranges.is_empty() {
5018 let start_anchor = snapshot.anchor_before(selection.start);
5019
5020 let is_word_char = text.chars().next().is_none_or(|char| {
5021 let classifier = snapshot
5022 .char_classifier_at(start_anchor.to_offset(&snapshot))
5023 .scope_context(Some(CharScopeContext::LinkedEdit));
5024 classifier.is_word(char)
5025 });
5026 let is_dot = text.as_ref() == ".";
5027 let should_apply_linked_edit = is_word_char || is_dot;
5028
5029 if should_apply_linked_edit {
5030 let anchor_range = start_anchor.text_anchor..anchor.text_anchor;
5031 linked_edits.push(&self, anchor_range, text.clone(), cx);
5032 } else {
5033 clear_linked_edit_ranges = true;
5034 }
5035 }
5036
5037 new_selections.push((selection.map(|_| anchor), 0));
5038 edits.push((selection.start..selection.end, text.clone()));
5039
5040 has_adjacent_edits |= next_is_adjacent;
5041 in_adjacent_group = next_is_adjacent;
5042 }
5043
5044 if all_selections_read_only {
5045 return;
5046 }
5047
5048 drop(regions);
5049 drop(snapshot);
5050
5051 self.transact(window, cx, |this, window, cx| {
5052 if clear_linked_edit_ranges {
5053 this.linked_edit_ranges.clear();
5054 }
5055 let initial_buffer_versions =
5056 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
5057
5058 this.buffer.update(cx, |buffer, cx| {
5059 if has_adjacent_edits {
5060 buffer.edit_non_coalesce(edits, this.autoindent_mode.clone(), cx);
5061 } else {
5062 buffer.edit(edits, this.autoindent_mode.clone(), cx);
5063 }
5064 });
5065 linked_edits.apply(cx);
5066 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
5067 let new_selection_deltas = new_selections.iter().map(|e| e.1);
5068 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
5069 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
5070 new_anchor_selections,
5071 &map,
5072 )
5073 .zip(new_selection_deltas)
5074 .map(|(selection, delta)| Selection {
5075 id: selection.id,
5076 start: selection.start + delta,
5077 end: selection.end + delta,
5078 reversed: selection.reversed,
5079 goal: SelectionGoal::None,
5080 })
5081 .collect::<Vec<_>>();
5082
5083 let mut i = 0;
5084 for (position, delta, selection_id, pair) in new_autoclose_regions {
5085 let position = position.to_offset(map.buffer_snapshot()) + delta;
5086 let start = map.buffer_snapshot().anchor_before(position);
5087 let end = map.buffer_snapshot().anchor_after(position);
5088 while let Some(existing_state) = this.autoclose_regions.get(i) {
5089 match existing_state
5090 .range
5091 .start
5092 .cmp(&start, map.buffer_snapshot())
5093 {
5094 Ordering::Less => i += 1,
5095 Ordering::Greater => break,
5096 Ordering::Equal => {
5097 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
5098 Ordering::Less => i += 1,
5099 Ordering::Equal => break,
5100 Ordering::Greater => break,
5101 }
5102 }
5103 }
5104 }
5105 this.autoclose_regions.insert(
5106 i,
5107 AutocloseRegion {
5108 selection_id,
5109 range: start..end,
5110 pair,
5111 },
5112 );
5113 }
5114
5115 let had_active_edit_prediction = this.has_active_edit_prediction();
5116 this.change_selections(
5117 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
5118 window,
5119 cx,
5120 |s| s.select(new_selections),
5121 );
5122
5123 if !bracket_inserted
5124 && let Some(on_type_format_task) =
5125 this.trigger_on_type_formatting(text.to_string(), window, cx)
5126 {
5127 on_type_format_task.detach_and_log_err(cx);
5128 }
5129
5130 let editor_settings = EditorSettings::get_global(cx);
5131 if bracket_inserted
5132 && (editor_settings.auto_signature_help
5133 || editor_settings.show_signature_help_after_edits)
5134 {
5135 this.show_signature_help(&ShowSignatureHelp, window, cx);
5136 }
5137
5138 let trigger_in_words =
5139 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
5140 if this.hard_wrap.is_some() {
5141 let latest: Range<Point> = this.selections.newest(&map).range();
5142 if latest.is_empty()
5143 && this
5144 .buffer()
5145 .read(cx)
5146 .snapshot(cx)
5147 .line_len(MultiBufferRow(latest.start.row))
5148 == latest.start.column
5149 {
5150 this.rewrap_impl(
5151 RewrapOptions {
5152 override_language_settings: true,
5153 preserve_existing_whitespace: true,
5154 line_length: None,
5155 },
5156 cx,
5157 )
5158 }
5159 }
5160 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
5161 refresh_linked_ranges(this, window, cx);
5162 this.refresh_edit_prediction(true, false, window, cx);
5163 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
5164 });
5165 }
5166
5167 fn find_possible_emoji_shortcode_at_position(
5168 snapshot: &MultiBufferSnapshot,
5169 position: Point,
5170 ) -> Option<String> {
5171 let mut chars = Vec::new();
5172 let mut found_colon = false;
5173 for char in snapshot.reversed_chars_at(position).take(100) {
5174 // Found a possible emoji shortcode in the middle of the buffer
5175 if found_colon {
5176 if char.is_whitespace() {
5177 chars.reverse();
5178 return Some(chars.iter().collect());
5179 }
5180 // If the previous character is not a whitespace, we are in the middle of a word
5181 // and we only want to complete the shortcode if the word is made up of other emojis
5182 let mut containing_word = String::new();
5183 for ch in snapshot
5184 .reversed_chars_at(position)
5185 .skip(chars.len() + 1)
5186 .take(100)
5187 {
5188 if ch.is_whitespace() {
5189 break;
5190 }
5191 containing_word.push(ch);
5192 }
5193 let containing_word = containing_word.chars().rev().collect::<String>();
5194 if util::word_consists_of_emojis(containing_word.as_str()) {
5195 chars.reverse();
5196 return Some(chars.iter().collect());
5197 }
5198 }
5199
5200 if char.is_whitespace() || !char.is_ascii() {
5201 return None;
5202 }
5203 if char == ':' {
5204 found_colon = true;
5205 } else {
5206 chars.push(char);
5207 }
5208 }
5209 // Found a possible emoji shortcode at the beginning of the buffer
5210 chars.reverse();
5211 Some(chars.iter().collect())
5212 }
5213
5214 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
5215 if self.read_only(cx) {
5216 return;
5217 }
5218
5219 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5220 self.transact(window, cx, |this, window, cx| {
5221 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
5222 let selections = this
5223 .selections
5224 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
5225 let multi_buffer = this.buffer.read(cx);
5226 let buffer = multi_buffer.snapshot(cx);
5227 selections
5228 .iter()
5229 .map(|selection| {
5230 let start_point = selection.start.to_point(&buffer);
5231 let mut existing_indent =
5232 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
5233 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
5234 let start = selection.start;
5235 let end = selection.end;
5236 let selection_is_empty = start == end;
5237 let language_scope = buffer.language_scope_at(start);
5238 let (delimiter, newline_config) = if let Some(language) = &language_scope {
5239 let needs_extra_newline = NewlineConfig::insert_extra_newline_brackets(
5240 &buffer,
5241 start..end,
5242 language,
5243 )
5244 || NewlineConfig::insert_extra_newline_tree_sitter(
5245 &buffer,
5246 start..end,
5247 );
5248
5249 let mut newline_config = NewlineConfig::Newline {
5250 additional_indent: IndentSize::spaces(0),
5251 extra_line_additional_indent: if needs_extra_newline {
5252 Some(IndentSize::spaces(0))
5253 } else {
5254 None
5255 },
5256 prevent_auto_indent: false,
5257 };
5258
5259 let comment_delimiter = maybe!({
5260 if !selection_is_empty {
5261 return None;
5262 }
5263
5264 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5265 return None;
5266 }
5267
5268 return comment_delimiter_for_newline(
5269 &start_point,
5270 &buffer,
5271 language,
5272 );
5273 });
5274
5275 let doc_delimiter = maybe!({
5276 if !selection_is_empty {
5277 return None;
5278 }
5279
5280 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5281 return None;
5282 }
5283
5284 return documentation_delimiter_for_newline(
5285 &start_point,
5286 &buffer,
5287 language,
5288 &mut newline_config,
5289 );
5290 });
5291
5292 let list_delimiter = maybe!({
5293 if !selection_is_empty {
5294 return None;
5295 }
5296
5297 if !multi_buffer.language_settings(cx).extend_list_on_newline {
5298 return None;
5299 }
5300
5301 return list_delimiter_for_newline(
5302 &start_point,
5303 &buffer,
5304 language,
5305 &mut newline_config,
5306 );
5307 });
5308
5309 (
5310 comment_delimiter.or(doc_delimiter).or(list_delimiter),
5311 newline_config,
5312 )
5313 } else {
5314 (
5315 None,
5316 NewlineConfig::Newline {
5317 additional_indent: IndentSize::spaces(0),
5318 extra_line_additional_indent: None,
5319 prevent_auto_indent: false,
5320 },
5321 )
5322 };
5323
5324 let (edit_start, new_text, prevent_auto_indent) = match &newline_config {
5325 NewlineConfig::ClearCurrentLine => {
5326 let row_start =
5327 buffer.point_to_offset(Point::new(start_point.row, 0));
5328 (row_start, String::new(), false)
5329 }
5330 NewlineConfig::UnindentCurrentLine { continuation } => {
5331 let row_start =
5332 buffer.point_to_offset(Point::new(start_point.row, 0));
5333 let tab_size = buffer.language_settings_at(start, cx).tab_size;
5334 let tab_size_indent = IndentSize::spaces(tab_size.get());
5335 let reduced_indent =
5336 existing_indent.with_delta(Ordering::Less, tab_size_indent);
5337 let mut new_text = String::new();
5338 new_text.extend(reduced_indent.chars());
5339 new_text.push_str(continuation);
5340 (row_start, new_text, true)
5341 }
5342 NewlineConfig::Newline {
5343 additional_indent,
5344 extra_line_additional_indent,
5345 prevent_auto_indent,
5346 } => {
5347 let auto_indent_mode =
5348 buffer.language_settings_at(start, cx).auto_indent;
5349 let preserve_indent =
5350 auto_indent_mode != language::AutoIndentMode::None;
5351 let apply_syntax_indent =
5352 auto_indent_mode == language::AutoIndentMode::SyntaxAware;
5353 let capacity_for_delimiter =
5354 delimiter.as_deref().map(str::len).unwrap_or_default();
5355 let existing_indent_len = if preserve_indent {
5356 existing_indent.len as usize
5357 } else {
5358 0
5359 };
5360 let extra_line_len = extra_line_additional_indent
5361 .map(|i| 1 + existing_indent_len + i.len as usize)
5362 .unwrap_or(0);
5363 let mut new_text = String::with_capacity(
5364 1 + capacity_for_delimiter
5365 + existing_indent_len
5366 + additional_indent.len as usize
5367 + extra_line_len,
5368 );
5369 new_text.push('\n');
5370 if preserve_indent {
5371 new_text.extend(existing_indent.chars());
5372 }
5373 new_text.extend(additional_indent.chars());
5374 if let Some(delimiter) = &delimiter {
5375 new_text.push_str(delimiter);
5376 }
5377 if let Some(extra_indent) = extra_line_additional_indent {
5378 new_text.push('\n');
5379 if preserve_indent {
5380 new_text.extend(existing_indent.chars());
5381 }
5382 new_text.extend(extra_indent.chars());
5383 }
5384 (
5385 start,
5386 new_text,
5387 *prevent_auto_indent || !apply_syntax_indent,
5388 )
5389 }
5390 };
5391
5392 let anchor = buffer.anchor_after(end);
5393 let new_selection = selection.map(|_| anchor);
5394 (
5395 ((edit_start..end, new_text), prevent_auto_indent),
5396 (newline_config.has_extra_line(), new_selection),
5397 )
5398 })
5399 .unzip()
5400 };
5401
5402 let mut auto_indent_edits = Vec::new();
5403 let mut edits = Vec::new();
5404 for (edit, prevent_auto_indent) in edits_with_flags {
5405 if prevent_auto_indent {
5406 edits.push(edit);
5407 } else {
5408 auto_indent_edits.push(edit);
5409 }
5410 }
5411 if !edits.is_empty() {
5412 this.edit(edits, cx);
5413 }
5414 if !auto_indent_edits.is_empty() {
5415 this.edit_with_autoindent(auto_indent_edits, cx);
5416 }
5417
5418 let buffer = this.buffer.read(cx).snapshot(cx);
5419 let new_selections = selection_info
5420 .into_iter()
5421 .map(|(extra_newline_inserted, new_selection)| {
5422 let mut cursor = new_selection.end.to_point(&buffer);
5423 if extra_newline_inserted {
5424 cursor.row -= 1;
5425 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
5426 }
5427 new_selection.map(|_| cursor)
5428 })
5429 .collect();
5430
5431 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
5432 this.refresh_edit_prediction(true, false, window, cx);
5433 if let Some(task) = this.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5434 task.detach_and_log_err(cx);
5435 }
5436 });
5437 }
5438
5439 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
5440 if self.read_only(cx) {
5441 return;
5442 }
5443
5444 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5445
5446 let buffer = self.buffer.read(cx);
5447 let snapshot = buffer.snapshot(cx);
5448
5449 let mut edits = Vec::new();
5450 let mut rows = Vec::new();
5451
5452 for (rows_inserted, selection) in self
5453 .selections
5454 .all_adjusted(&self.display_snapshot(cx))
5455 .into_iter()
5456 .enumerate()
5457 {
5458 let cursor = selection.head();
5459 let row = cursor.row;
5460
5461 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
5462
5463 let newline = "\n".to_string();
5464 edits.push((start_of_line..start_of_line, newline));
5465
5466 rows.push(row + rows_inserted as u32);
5467 }
5468
5469 self.transact(window, cx, |editor, window, cx| {
5470 editor.edit(edits, cx);
5471
5472 editor.change_selections(Default::default(), window, cx, |s| {
5473 let mut index = 0;
5474 s.move_cursors_with(&mut |map, _, _| {
5475 let row = rows[index];
5476 index += 1;
5477
5478 let point = Point::new(row, 0);
5479 let boundary = map.next_line_boundary(point).1;
5480 let clipped = map.clip_point(boundary, Bias::Left);
5481
5482 (clipped, SelectionGoal::None)
5483 });
5484 });
5485
5486 let mut indent_edits = Vec::new();
5487 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5488 for row in rows {
5489 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5490 for (row, indent) in indents {
5491 if indent.len == 0 {
5492 continue;
5493 }
5494
5495 let text = match indent.kind {
5496 IndentKind::Space => " ".repeat(indent.len as usize),
5497 IndentKind::Tab => "\t".repeat(indent.len as usize),
5498 };
5499 let point = Point::new(row.0, 0);
5500 indent_edits.push((point..point, text));
5501 }
5502 }
5503 editor.edit(indent_edits, cx);
5504 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5505 format.detach_and_log_err(cx);
5506 }
5507 });
5508 }
5509
5510 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5511 if self.read_only(cx) {
5512 return;
5513 }
5514
5515 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5516
5517 let mut buffer_edits: HashMap<EntityId, (Entity<Buffer>, Vec<Point>)> = HashMap::default();
5518 let mut rows = Vec::new();
5519 let mut rows_inserted = 0;
5520
5521 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5522 let cursor = selection.head();
5523 let row = cursor.row;
5524
5525 let point = Point::new(row, 0);
5526 let Some((buffer_handle, buffer_point, _)) =
5527 self.buffer.read(cx).point_to_buffer_point(point, cx)
5528 else {
5529 continue;
5530 };
5531
5532 buffer_edits
5533 .entry(buffer_handle.entity_id())
5534 .or_insert_with(|| (buffer_handle, Vec::new()))
5535 .1
5536 .push(buffer_point);
5537
5538 rows_inserted += 1;
5539 rows.push(row + rows_inserted);
5540 }
5541
5542 self.transact(window, cx, |editor, window, cx| {
5543 for (_, (buffer_handle, points)) in &buffer_edits {
5544 buffer_handle.update(cx, |buffer, cx| {
5545 let edits: Vec<_> = points
5546 .iter()
5547 .map(|point| {
5548 let target = Point::new(point.row + 1, 0);
5549 let start_of_line = buffer.point_to_offset(target).min(buffer.len());
5550 (start_of_line..start_of_line, "\n")
5551 })
5552 .collect();
5553 buffer.edit(edits, None, cx);
5554 });
5555 }
5556
5557 editor.change_selections(Default::default(), window, cx, |s| {
5558 let mut index = 0;
5559 s.move_cursors_with(&mut |map, _, _| {
5560 let row = rows[index];
5561 index += 1;
5562
5563 let point = Point::new(row, 0);
5564 let boundary = map.next_line_boundary(point).1;
5565 let clipped = map.clip_point(boundary, Bias::Left);
5566
5567 (clipped, SelectionGoal::None)
5568 });
5569 });
5570
5571 let mut indent_edits = Vec::new();
5572 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5573 for row in rows {
5574 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5575 for (row, indent) in indents {
5576 if indent.len == 0 {
5577 continue;
5578 }
5579
5580 let text = match indent.kind {
5581 IndentKind::Space => " ".repeat(indent.len as usize),
5582 IndentKind::Tab => "\t".repeat(indent.len as usize),
5583 };
5584 let point = Point::new(row.0, 0);
5585 indent_edits.push((point..point, text));
5586 }
5587 }
5588 editor.edit(indent_edits, cx);
5589 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5590 format.detach_and_log_err(cx);
5591 }
5592 });
5593 }
5594
5595 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5596 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5597 original_indent_columns: Vec::new(),
5598 });
5599 self.replace_selections(text, autoindent, window, cx, false);
5600 }
5601
5602 /// Replaces the editor's selections with the provided `text`, applying the
5603 /// given `autoindent_mode` (`None` will skip autoindentation).
5604 ///
5605 /// Early returns if the editor is in read-only mode, without applying any
5606 /// edits.
5607 fn replace_selections(
5608 &mut self,
5609 text: &str,
5610 autoindent_mode: Option<AutoindentMode>,
5611 window: &mut Window,
5612 cx: &mut Context<Self>,
5613 apply_linked_edits: bool,
5614 ) {
5615 if self.read_only(cx) {
5616 return;
5617 }
5618
5619 let text: Arc<str> = text.into();
5620 self.transact(window, cx, |this, window, cx| {
5621 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5622 let linked_edits = if apply_linked_edits {
5623 this.linked_edits_for_selections(text.clone(), cx)
5624 } else {
5625 LinkedEdits::new()
5626 };
5627
5628 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5629 let anchors = {
5630 let snapshot = buffer.read(cx);
5631 old_selections
5632 .iter()
5633 .map(|s| {
5634 let anchor = snapshot.anchor_after(s.head());
5635 s.map(|_| anchor)
5636 })
5637 .collect::<Vec<_>>()
5638 };
5639 buffer.edit(
5640 old_selections
5641 .iter()
5642 .map(|s| (s.start..s.end, text.clone())),
5643 autoindent_mode,
5644 cx,
5645 );
5646 anchors
5647 });
5648
5649 linked_edits.apply(cx);
5650
5651 this.change_selections(Default::default(), window, cx, |s| {
5652 s.select_anchors(selection_anchors);
5653 });
5654
5655 if apply_linked_edits {
5656 refresh_linked_ranges(this, window, cx);
5657 }
5658
5659 cx.notify();
5660 });
5661 }
5662
5663 /// Collects linked edits for the current selections, pairing each linked
5664 /// range with `text`.
5665 pub fn linked_edits_for_selections(&self, text: Arc<str>, cx: &App) -> LinkedEdits {
5666 let mut linked_edits = LinkedEdits::new();
5667 if !self.linked_edit_ranges.is_empty() {
5668 for selection in self.selections.disjoint_anchors() {
5669 let start = selection.start.text_anchor;
5670 let end = selection.end.text_anchor;
5671 linked_edits.push(self, start..end, text.clone(), cx);
5672 }
5673 }
5674 linked_edits
5675 }
5676
5677 /// Deletes the content covered by the current selections and applies
5678 /// linked edits.
5679 pub fn delete_selections_with_linked_edits(
5680 &mut self,
5681 window: &mut Window,
5682 cx: &mut Context<Self>,
5683 ) {
5684 self.replace_selections("", None, window, cx, true);
5685 }
5686
5687 #[cfg(any(test, feature = "test-support"))]
5688 pub fn set_linked_edit_ranges_for_testing(
5689 &mut self,
5690 ranges: Vec<(Range<Point>, Vec<Range<Point>>)>,
5691 cx: &mut Context<Self>,
5692 ) -> Option<()> {
5693 let Some((buffer, _)) = self
5694 .buffer
5695 .read(cx)
5696 .text_anchor_for_position(self.selections.newest_anchor().start, cx)
5697 else {
5698 return None;
5699 };
5700 let buffer = buffer.read(cx);
5701 let buffer_id = buffer.remote_id();
5702 let mut linked_ranges = Vec::with_capacity(ranges.len());
5703 for (base_range, linked_ranges_points) in ranges {
5704 let base_anchor =
5705 buffer.anchor_before(base_range.start)..buffer.anchor_after(base_range.end);
5706 let linked_anchors = linked_ranges_points
5707 .into_iter()
5708 .map(|range| buffer.anchor_before(range.start)..buffer.anchor_after(range.end))
5709 .collect();
5710 linked_ranges.push((base_anchor, linked_anchors));
5711 }
5712 let mut map = HashMap::default();
5713 map.insert(buffer_id, linked_ranges);
5714 self.linked_edit_ranges = linked_editing_ranges::LinkedEditingRanges(map);
5715 Some(())
5716 }
5717
5718 fn trigger_completion_on_input(
5719 &mut self,
5720 text: &str,
5721 trigger_in_words: bool,
5722 window: &mut Window,
5723 cx: &mut Context<Self>,
5724 ) {
5725 let completions_source = self
5726 .context_menu
5727 .borrow()
5728 .as_ref()
5729 .and_then(|menu| match menu {
5730 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5731 CodeContextMenu::CodeActions(_) => None,
5732 });
5733
5734 match completions_source {
5735 Some(CompletionsMenuSource::Words { .. }) => {
5736 self.open_or_update_completions_menu(
5737 Some(CompletionsMenuSource::Words {
5738 ignore_threshold: false,
5739 }),
5740 None,
5741 trigger_in_words,
5742 window,
5743 cx,
5744 );
5745 }
5746 _ => self.open_or_update_completions_menu(
5747 None,
5748 Some(text.to_owned()).filter(|x| !x.is_empty()),
5749 true,
5750 window,
5751 cx,
5752 ),
5753 }
5754 }
5755
5756 /// If any empty selections is touching the start of its innermost containing autoclose
5757 /// region, expand it to select the brackets.
5758 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5759 let selections = self
5760 .selections
5761 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5762 let buffer = self.buffer.read(cx).read(cx);
5763 let new_selections = self
5764 .selections_with_autoclose_regions(selections, &buffer)
5765 .map(|(mut selection, region)| {
5766 if !selection.is_empty() {
5767 return selection;
5768 }
5769
5770 if let Some(region) = region {
5771 let mut range = region.range.to_offset(&buffer);
5772 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5773 range.start -= region.pair.start.len();
5774 if buffer.contains_str_at(range.start, ®ion.pair.start)
5775 && buffer.contains_str_at(range.end, ®ion.pair.end)
5776 {
5777 range.end += region.pair.end.len();
5778 selection.start = range.start;
5779 selection.end = range.end;
5780
5781 return selection;
5782 }
5783 }
5784 }
5785
5786 let always_treat_brackets_as_autoclosed = buffer
5787 .language_settings_at(selection.start, cx)
5788 .always_treat_brackets_as_autoclosed;
5789
5790 if !always_treat_brackets_as_autoclosed {
5791 return selection;
5792 }
5793
5794 if let Some(scope) = buffer.language_scope_at(selection.start) {
5795 for (pair, enabled) in scope.brackets() {
5796 if !enabled || !pair.close {
5797 continue;
5798 }
5799
5800 if buffer.contains_str_at(selection.start, &pair.end) {
5801 let pair_start_len = pair.start.len();
5802 if buffer.contains_str_at(
5803 selection.start.saturating_sub_usize(pair_start_len),
5804 &pair.start,
5805 ) {
5806 selection.start -= pair_start_len;
5807 selection.end += pair.end.len();
5808
5809 return selection;
5810 }
5811 }
5812 }
5813 }
5814
5815 selection
5816 })
5817 .collect();
5818
5819 drop(buffer);
5820 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5821 selections.select(new_selections)
5822 });
5823 }
5824
5825 /// Iterate the given selections, and for each one, find the smallest surrounding
5826 /// autoclose region. This uses the ordering of the selections and the autoclose
5827 /// regions to avoid repeated comparisons.
5828 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5829 &'a self,
5830 selections: impl IntoIterator<Item = Selection<D>>,
5831 buffer: &'a MultiBufferSnapshot,
5832 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5833 let mut i = 0;
5834 let mut regions = self.autoclose_regions.as_slice();
5835 selections.into_iter().map(move |selection| {
5836 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5837
5838 let mut enclosing = None;
5839 while let Some(pair_state) = regions.get(i) {
5840 if pair_state.range.end.to_offset(buffer) < range.start {
5841 regions = ®ions[i + 1..];
5842 i = 0;
5843 } else if pair_state.range.start.to_offset(buffer) > range.end {
5844 break;
5845 } else {
5846 if pair_state.selection_id == selection.id {
5847 enclosing = Some(pair_state);
5848 }
5849 i += 1;
5850 }
5851 }
5852
5853 (selection, enclosing)
5854 })
5855 }
5856
5857 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5858 fn invalidate_autoclose_regions(
5859 &mut self,
5860 mut selections: &[Selection<Anchor>],
5861 buffer: &MultiBufferSnapshot,
5862 ) {
5863 self.autoclose_regions.retain(|state| {
5864 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5865 return false;
5866 }
5867
5868 let mut i = 0;
5869 while let Some(selection) = selections.get(i) {
5870 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5871 selections = &selections[1..];
5872 continue;
5873 }
5874 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5875 break;
5876 }
5877 if selection.id == state.selection_id {
5878 return true;
5879 } else {
5880 i += 1;
5881 }
5882 }
5883 false
5884 });
5885 }
5886
5887 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5888 let offset = position.to_offset(buffer);
5889 let (word_range, kind) =
5890 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5891 if offset > word_range.start && kind == Some(CharKind::Word) {
5892 Some(
5893 buffer
5894 .text_for_range(word_range.start..offset)
5895 .collect::<String>(),
5896 )
5897 } else {
5898 None
5899 }
5900 }
5901
5902 pub fn visible_excerpts(
5903 &self,
5904 lsp_related_only: bool,
5905 cx: &mut Context<Editor>,
5906 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5907 let project = self.project().cloned();
5908 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
5909 let multi_buffer = self.buffer().read(cx);
5910 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5911 multi_buffer_snapshot
5912 .range_to_buffer_ranges(
5913 self.multi_buffer_visible_range(&display_snapshot, cx)
5914 .to_inclusive(),
5915 )
5916 .into_iter()
5917 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5918 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5919 if !lsp_related_only {
5920 return Some((
5921 excerpt_id,
5922 (
5923 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5924 buffer.version().clone(),
5925 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5926 ),
5927 ));
5928 }
5929
5930 let project = project.as_ref()?.read(cx);
5931 let buffer_file = project::File::from_dyn(buffer.file())?;
5932 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5933 let worktree_entry = buffer_worktree
5934 .read(cx)
5935 .entry_for_id(buffer_file.project_entry_id()?)?;
5936 if worktree_entry.is_ignored {
5937 None
5938 } else {
5939 Some((
5940 excerpt_id,
5941 (
5942 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5943 buffer.version().clone(),
5944 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5945 ),
5946 ))
5947 }
5948 })
5949 .collect()
5950 }
5951
5952 pub fn text_layout_details(&self, window: &mut Window, cx: &mut App) -> TextLayoutDetails {
5953 TextLayoutDetails {
5954 text_system: window.text_system().clone(),
5955 editor_style: self.style.clone().unwrap(),
5956 rem_size: window.rem_size(),
5957 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
5958 visible_rows: self.visible_line_count(),
5959 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5960 }
5961 }
5962
5963 fn trigger_on_type_formatting(
5964 &self,
5965 input: String,
5966 window: &mut Window,
5967 cx: &mut Context<Self>,
5968 ) -> Option<Task<Result<()>>> {
5969 if input.chars().count() != 1 {
5970 return None;
5971 }
5972
5973 let project = self.project()?;
5974 let position = self.selections.newest_anchor().head();
5975 let (buffer, buffer_position) = self
5976 .buffer
5977 .read(cx)
5978 .text_anchor_for_position(position, cx)?;
5979
5980 let settings = language_settings::language_settings(
5981 buffer
5982 .read(cx)
5983 .language_at(buffer_position)
5984 .map(|l| l.name()),
5985 buffer.read(cx).file(),
5986 cx,
5987 );
5988 if !settings.use_on_type_format {
5989 return None;
5990 }
5991
5992 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5993 // hence we do LSP request & edit on host side only — add formats to host's history.
5994 let push_to_lsp_host_history = true;
5995 // If this is not the host, append its history with new edits.
5996 let push_to_client_history = project.read(cx).is_via_collab();
5997
5998 let on_type_formatting = project.update(cx, |project, cx| {
5999 project.on_type_format(
6000 buffer.clone(),
6001 buffer_position,
6002 input,
6003 push_to_lsp_host_history,
6004 cx,
6005 )
6006 });
6007 Some(cx.spawn_in(window, async move |editor, cx| {
6008 if let Some(transaction) = on_type_formatting.await? {
6009 if push_to_client_history {
6010 buffer.update(cx, |buffer, _| {
6011 buffer.push_transaction(transaction, Instant::now());
6012 buffer.finalize_last_transaction();
6013 });
6014 }
6015 editor.update(cx, |editor, cx| {
6016 editor.refresh_document_highlights(cx);
6017 })?;
6018 }
6019 Ok(())
6020 }))
6021 }
6022
6023 pub fn show_word_completions(
6024 &mut self,
6025 _: &ShowWordCompletions,
6026 window: &mut Window,
6027 cx: &mut Context<Self>,
6028 ) {
6029 self.open_or_update_completions_menu(
6030 Some(CompletionsMenuSource::Words {
6031 ignore_threshold: true,
6032 }),
6033 None,
6034 false,
6035 window,
6036 cx,
6037 );
6038 }
6039
6040 pub fn show_completions(
6041 &mut self,
6042 _: &ShowCompletions,
6043 window: &mut Window,
6044 cx: &mut Context<Self>,
6045 ) {
6046 self.open_or_update_completions_menu(None, None, false, window, cx);
6047 }
6048
6049 fn open_or_update_completions_menu(
6050 &mut self,
6051 requested_source: Option<CompletionsMenuSource>,
6052 trigger: Option<String>,
6053 trigger_in_words: bool,
6054 window: &mut Window,
6055 cx: &mut Context<Self>,
6056 ) {
6057 if self.pending_rename.is_some() {
6058 return;
6059 }
6060
6061 let completions_source = self
6062 .context_menu
6063 .borrow()
6064 .as_ref()
6065 .and_then(|menu| match menu {
6066 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
6067 CodeContextMenu::CodeActions(_) => None,
6068 });
6069
6070 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
6071
6072 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
6073 // inserted and selected. To handle that case, the start of the selection is used so that
6074 // the menu starts with all choices.
6075 let position = self
6076 .selections
6077 .newest_anchor()
6078 .start
6079 .bias_right(&multibuffer_snapshot);
6080 if position.diff_base_anchor.is_some() {
6081 return;
6082 }
6083 let buffer_position = multibuffer_snapshot.anchor_before(position);
6084 let Some(buffer) = buffer_position
6085 .text_anchor
6086 .buffer_id
6087 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
6088 else {
6089 return;
6090 };
6091 let buffer_snapshot = buffer.read(cx).snapshot();
6092
6093 let menu_is_open = matches!(
6094 self.context_menu.borrow().as_ref(),
6095 Some(CodeContextMenu::Completions(_))
6096 );
6097
6098 let language = buffer_snapshot
6099 .language_at(buffer_position.text_anchor)
6100 .map(|language| language.name());
6101
6102 let language_settings = language_settings(language.clone(), buffer_snapshot.file(), cx);
6103 let completion_settings = language_settings.completions.clone();
6104
6105 let show_completions_on_input = self
6106 .show_completions_on_input_override
6107 .unwrap_or(language_settings.show_completions_on_input);
6108 if !menu_is_open && trigger.is_some() && !show_completions_on_input {
6109 return;
6110 }
6111
6112 let query: Option<Arc<String>> =
6113 Self::completion_query(&multibuffer_snapshot, buffer_position)
6114 .map(|query| query.into());
6115
6116 drop(multibuffer_snapshot);
6117
6118 // Hide the current completions menu when query is empty. Without this, cached
6119 // completions from before the trigger char may be reused (#32774).
6120 if query.is_none() && menu_is_open {
6121 self.hide_context_menu(window, cx);
6122 }
6123
6124 let mut ignore_word_threshold = false;
6125 let provider = match requested_source {
6126 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
6127 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
6128 ignore_word_threshold = ignore_threshold;
6129 None
6130 }
6131 Some(CompletionsMenuSource::SnippetChoices)
6132 | Some(CompletionsMenuSource::SnippetsOnly) => {
6133 log::error!("bug: SnippetChoices requested_source is not handled");
6134 None
6135 }
6136 };
6137
6138 let sort_completions = provider
6139 .as_ref()
6140 .is_some_and(|provider| provider.sort_completions());
6141
6142 let filter_completions = provider
6143 .as_ref()
6144 .is_none_or(|provider| provider.filter_completions());
6145
6146 let was_snippets_only = matches!(
6147 completions_source,
6148 Some(CompletionsMenuSource::SnippetsOnly)
6149 );
6150
6151 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
6152 if filter_completions {
6153 menu.filter(
6154 query.clone().unwrap_or_default(),
6155 buffer_position.text_anchor,
6156 &buffer,
6157 provider.clone(),
6158 window,
6159 cx,
6160 );
6161 }
6162 // When `is_incomplete` is false, no need to re-query completions when the current query
6163 // is a suffix of the initial query.
6164 let was_complete = !menu.is_incomplete;
6165 if was_complete && !was_snippets_only {
6166 // If the new query is a suffix of the old query (typing more characters) and
6167 // the previous result was complete, the existing completions can be filtered.
6168 //
6169 // Note that snippet completions are always complete.
6170 let query_matches = match (&menu.initial_query, &query) {
6171 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
6172 (None, _) => true,
6173 _ => false,
6174 };
6175 if query_matches {
6176 let position_matches = if menu.initial_position == position {
6177 true
6178 } else {
6179 let snapshot = self.buffer.read(cx).read(cx);
6180 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
6181 };
6182 if position_matches {
6183 return;
6184 }
6185 }
6186 }
6187 };
6188
6189 let Anchor {
6190 excerpt_id: buffer_excerpt_id,
6191 text_anchor: buffer_position,
6192 ..
6193 } = buffer_position;
6194
6195 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
6196 buffer_snapshot.surrounding_word(buffer_position, None)
6197 {
6198 let word_to_exclude = buffer_snapshot
6199 .text_for_range(word_range.clone())
6200 .collect::<String>();
6201 (
6202 buffer_snapshot.anchor_before(word_range.start)
6203 ..buffer_snapshot.anchor_after(buffer_position),
6204 Some(word_to_exclude),
6205 )
6206 } else {
6207 (buffer_position..buffer_position, None)
6208 };
6209
6210 let show_completion_documentation = buffer_snapshot
6211 .settings_at(buffer_position, cx)
6212 .show_completion_documentation;
6213
6214 // The document can be large, so stay in reasonable bounds when searching for words,
6215 // otherwise completion pop-up might be slow to appear.
6216 const WORD_LOOKUP_ROWS: u32 = 5_000;
6217 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
6218 let min_word_search = buffer_snapshot.clip_point(
6219 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
6220 Bias::Left,
6221 );
6222 let max_word_search = buffer_snapshot.clip_point(
6223 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
6224 Bias::Right,
6225 );
6226 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
6227 ..buffer_snapshot.point_to_offset(max_word_search);
6228
6229 let skip_digits = query
6230 .as_ref()
6231 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
6232
6233 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
6234 trigger.as_ref().is_none_or(|trigger| {
6235 provider.is_completion_trigger(
6236 &buffer,
6237 position.text_anchor,
6238 trigger,
6239 trigger_in_words,
6240 cx,
6241 )
6242 })
6243 });
6244
6245 let provider_responses = if let Some(provider) = &provider
6246 && load_provider_completions
6247 {
6248 let trigger_character =
6249 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
6250 let completion_context = CompletionContext {
6251 trigger_kind: match &trigger_character {
6252 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
6253 None => CompletionTriggerKind::INVOKED,
6254 },
6255 trigger_character,
6256 };
6257
6258 provider.completions(
6259 buffer_excerpt_id,
6260 &buffer,
6261 buffer_position,
6262 completion_context,
6263 window,
6264 cx,
6265 )
6266 } else {
6267 Task::ready(Ok(Vec::new()))
6268 };
6269
6270 let load_word_completions = if !self.word_completions_enabled {
6271 false
6272 } else if requested_source
6273 == Some(CompletionsMenuSource::Words {
6274 ignore_threshold: true,
6275 })
6276 {
6277 true
6278 } else {
6279 load_provider_completions
6280 && completion_settings.words != WordsCompletionMode::Disabled
6281 && (ignore_word_threshold || {
6282 let words_min_length = completion_settings.words_min_length;
6283 // check whether word has at least `words_min_length` characters
6284 let query_chars = query.iter().flat_map(|q| q.chars());
6285 query_chars.take(words_min_length).count() == words_min_length
6286 })
6287 };
6288
6289 let mut words = if load_word_completions {
6290 cx.background_spawn({
6291 let buffer_snapshot = buffer_snapshot.clone();
6292 async move {
6293 buffer_snapshot.words_in_range(WordsQuery {
6294 fuzzy_contents: None,
6295 range: word_search_range,
6296 skip_digits,
6297 })
6298 }
6299 })
6300 } else {
6301 Task::ready(BTreeMap::default())
6302 };
6303
6304 let snippets = if let Some(provider) = &provider
6305 && provider.show_snippets()
6306 && let Some(project) = self.project()
6307 {
6308 let char_classifier = buffer_snapshot
6309 .char_classifier_at(buffer_position)
6310 .scope_context(Some(CharScopeContext::Completion));
6311 project.update(cx, |project, cx| {
6312 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
6313 })
6314 } else {
6315 Task::ready(Ok(CompletionResponse {
6316 completions: Vec::new(),
6317 display_options: Default::default(),
6318 is_incomplete: false,
6319 }))
6320 };
6321
6322 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
6323
6324 let id = post_inc(&mut self.next_completion_id);
6325 let task = cx.spawn_in(window, async move |editor, cx| {
6326 let Ok(()) = editor.update(cx, |this, _| {
6327 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
6328 }) else {
6329 return;
6330 };
6331
6332 // TODO: Ideally completions from different sources would be selectively re-queried, so
6333 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
6334 let mut completions = Vec::new();
6335 let mut is_incomplete = false;
6336 let mut display_options: Option<CompletionDisplayOptions> = None;
6337 if let Some(provider_responses) = provider_responses.await.log_err()
6338 && !provider_responses.is_empty()
6339 {
6340 for response in provider_responses {
6341 completions.extend(response.completions);
6342 is_incomplete = is_incomplete || response.is_incomplete;
6343 match display_options.as_mut() {
6344 None => {
6345 display_options = Some(response.display_options);
6346 }
6347 Some(options) => options.merge(&response.display_options),
6348 }
6349 }
6350 if completion_settings.words == WordsCompletionMode::Fallback {
6351 words = Task::ready(BTreeMap::default());
6352 }
6353 }
6354 let display_options = display_options.unwrap_or_default();
6355
6356 let mut words = words.await;
6357 if let Some(word_to_exclude) = &word_to_exclude {
6358 words.remove(word_to_exclude);
6359 }
6360 for lsp_completion in &completions {
6361 words.remove(&lsp_completion.new_text);
6362 }
6363 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
6364 replace_range: word_replace_range.clone(),
6365 new_text: word.clone(),
6366 label: CodeLabel::plain(word, None),
6367 match_start: None,
6368 snippet_deduplication_key: None,
6369 icon_path: None,
6370 documentation: None,
6371 source: CompletionSource::BufferWord {
6372 word_range,
6373 resolved: false,
6374 },
6375 insert_text_mode: Some(InsertTextMode::AS_IS),
6376 confirm: None,
6377 }));
6378
6379 completions.extend(
6380 snippets
6381 .await
6382 .into_iter()
6383 .flat_map(|response| response.completions),
6384 );
6385
6386 let menu = if completions.is_empty() {
6387 None
6388 } else {
6389 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
6390 let languages = editor
6391 .workspace
6392 .as_ref()
6393 .and_then(|(workspace, _)| workspace.upgrade())
6394 .map(|workspace| workspace.read(cx).app_state().languages.clone());
6395 let menu = CompletionsMenu::new(
6396 id,
6397 requested_source.unwrap_or(if load_provider_completions {
6398 CompletionsMenuSource::Normal
6399 } else {
6400 CompletionsMenuSource::SnippetsOnly
6401 }),
6402 sort_completions,
6403 show_completion_documentation,
6404 position,
6405 query.clone(),
6406 is_incomplete,
6407 buffer.clone(),
6408 completions.into(),
6409 editor
6410 .context_menu()
6411 .borrow_mut()
6412 .as_ref()
6413 .map(|menu| menu.primary_scroll_handle()),
6414 display_options,
6415 snippet_sort_order,
6416 languages,
6417 language,
6418 cx,
6419 );
6420
6421 let query = if filter_completions { query } else { None };
6422 let matches_task = menu.do_async_filtering(
6423 query.unwrap_or_default(),
6424 buffer_position,
6425 &buffer,
6426 cx,
6427 );
6428 (menu, matches_task)
6429 }) else {
6430 return;
6431 };
6432
6433 let matches = matches_task.await;
6434
6435 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
6436 // Newer menu already set, so exit.
6437 if let Some(CodeContextMenu::Completions(prev_menu)) =
6438 editor.context_menu.borrow().as_ref()
6439 && prev_menu.id > id
6440 {
6441 return;
6442 };
6443
6444 // Only valid to take prev_menu because either the new menu is immediately set
6445 // below, or the menu is hidden.
6446 if let Some(CodeContextMenu::Completions(prev_menu)) =
6447 editor.context_menu.borrow_mut().take()
6448 {
6449 let position_matches =
6450 if prev_menu.initial_position == menu.initial_position {
6451 true
6452 } else {
6453 let snapshot = editor.buffer.read(cx).read(cx);
6454 prev_menu.initial_position.to_offset(&snapshot)
6455 == menu.initial_position.to_offset(&snapshot)
6456 };
6457 if position_matches {
6458 // Preserve markdown cache before `set_filter_results` because it will
6459 // try to populate the documentation cache.
6460 menu.preserve_markdown_cache(prev_menu);
6461 }
6462 };
6463
6464 menu.set_filter_results(matches, provider, window, cx);
6465 }) else {
6466 return;
6467 };
6468
6469 menu.visible().then_some(menu)
6470 };
6471
6472 editor
6473 .update_in(cx, |editor, window, cx| {
6474 if editor.focus_handle.is_focused(window)
6475 && let Some(menu) = menu
6476 {
6477 *editor.context_menu.borrow_mut() =
6478 Some(CodeContextMenu::Completions(menu));
6479
6480 crate::hover_popover::hide_hover(editor, cx);
6481 if editor.show_edit_predictions_in_menu() {
6482 editor.update_visible_edit_prediction(window, cx);
6483 } else {
6484 editor
6485 .discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
6486 }
6487
6488 cx.notify();
6489 return;
6490 }
6491
6492 if editor.completion_tasks.len() <= 1 {
6493 // If there are no more completion tasks and the last menu was empty, we should hide it.
6494 let was_hidden = editor.hide_context_menu(window, cx).is_none();
6495 // If it was already hidden and we don't show edit predictions in the menu,
6496 // we should also show the edit prediction when available.
6497 if was_hidden && editor.show_edit_predictions_in_menu() {
6498 editor.update_visible_edit_prediction(window, cx);
6499 }
6500 }
6501 })
6502 .ok();
6503 });
6504
6505 self.completion_tasks.push((id, task));
6506 }
6507
6508 #[cfg(any(test, feature = "test-support"))]
6509 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
6510 let menu = self.context_menu.borrow();
6511 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
6512 let completions = menu.completions.borrow();
6513 Some(completions.to_vec())
6514 } else {
6515 None
6516 }
6517 }
6518
6519 pub fn with_completions_menu_matching_id<R>(
6520 &self,
6521 id: CompletionId,
6522 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
6523 ) -> R {
6524 let mut context_menu = self.context_menu.borrow_mut();
6525 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
6526 return f(None);
6527 };
6528 if completions_menu.id != id {
6529 return f(None);
6530 }
6531 f(Some(completions_menu))
6532 }
6533
6534 pub fn confirm_completion(
6535 &mut self,
6536 action: &ConfirmCompletion,
6537 window: &mut Window,
6538 cx: &mut Context<Self>,
6539 ) -> Option<Task<Result<()>>> {
6540 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6541 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
6542 }
6543
6544 pub fn confirm_completion_insert(
6545 &mut self,
6546 _: &ConfirmCompletionInsert,
6547 window: &mut Window,
6548 cx: &mut Context<Self>,
6549 ) -> Option<Task<Result<()>>> {
6550 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6551 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
6552 }
6553
6554 pub fn confirm_completion_replace(
6555 &mut self,
6556 _: &ConfirmCompletionReplace,
6557 window: &mut Window,
6558 cx: &mut Context<Self>,
6559 ) -> Option<Task<Result<()>>> {
6560 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6561 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
6562 }
6563
6564 pub fn compose_completion(
6565 &mut self,
6566 action: &ComposeCompletion,
6567 window: &mut Window,
6568 cx: &mut Context<Self>,
6569 ) -> Option<Task<Result<()>>> {
6570 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6571 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
6572 }
6573
6574 fn do_completion(
6575 &mut self,
6576 item_ix: Option<usize>,
6577 intent: CompletionIntent,
6578 window: &mut Window,
6579 cx: &mut Context<Editor>,
6580 ) -> Option<Task<Result<()>>> {
6581 use language::ToOffset as _;
6582
6583 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
6584 else {
6585 return None;
6586 };
6587
6588 let candidate_id = {
6589 let entries = completions_menu.entries.borrow();
6590 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6591 if self.show_edit_predictions_in_menu() {
6592 self.discard_edit_prediction(EditPredictionDiscardReason::Rejected, cx);
6593 }
6594 mat.candidate_id
6595 };
6596
6597 let completion = completions_menu
6598 .completions
6599 .borrow()
6600 .get(candidate_id)?
6601 .clone();
6602 cx.stop_propagation();
6603
6604 let buffer_handle = completions_menu.buffer.clone();
6605
6606 let CompletionEdit {
6607 new_text,
6608 snippet,
6609 replace_range,
6610 } = process_completion_for_edit(
6611 &completion,
6612 intent,
6613 &buffer_handle,
6614 &completions_menu.initial_position.text_anchor,
6615 cx,
6616 );
6617
6618 let buffer = buffer_handle.read(cx);
6619 let snapshot = self.buffer.read(cx).snapshot(cx);
6620 let newest_anchor = self.selections.newest_anchor();
6621 let replace_range_multibuffer = {
6622 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6623 excerpt.map_range_from_buffer(replace_range.clone())
6624 };
6625 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6626 return None;
6627 }
6628
6629 let old_text = buffer
6630 .text_for_range(replace_range.clone())
6631 .collect::<String>();
6632 let lookbehind = newest_anchor
6633 .start
6634 .text_anchor
6635 .to_offset(buffer)
6636 .saturating_sub(replace_range.start.0);
6637 let lookahead = replace_range
6638 .end
6639 .0
6640 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6641 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6642 let suffix = &old_text[lookbehind.min(old_text.len())..];
6643
6644 let selections = self
6645 .selections
6646 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6647 let mut ranges = Vec::new();
6648 let mut all_commit_ranges = Vec::new();
6649 let mut linked_edits = LinkedEdits::new();
6650
6651 let text: Arc<str> = new_text.clone().into();
6652 for selection in &selections {
6653 let range = if selection.id == newest_anchor.id {
6654 replace_range_multibuffer.clone()
6655 } else {
6656 let mut range = selection.range();
6657
6658 // if prefix is present, don't duplicate it
6659 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6660 range.start = range.start.saturating_sub_usize(lookbehind);
6661
6662 // if suffix is also present, mimic the newest cursor and replace it
6663 if selection.id != newest_anchor.id
6664 && snapshot.contains_str_at(range.end, suffix)
6665 {
6666 range.end += lookahead;
6667 }
6668 }
6669 range
6670 };
6671
6672 ranges.push(range.clone());
6673
6674 let start_anchor = snapshot.anchor_before(range.start);
6675 let end_anchor = snapshot.anchor_after(range.end);
6676 let anchor_range = start_anchor.text_anchor..end_anchor.text_anchor;
6677 all_commit_ranges.push(anchor_range.clone());
6678
6679 if !self.linked_edit_ranges.is_empty() {
6680 linked_edits.push(&self, anchor_range, text.clone(), cx);
6681 }
6682 }
6683
6684 let common_prefix_len = old_text
6685 .chars()
6686 .zip(new_text.chars())
6687 .take_while(|(a, b)| a == b)
6688 .map(|(a, _)| a.len_utf8())
6689 .sum::<usize>();
6690
6691 cx.emit(EditorEvent::InputHandled {
6692 utf16_range_to_replace: None,
6693 text: new_text[common_prefix_len..].into(),
6694 });
6695
6696 self.transact(window, cx, |editor, window, cx| {
6697 if let Some(mut snippet) = snippet {
6698 snippet.text = new_text.to_string();
6699 editor
6700 .insert_snippet(&ranges, snippet, window, cx)
6701 .log_err();
6702 } else {
6703 editor.buffer.update(cx, |multi_buffer, cx| {
6704 let auto_indent = match completion.insert_text_mode {
6705 Some(InsertTextMode::AS_IS) => None,
6706 _ => editor.autoindent_mode.clone(),
6707 };
6708 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6709 multi_buffer.edit(edits, auto_indent, cx);
6710 });
6711 }
6712 linked_edits.apply(cx);
6713 editor.refresh_edit_prediction(true, false, window, cx);
6714 });
6715 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6716
6717 let show_new_completions_on_confirm = completion
6718 .confirm
6719 .as_ref()
6720 .is_some_and(|confirm| confirm(intent, window, cx));
6721 if show_new_completions_on_confirm {
6722 self.open_or_update_completions_menu(None, None, false, window, cx);
6723 }
6724
6725 let provider = self.completion_provider.as_ref()?;
6726
6727 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6728 let command = lsp_store.as_ref().and_then(|lsp_store| {
6729 let CompletionSource::Lsp {
6730 lsp_completion,
6731 server_id,
6732 ..
6733 } = &completion.source
6734 else {
6735 return None;
6736 };
6737 let lsp_command = lsp_completion.command.as_ref()?;
6738 let available_commands = lsp_store
6739 .read(cx)
6740 .lsp_server_capabilities
6741 .get(server_id)
6742 .and_then(|server_capabilities| {
6743 server_capabilities
6744 .execute_command_provider
6745 .as_ref()
6746 .map(|options| options.commands.as_slice())
6747 })?;
6748 if available_commands.contains(&lsp_command.command) {
6749 Some(CodeAction {
6750 server_id: *server_id,
6751 range: language::Anchor::MIN..language::Anchor::MIN,
6752 lsp_action: LspAction::Command(lsp_command.clone()),
6753 resolved: false,
6754 })
6755 } else {
6756 None
6757 }
6758 });
6759
6760 drop(completion);
6761 let apply_edits = provider.apply_additional_edits_for_completion(
6762 buffer_handle.clone(),
6763 completions_menu.completions.clone(),
6764 candidate_id,
6765 true,
6766 all_commit_ranges,
6767 cx,
6768 );
6769
6770 let editor_settings = EditorSettings::get_global(cx);
6771 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6772 // After the code completion is finished, users often want to know what signatures are needed.
6773 // so we should automatically call signature_help
6774 self.show_signature_help(&ShowSignatureHelp, window, cx);
6775 }
6776
6777 Some(cx.spawn_in(window, async move |editor, cx| {
6778 apply_edits.await?;
6779
6780 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6781 let title = command.lsp_action.title().to_owned();
6782 let project_transaction = lsp_store
6783 .update(cx, |lsp_store, cx| {
6784 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6785 })
6786 .await
6787 .context("applying post-completion command")?;
6788 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6789 Self::open_project_transaction(
6790 &editor,
6791 workspace.downgrade(),
6792 project_transaction,
6793 title,
6794 cx,
6795 )
6796 .await?;
6797 }
6798 }
6799
6800 Ok(())
6801 }))
6802 }
6803
6804 pub fn toggle_code_actions(
6805 &mut self,
6806 action: &ToggleCodeActions,
6807 window: &mut Window,
6808 cx: &mut Context<Self>,
6809 ) {
6810 let quick_launch = action.quick_launch;
6811 let mut context_menu = self.context_menu.borrow_mut();
6812 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6813 if code_actions.deployed_from == action.deployed_from {
6814 // Toggle if we're selecting the same one
6815 *context_menu = None;
6816 cx.notify();
6817 return;
6818 } else {
6819 // Otherwise, clear it and start a new one
6820 *context_menu = None;
6821 cx.notify();
6822 }
6823 }
6824 drop(context_menu);
6825 let snapshot = self.snapshot(window, cx);
6826 let deployed_from = action.deployed_from.clone();
6827 let action = action.clone();
6828 self.completion_tasks.clear();
6829 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
6830
6831 let multibuffer_point = match &action.deployed_from {
6832 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6833 DisplayPoint::new(*row, 0).to_point(&snapshot)
6834 }
6835 _ => self
6836 .selections
6837 .newest::<Point>(&snapshot.display_snapshot)
6838 .head(),
6839 };
6840 let Some((buffer, buffer_row)) = snapshot
6841 .buffer_snapshot()
6842 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6843 .and_then(|(buffer_snapshot, range)| {
6844 self.buffer()
6845 .read(cx)
6846 .buffer(buffer_snapshot.remote_id())
6847 .map(|buffer| (buffer, range.start.row))
6848 })
6849 else {
6850 return;
6851 };
6852 let buffer_id = buffer.read(cx).remote_id();
6853 let tasks = self
6854 .runnables
6855 .runnables((buffer_id, buffer_row))
6856 .map(|t| Arc::new(t.to_owned()));
6857
6858 if !self.focus_handle.is_focused(window) {
6859 return;
6860 }
6861 let project = self.project.clone();
6862
6863 let code_actions_task = match deployed_from {
6864 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6865 _ => self.code_actions(buffer_row, window, cx),
6866 };
6867
6868 let runnable_task = match deployed_from {
6869 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6870 _ => {
6871 let mut task_context_task = Task::ready(None);
6872 if let Some(tasks) = &tasks
6873 && let Some(project) = project
6874 {
6875 task_context_task =
6876 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6877 }
6878
6879 cx.spawn_in(window, {
6880 let buffer = buffer.clone();
6881 async move |editor, cx| {
6882 let task_context = task_context_task.await;
6883
6884 let resolved_tasks =
6885 tasks
6886 .zip(task_context.clone())
6887 .map(|(tasks, task_context)| ResolvedTasks {
6888 templates: tasks.resolve(&task_context).collect(),
6889 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6890 multibuffer_point.row,
6891 tasks.column,
6892 )),
6893 });
6894 let debug_scenarios = editor
6895 .update(cx, |editor, cx| {
6896 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6897 })?
6898 .await;
6899 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6900 }
6901 })
6902 }
6903 };
6904
6905 cx.spawn_in(window, async move |editor, cx| {
6906 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6907 let code_actions = code_actions_task.await;
6908 let spawn_straight_away = quick_launch
6909 && resolved_tasks
6910 .as_ref()
6911 .is_some_and(|tasks| tasks.templates.len() == 1)
6912 && code_actions
6913 .as_ref()
6914 .is_none_or(|actions| actions.is_empty())
6915 && debug_scenarios.is_empty();
6916
6917 editor.update_in(cx, |editor, window, cx| {
6918 crate::hover_popover::hide_hover(editor, cx);
6919 let actions = CodeActionContents::new(
6920 resolved_tasks,
6921 code_actions,
6922 debug_scenarios,
6923 task_context.unwrap_or_default(),
6924 );
6925
6926 // Don't show the menu if there are no actions available
6927 if actions.is_empty() {
6928 cx.notify();
6929 return Task::ready(Ok(()));
6930 }
6931
6932 *editor.context_menu.borrow_mut() =
6933 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6934 buffer,
6935 actions,
6936 selected_item: Default::default(),
6937 scroll_handle: UniformListScrollHandle::default(),
6938 deployed_from,
6939 }));
6940 cx.notify();
6941 if spawn_straight_away
6942 && let Some(task) = editor.confirm_code_action(
6943 &ConfirmCodeAction { item_ix: Some(0) },
6944 window,
6945 cx,
6946 )
6947 {
6948 return task;
6949 }
6950
6951 Task::ready(Ok(()))
6952 })
6953 })
6954 .detach_and_log_err(cx);
6955 }
6956
6957 fn debug_scenarios(
6958 &mut self,
6959 resolved_tasks: &Option<ResolvedTasks>,
6960 buffer: &Entity<Buffer>,
6961 cx: &mut App,
6962 ) -> Task<Vec<task::DebugScenario>> {
6963 maybe!({
6964 let project = self.project()?;
6965 let dap_store = project.read(cx).dap_store();
6966 let mut scenarios = vec![];
6967 let resolved_tasks = resolved_tasks.as_ref()?;
6968 let buffer = buffer.read(cx);
6969 let language = buffer.language()?;
6970 let file = buffer.file();
6971 let debug_adapter = language_settings(language.name().into(), file, cx)
6972 .debuggers
6973 .first()
6974 .map(SharedString::from)
6975 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6976
6977 dap_store.update(cx, |dap_store, cx| {
6978 for (_, task) in &resolved_tasks.templates {
6979 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6980 task.original_task().clone(),
6981 debug_adapter.clone().into(),
6982 task.display_label().to_owned().into(),
6983 cx,
6984 );
6985 scenarios.push(maybe_scenario);
6986 }
6987 });
6988 Some(cx.background_spawn(async move {
6989 futures::future::join_all(scenarios)
6990 .await
6991 .into_iter()
6992 .flatten()
6993 .collect::<Vec<_>>()
6994 }))
6995 })
6996 .unwrap_or_else(|| Task::ready(vec![]))
6997 }
6998
6999 fn code_actions(
7000 &mut self,
7001 buffer_row: u32,
7002 window: &mut Window,
7003 cx: &mut Context<Self>,
7004 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
7005 let mut task = self.code_actions_task.take();
7006 cx.spawn_in(window, async move |editor, cx| {
7007 while let Some(prev_task) = task {
7008 prev_task.await.log_err();
7009 task = editor
7010 .update(cx, |this, _| this.code_actions_task.take())
7011 .ok()?;
7012 }
7013
7014 editor
7015 .update(cx, |editor, cx| {
7016 editor
7017 .available_code_actions
7018 .clone()
7019 .and_then(|(location, code_actions)| {
7020 let snapshot = location.buffer.read(cx).snapshot();
7021 let point_range = location.range.to_point(&snapshot);
7022 let point_range = point_range.start.row..=point_range.end.row;
7023 if point_range.contains(&buffer_row) {
7024 Some(code_actions)
7025 } else {
7026 None
7027 }
7028 })
7029 })
7030 .ok()
7031 .flatten()
7032 })
7033 }
7034
7035 pub fn confirm_code_action(
7036 &mut self,
7037 action: &ConfirmCodeAction,
7038 window: &mut Window,
7039 cx: &mut Context<Self>,
7040 ) -> Option<Task<Result<()>>> {
7041 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
7042
7043 let actions_menu =
7044 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
7045 menu
7046 } else {
7047 return None;
7048 };
7049
7050 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
7051 let action = actions_menu.actions.get(action_ix)?;
7052 let title = action.label();
7053 let buffer = actions_menu.buffer;
7054 let workspace = self.workspace()?;
7055
7056 match action {
7057 CodeActionsItem::Task(task_source_kind, resolved_task) => {
7058 workspace.update(cx, |workspace, cx| {
7059 workspace.schedule_resolved_task(
7060 task_source_kind,
7061 resolved_task,
7062 false,
7063 window,
7064 cx,
7065 );
7066
7067 Some(Task::ready(Ok(())))
7068 })
7069 }
7070 CodeActionsItem::CodeAction {
7071 excerpt_id,
7072 action,
7073 provider,
7074 } => {
7075 let apply_code_action =
7076 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
7077 let workspace = workspace.downgrade();
7078 Some(cx.spawn_in(window, async move |editor, cx| {
7079 let project_transaction = apply_code_action.await?;
7080 Self::open_project_transaction(
7081 &editor,
7082 workspace,
7083 project_transaction,
7084 title,
7085 cx,
7086 )
7087 .await
7088 }))
7089 }
7090 CodeActionsItem::DebugScenario(scenario) => {
7091 let context = actions_menu.actions.context.into();
7092
7093 workspace.update(cx, |workspace, cx| {
7094 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
7095 workspace.start_debug_session(
7096 scenario,
7097 context,
7098 Some(buffer),
7099 None,
7100 window,
7101 cx,
7102 );
7103 });
7104 Some(Task::ready(Ok(())))
7105 }
7106 }
7107 }
7108
7109 fn open_transaction_for_hidden_buffers(
7110 workspace: Entity<Workspace>,
7111 transaction: ProjectTransaction,
7112 title: String,
7113 window: &mut Window,
7114 cx: &mut Context<Self>,
7115 ) {
7116 if transaction.0.is_empty() {
7117 return;
7118 }
7119
7120 let edited_buffers_already_open = {
7121 let other_editors: Vec<Entity<Editor>> = workspace
7122 .read(cx)
7123 .panes()
7124 .iter()
7125 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
7126 .filter(|editor| editor.entity_id() != cx.entity_id())
7127 .collect();
7128
7129 transaction.0.keys().all(|buffer| {
7130 other_editors.iter().any(|editor| {
7131 let multi_buffer = editor.read(cx).buffer();
7132 multi_buffer.read(cx).is_singleton()
7133 && multi_buffer
7134 .read(cx)
7135 .as_singleton()
7136 .map_or(false, |singleton| {
7137 singleton.entity_id() == buffer.entity_id()
7138 })
7139 })
7140 })
7141 };
7142 if !edited_buffers_already_open {
7143 let workspace = workspace.downgrade();
7144 cx.defer_in(window, move |_, window, cx| {
7145 cx.spawn_in(window, async move |editor, cx| {
7146 Self::open_project_transaction(&editor, workspace, transaction, title, cx)
7147 .await
7148 .ok()
7149 })
7150 .detach();
7151 });
7152 }
7153 }
7154
7155 pub async fn open_project_transaction(
7156 editor: &WeakEntity<Editor>,
7157 workspace: WeakEntity<Workspace>,
7158 transaction: ProjectTransaction,
7159 title: String,
7160 cx: &mut AsyncWindowContext,
7161 ) -> Result<()> {
7162 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
7163 cx.update(|_, cx| {
7164 entries.sort_unstable_by_key(|(buffer, _)| {
7165 buffer.read(cx).file().map(|f| f.path().clone())
7166 });
7167 })?;
7168 if entries.is_empty() {
7169 return Ok(());
7170 }
7171
7172 // If the project transaction's edits are all contained within this editor, then
7173 // avoid opening a new editor to display them.
7174
7175 if let [(buffer, transaction)] = &*entries {
7176 let excerpt = editor.update(cx, |editor, cx| {
7177 editor
7178 .buffer()
7179 .read(cx)
7180 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
7181 })?;
7182 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
7183 && excerpted_buffer == *buffer
7184 {
7185 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
7186 let excerpt_range = excerpt_range.to_offset(buffer);
7187 buffer
7188 .edited_ranges_for_transaction::<usize>(transaction)
7189 .all(|range| {
7190 excerpt_range.start <= range.start && excerpt_range.end >= range.end
7191 })
7192 });
7193
7194 if all_edits_within_excerpt {
7195 return Ok(());
7196 }
7197 }
7198 }
7199
7200 let mut ranges_to_highlight = Vec::new();
7201 let excerpt_buffer = cx.new(|cx| {
7202 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
7203 for (buffer_handle, transaction) in &entries {
7204 let edited_ranges = buffer_handle
7205 .read(cx)
7206 .edited_ranges_for_transaction::<Point>(transaction)
7207 .collect::<Vec<_>>();
7208 let (ranges, _) = multibuffer.set_excerpts_for_path(
7209 PathKey::for_buffer(buffer_handle, cx),
7210 buffer_handle.clone(),
7211 edited_ranges,
7212 multibuffer_context_lines(cx),
7213 cx,
7214 );
7215
7216 ranges_to_highlight.extend(ranges);
7217 }
7218 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
7219 multibuffer
7220 });
7221
7222 workspace.update_in(cx, |workspace, window, cx| {
7223 let project = workspace.project().clone();
7224 let editor =
7225 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
7226 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
7227 editor.update(cx, |editor, cx| {
7228 editor.highlight_background(
7229 HighlightKey::Editor,
7230 &ranges_to_highlight,
7231 |_, theme| theme.colors().editor_highlighted_line_background,
7232 cx,
7233 );
7234 });
7235 })?;
7236
7237 Ok(())
7238 }
7239
7240 pub fn clear_code_action_providers(&mut self) {
7241 self.code_action_providers.clear();
7242 self.available_code_actions.take();
7243 }
7244
7245 pub fn add_code_action_provider(
7246 &mut self,
7247 provider: Rc<dyn CodeActionProvider>,
7248 window: &mut Window,
7249 cx: &mut Context<Self>,
7250 ) {
7251 if self
7252 .code_action_providers
7253 .iter()
7254 .any(|existing_provider| existing_provider.id() == provider.id())
7255 {
7256 return;
7257 }
7258
7259 self.code_action_providers.push(provider);
7260 self.refresh_code_actions(window, cx);
7261 }
7262
7263 pub fn remove_code_action_provider(
7264 &mut self,
7265 id: Arc<str>,
7266 window: &mut Window,
7267 cx: &mut Context<Self>,
7268 ) {
7269 self.code_action_providers
7270 .retain(|provider| provider.id() != id);
7271 self.refresh_code_actions(window, cx);
7272 }
7273
7274 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
7275 !self.code_action_providers.is_empty()
7276 && EditorSettings::get_global(cx).toolbar.code_actions
7277 }
7278
7279 pub fn has_available_code_actions(&self) -> bool {
7280 self.available_code_actions
7281 .as_ref()
7282 .is_some_and(|(_, actions)| !actions.is_empty())
7283 }
7284
7285 fn render_inline_code_actions(
7286 &self,
7287 icon_size: ui::IconSize,
7288 display_row: DisplayRow,
7289 is_active: bool,
7290 cx: &mut Context<Self>,
7291 ) -> AnyElement {
7292 let show_tooltip = !self.context_menu_visible();
7293 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
7294 .icon_size(icon_size)
7295 .shape(ui::IconButtonShape::Square)
7296 .icon_color(ui::Color::Hidden)
7297 .toggle_state(is_active)
7298 .when(show_tooltip, |this| {
7299 this.tooltip({
7300 let focus_handle = self.focus_handle.clone();
7301 move |_window, cx| {
7302 Tooltip::for_action_in(
7303 "Toggle Code Actions",
7304 &ToggleCodeActions {
7305 deployed_from: None,
7306 quick_launch: false,
7307 },
7308 &focus_handle,
7309 cx,
7310 )
7311 }
7312 })
7313 })
7314 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
7315 window.focus(&editor.focus_handle(cx), cx);
7316 editor.toggle_code_actions(
7317 &crate::actions::ToggleCodeActions {
7318 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
7319 display_row,
7320 )),
7321 quick_launch: false,
7322 },
7323 window,
7324 cx,
7325 );
7326 }))
7327 .into_any_element()
7328 }
7329
7330 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
7331 &self.context_menu
7332 }
7333
7334 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7335 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
7336 cx.background_executor()
7337 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
7338 .await;
7339
7340 let (start_buffer, start, _, end, newest_selection) = this
7341 .update(cx, |this, cx| {
7342 let newest_selection = this.selections.newest_anchor().clone();
7343 if newest_selection.head().diff_base_anchor.is_some() {
7344 return None;
7345 }
7346 let display_snapshot = this.display_snapshot(cx);
7347 let newest_selection_adjusted =
7348 this.selections.newest_adjusted(&display_snapshot);
7349 let buffer = this.buffer.read(cx);
7350
7351 let (start_buffer, start) =
7352 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
7353 let (end_buffer, end) =
7354 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
7355
7356 Some((start_buffer, start, end_buffer, end, newest_selection))
7357 })?
7358 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
7359 .context(
7360 "Expected selection to lie in a single buffer when refreshing code actions",
7361 )?;
7362 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
7363 let providers = this.code_action_providers.clone();
7364 let tasks = this
7365 .code_action_providers
7366 .iter()
7367 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
7368 .collect::<Vec<_>>();
7369 (providers, tasks)
7370 })?;
7371
7372 let mut actions = Vec::new();
7373 for (provider, provider_actions) in
7374 providers.into_iter().zip(future::join_all(tasks).await)
7375 {
7376 if let Some(provider_actions) = provider_actions.log_err() {
7377 actions.extend(provider_actions.into_iter().map(|action| {
7378 AvailableCodeAction {
7379 excerpt_id: newest_selection.start.excerpt_id,
7380 action,
7381 provider: provider.clone(),
7382 }
7383 }));
7384 }
7385 }
7386
7387 this.update(cx, |this, cx| {
7388 this.available_code_actions = if actions.is_empty() {
7389 None
7390 } else {
7391 Some((
7392 Location {
7393 buffer: start_buffer,
7394 range: start..end,
7395 },
7396 actions.into(),
7397 ))
7398 };
7399 cx.notify();
7400 })
7401 }));
7402 }
7403
7404 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7405 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
7406 self.show_git_blame_inline = false;
7407
7408 self.show_git_blame_inline_delay_task =
7409 Some(cx.spawn_in(window, async move |this, cx| {
7410 cx.background_executor().timer(delay).await;
7411
7412 this.update(cx, |this, cx| {
7413 this.show_git_blame_inline = true;
7414 cx.notify();
7415 })
7416 .log_err();
7417 }));
7418 }
7419 }
7420
7421 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
7422 let snapshot = self.snapshot(window, cx);
7423 let cursor = self
7424 .selections
7425 .newest::<Point>(&snapshot.display_snapshot)
7426 .head();
7427 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
7428 else {
7429 return;
7430 };
7431
7432 if self.blame.is_none() {
7433 self.start_git_blame(true, window, cx);
7434 }
7435 let Some(blame) = self.blame.as_ref() else {
7436 return;
7437 };
7438
7439 let row_info = RowInfo {
7440 buffer_id: Some(buffer.remote_id()),
7441 buffer_row: Some(point.row),
7442 ..Default::default()
7443 };
7444 let Some((buffer, blame_entry)) = blame
7445 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
7446 .flatten()
7447 else {
7448 return;
7449 };
7450
7451 let anchor = self.selections.newest_anchor().head();
7452 let position = self.to_pixel_point(anchor, &snapshot, window, cx);
7453 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
7454 self.show_blame_popover(
7455 buffer,
7456 &blame_entry,
7457 position + last_bounds.origin,
7458 true,
7459 cx,
7460 );
7461 };
7462 }
7463
7464 fn show_blame_popover(
7465 &mut self,
7466 buffer: BufferId,
7467 blame_entry: &BlameEntry,
7468 position: gpui::Point<Pixels>,
7469 ignore_timeout: bool,
7470 cx: &mut Context<Self>,
7471 ) {
7472 if let Some(state) = &mut self.inline_blame_popover {
7473 state.hide_task.take();
7474 } else {
7475 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
7476 let blame_entry = blame_entry.clone();
7477 let show_task = cx.spawn(async move |editor, cx| {
7478 if !ignore_timeout {
7479 cx.background_executor()
7480 .timer(std::time::Duration::from_millis(blame_popover_delay))
7481 .await;
7482 }
7483 editor
7484 .update(cx, |editor, cx| {
7485 editor.inline_blame_popover_show_task.take();
7486 let Some(blame) = editor.blame.as_ref() else {
7487 return;
7488 };
7489 let blame = blame.read(cx);
7490 let details = blame.details_for_entry(buffer, &blame_entry);
7491 let markdown = cx.new(|cx| {
7492 Markdown::new(
7493 details
7494 .as_ref()
7495 .map(|message| message.message.clone())
7496 .unwrap_or_default(),
7497 None,
7498 None,
7499 cx,
7500 )
7501 });
7502 editor.inline_blame_popover = Some(InlineBlamePopover {
7503 position,
7504 hide_task: None,
7505 popover_bounds: None,
7506 popover_state: InlineBlamePopoverState {
7507 scroll_handle: ScrollHandle::new(),
7508 commit_message: details,
7509 markdown,
7510 },
7511 keyboard_grace: ignore_timeout,
7512 });
7513 cx.notify();
7514 })
7515 .ok();
7516 });
7517 self.inline_blame_popover_show_task = Some(show_task);
7518 }
7519 }
7520
7521 pub fn has_mouse_context_menu(&self) -> bool {
7522 self.mouse_context_menu.is_some()
7523 }
7524
7525 pub fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
7526 self.inline_blame_popover_show_task.take();
7527 if let Some(state) = &mut self.inline_blame_popover {
7528 let hide_task = cx.spawn(async move |editor, cx| {
7529 if !ignore_timeout {
7530 cx.background_executor()
7531 .timer(std::time::Duration::from_millis(100))
7532 .await;
7533 }
7534 editor
7535 .update(cx, |editor, cx| {
7536 editor.inline_blame_popover.take();
7537 cx.notify();
7538 })
7539 .ok();
7540 });
7541 state.hide_task = Some(hide_task);
7542 true
7543 } else {
7544 false
7545 }
7546 }
7547
7548 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
7549 if self.pending_rename.is_some() {
7550 return None;
7551 }
7552
7553 let provider = self.semantics_provider.clone()?;
7554 let buffer = self.buffer.read(cx);
7555 let newest_selection = self.selections.newest_anchor().clone();
7556 let cursor_position = newest_selection.head();
7557 let (cursor_buffer, cursor_buffer_position) =
7558 buffer.text_anchor_for_position(cursor_position, cx)?;
7559 let (tail_buffer, tail_buffer_position) =
7560 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
7561 if cursor_buffer != tail_buffer {
7562 return None;
7563 }
7564
7565 let snapshot = cursor_buffer.read(cx).snapshot();
7566 let word_ranges = cx.background_spawn(async move {
7567 // this might look odd to put on the background thread, but
7568 // `surrounding_word` can be quite expensive as it calls into
7569 // tree-sitter language scopes
7570 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
7571 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
7572 (start_word_range, end_word_range)
7573 });
7574
7575 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
7576 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
7577 let (start_word_range, end_word_range) = word_ranges.await;
7578 if start_word_range != end_word_range {
7579 this.update(cx, |this, cx| {
7580 this.document_highlights_task.take();
7581 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
7582 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
7583 })
7584 .ok();
7585 return;
7586 }
7587 cx.background_executor()
7588 .timer(Duration::from_millis(debounce))
7589 .await;
7590
7591 let highlights = if let Some(highlights) = cx.update(|cx| {
7592 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
7593 }) {
7594 highlights.await.log_err()
7595 } else {
7596 None
7597 };
7598
7599 if let Some(highlights) = highlights {
7600 this.update(cx, |this, cx| {
7601 if this.pending_rename.is_some() {
7602 return;
7603 }
7604
7605 let buffer = this.buffer.read(cx);
7606 if buffer
7607 .text_anchor_for_position(cursor_position, cx)
7608 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
7609 {
7610 return;
7611 }
7612
7613 let cursor_buffer_snapshot = cursor_buffer.read(cx);
7614 let mut write_ranges = Vec::new();
7615 let mut read_ranges = Vec::new();
7616 for highlight in highlights {
7617 let buffer_id = cursor_buffer.read(cx).remote_id();
7618 for (excerpt_id, _, excerpt_range) in
7619 buffer.excerpts_for_buffer(buffer_id, cx)
7620 {
7621 let start = highlight
7622 .range
7623 .start
7624 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
7625 let end = highlight
7626 .range
7627 .end
7628 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
7629 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
7630 continue;
7631 }
7632
7633 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
7634 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7635 write_ranges.push(range);
7636 } else {
7637 read_ranges.push(range);
7638 }
7639 }
7640 }
7641
7642 this.highlight_background(
7643 HighlightKey::DocumentHighlightRead,
7644 &read_ranges,
7645 |_, theme| theme.colors().editor_document_highlight_read_background,
7646 cx,
7647 );
7648 this.highlight_background(
7649 HighlightKey::DocumentHighlightWrite,
7650 &write_ranges,
7651 |_, theme| theme.colors().editor_document_highlight_write_background,
7652 cx,
7653 );
7654 cx.notify();
7655 })
7656 .log_err();
7657 }
7658 }));
7659 None
7660 }
7661
7662 fn prepare_highlight_query_from_selection(
7663 &mut self,
7664 snapshot: &DisplaySnapshot,
7665 cx: &mut Context<Editor>,
7666 ) -> Option<(String, Range<Anchor>)> {
7667 if matches!(self.mode, EditorMode::SingleLine) {
7668 return None;
7669 }
7670 if !EditorSettings::get_global(cx).selection_highlight {
7671 return None;
7672 }
7673 if self.selections.count() != 1 || self.selections.line_mode() {
7674 return None;
7675 }
7676 let selection = self.selections.newest::<Point>(&snapshot);
7677 // If the selection spans multiple rows OR it is empty
7678 if selection.start.row != selection.end.row
7679 || selection.start.column == selection.end.column
7680 {
7681 return None;
7682 }
7683 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7684 let query = snapshot
7685 .buffer_snapshot()
7686 .text_for_range(selection_anchor_range.clone())
7687 .collect::<String>();
7688 if query.trim().is_empty() {
7689 return None;
7690 }
7691 Some((query, selection_anchor_range))
7692 }
7693
7694 #[ztracing::instrument(skip_all)]
7695 fn update_selection_occurrence_highlights(
7696 &mut self,
7697 multi_buffer_snapshot: MultiBufferSnapshot,
7698 query_text: String,
7699 query_range: Range<Anchor>,
7700 multi_buffer_range_to_query: Range<Point>,
7701 use_debounce: bool,
7702 window: &mut Window,
7703 cx: &mut Context<Editor>,
7704 ) -> Task<()> {
7705 cx.spawn_in(window, async move |editor, cx| {
7706 if use_debounce {
7707 cx.background_executor()
7708 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7709 .await;
7710 }
7711 let match_task = cx.background_spawn(async move {
7712 let buffer_ranges = multi_buffer_snapshot
7713 .range_to_buffer_ranges(
7714 multi_buffer_range_to_query.start..=multi_buffer_range_to_query.end,
7715 )
7716 .into_iter()
7717 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7718 let mut match_ranges = Vec::new();
7719 let Ok(regex) = project::search::SearchQuery::text(
7720 query_text,
7721 false,
7722 false,
7723 false,
7724 Default::default(),
7725 Default::default(),
7726 false,
7727 None,
7728 ) else {
7729 return Vec::default();
7730 };
7731 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7732 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7733 match_ranges.extend(
7734 regex
7735 .search(
7736 buffer_snapshot,
7737 Some(search_range.start.0..search_range.end.0),
7738 )
7739 .await
7740 .into_iter()
7741 .filter_map(|match_range| {
7742 let match_start = buffer_snapshot
7743 .anchor_after(search_range.start + match_range.start);
7744 let match_end = buffer_snapshot
7745 .anchor_before(search_range.start + match_range.end);
7746 let match_anchor_range =
7747 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7748 (match_anchor_range != query_range).then_some(match_anchor_range)
7749 }),
7750 );
7751 }
7752 match_ranges
7753 });
7754 let match_ranges = match_task.await;
7755 editor
7756 .update_in(cx, |editor, _, cx| {
7757 if use_debounce {
7758 editor.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
7759 editor.debounced_selection_highlight_complete = true;
7760 } else if editor.debounced_selection_highlight_complete {
7761 return;
7762 }
7763 if !match_ranges.is_empty() {
7764 editor.highlight_background(
7765 HighlightKey::SelectedTextHighlight,
7766 &match_ranges,
7767 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7768 cx,
7769 )
7770 }
7771 })
7772 .log_err();
7773 })
7774 }
7775
7776 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7777 struct NewlineFold;
7778 let type_id = std::any::TypeId::of::<NewlineFold>();
7779 if !self.mode.is_single_line() {
7780 return;
7781 }
7782 let snapshot = self.snapshot(window, cx);
7783 if snapshot.buffer_snapshot().max_point().row == 0 {
7784 return;
7785 }
7786 let task = cx.background_spawn(async move {
7787 let new_newlines = snapshot
7788 .buffer_chars_at(MultiBufferOffset(0))
7789 .filter_map(|(c, i)| {
7790 if c == '\n' {
7791 Some(
7792 snapshot.buffer_snapshot().anchor_after(i)
7793 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7794 )
7795 } else {
7796 None
7797 }
7798 })
7799 .collect::<Vec<_>>();
7800 let existing_newlines = snapshot
7801 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7802 .filter_map(|fold| {
7803 if fold.placeholder.type_tag == Some(type_id) {
7804 Some(fold.range.start..fold.range.end)
7805 } else {
7806 None
7807 }
7808 })
7809 .collect::<Vec<_>>();
7810
7811 (new_newlines, existing_newlines)
7812 });
7813 self.folding_newlines = cx.spawn(async move |this, cx| {
7814 let (new_newlines, existing_newlines) = task.await;
7815 if new_newlines == existing_newlines {
7816 return;
7817 }
7818 let placeholder = FoldPlaceholder {
7819 render: Arc::new(move |_, _, cx| {
7820 div()
7821 .bg(cx.theme().status().hint_background)
7822 .border_b_1()
7823 .size_full()
7824 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7825 .border_color(cx.theme().status().hint)
7826 .child("\\n")
7827 .into_any()
7828 }),
7829 constrain_width: false,
7830 merge_adjacent: false,
7831 type_tag: Some(type_id),
7832 collapsed_text: None,
7833 };
7834 let creases = new_newlines
7835 .into_iter()
7836 .map(|range| Crease::simple(range, placeholder.clone()))
7837 .collect();
7838 this.update(cx, |this, cx| {
7839 this.display_map.update(cx, |display_map, cx| {
7840 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7841 display_map.fold(creases, cx);
7842 });
7843 })
7844 .ok();
7845 });
7846 }
7847
7848 #[ztracing::instrument(skip_all)]
7849 fn refresh_outline_symbols_at_cursor(&mut self, cx: &mut Context<Editor>) {
7850 if !self.lsp_data_enabled() {
7851 return;
7852 }
7853 let cursor = self.selections.newest_anchor().head();
7854 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7855
7856 if self.uses_lsp_document_symbols(cursor, &multi_buffer_snapshot, cx) {
7857 self.outline_symbols_at_cursor =
7858 self.lsp_symbols_at_cursor(cursor, &multi_buffer_snapshot, cx);
7859 cx.emit(EditorEvent::OutlineSymbolsChanged);
7860 cx.notify();
7861 } else {
7862 let syntax = cx.theme().syntax().clone();
7863 let background_task = cx.background_spawn(async move {
7864 multi_buffer_snapshot.symbols_containing(cursor, Some(&syntax))
7865 });
7866 self.refresh_outline_symbols_at_cursor_at_cursor_task =
7867 cx.spawn(async move |this, cx| {
7868 let symbols = background_task.await;
7869 this.update(cx, |this, cx| {
7870 this.outline_symbols_at_cursor = symbols;
7871 cx.emit(EditorEvent::OutlineSymbolsChanged);
7872 cx.notify();
7873 })
7874 .ok();
7875 });
7876 }
7877 }
7878
7879 #[ztracing::instrument(skip_all)]
7880 fn refresh_selected_text_highlights(
7881 &mut self,
7882 snapshot: &DisplaySnapshot,
7883 on_buffer_edit: bool,
7884 window: &mut Window,
7885 cx: &mut Context<Editor>,
7886 ) {
7887 let Some((query_text, query_range)) =
7888 self.prepare_highlight_query_from_selection(snapshot, cx)
7889 else {
7890 self.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
7891 self.quick_selection_highlight_task.take();
7892 self.debounced_selection_highlight_task.take();
7893 self.debounced_selection_highlight_complete = false;
7894 return;
7895 };
7896 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7897 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7898 let query_changed = self
7899 .quick_selection_highlight_task
7900 .as_ref()
7901 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range);
7902 if query_changed {
7903 self.debounced_selection_highlight_complete = false;
7904 }
7905 if on_buffer_edit || query_changed {
7906 self.quick_selection_highlight_task = Some((
7907 query_range.clone(),
7908 self.update_selection_occurrence_highlights(
7909 snapshot.buffer.clone(),
7910 query_text.clone(),
7911 query_range.clone(),
7912 self.multi_buffer_visible_range(&display_snapshot, cx),
7913 false,
7914 window,
7915 cx,
7916 ),
7917 ));
7918 }
7919 if on_buffer_edit
7920 || self
7921 .debounced_selection_highlight_task
7922 .as_ref()
7923 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7924 {
7925 let multi_buffer_start = multi_buffer_snapshot
7926 .anchor_before(MultiBufferOffset(0))
7927 .to_point(&multi_buffer_snapshot);
7928 let multi_buffer_end = multi_buffer_snapshot
7929 .anchor_after(multi_buffer_snapshot.len())
7930 .to_point(&multi_buffer_snapshot);
7931 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7932 self.debounced_selection_highlight_task = Some((
7933 query_range.clone(),
7934 self.update_selection_occurrence_highlights(
7935 snapshot.buffer.clone(),
7936 query_text,
7937 query_range,
7938 multi_buffer_full_range,
7939 true,
7940 window,
7941 cx,
7942 ),
7943 ));
7944 }
7945 }
7946
7947 pub fn multi_buffer_visible_range(
7948 &self,
7949 display_snapshot: &DisplaySnapshot,
7950 cx: &App,
7951 ) -> Range<Point> {
7952 let visible_start = self
7953 .scroll_manager
7954 .native_anchor(display_snapshot, cx)
7955 .anchor
7956 .to_point(display_snapshot.buffer_snapshot())
7957 .to_display_point(display_snapshot);
7958
7959 let mut target_end = visible_start;
7960 *target_end.row_mut() += self.visible_line_count().unwrap_or(0.).ceil() as u32;
7961
7962 visible_start.to_point(display_snapshot)
7963 ..display_snapshot
7964 .clip_point(target_end, Bias::Right)
7965 .to_point(display_snapshot)
7966 }
7967
7968 pub fn refresh_edit_prediction(
7969 &mut self,
7970 debounce: bool,
7971 user_requested: bool,
7972 window: &mut Window,
7973 cx: &mut Context<Self>,
7974 ) -> Option<()> {
7975 if self.leader_id.is_some() {
7976 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7977 return None;
7978 }
7979
7980 let cursor = self.selections.newest_anchor().head();
7981 let (buffer, cursor_buffer_position) =
7982 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7983
7984 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
7985 return None;
7986 }
7987
7988 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7989 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7990 return None;
7991 }
7992
7993 self.update_visible_edit_prediction(window, cx);
7994
7995 if !user_requested
7996 && (!self.should_show_edit_predictions()
7997 || !self.is_focused(window)
7998 || buffer.read(cx).is_empty())
7999 {
8000 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8001 return None;
8002 }
8003
8004 self.edit_prediction_provider()?
8005 .refresh(buffer, cursor_buffer_position, debounce, cx);
8006 Some(())
8007 }
8008
8009 fn show_edit_predictions_in_menu(&self) -> bool {
8010 match self.edit_prediction_settings {
8011 EditPredictionSettings::Disabled => false,
8012 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
8013 }
8014 }
8015
8016 pub fn edit_predictions_enabled(&self) -> bool {
8017 match self.edit_prediction_settings {
8018 EditPredictionSettings::Disabled => false,
8019 EditPredictionSettings::Enabled { .. } => true,
8020 }
8021 }
8022
8023 fn edit_prediction_requires_modifier(&self) -> bool {
8024 match self.edit_prediction_settings {
8025 EditPredictionSettings::Disabled => false,
8026 EditPredictionSettings::Enabled {
8027 preview_requires_modifier,
8028 ..
8029 } => preview_requires_modifier,
8030 }
8031 }
8032
8033 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
8034 if self.edit_prediction_provider.is_none() {
8035 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8036 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8037 return;
8038 }
8039
8040 let selection = self.selections.newest_anchor();
8041 let cursor = selection.head();
8042
8043 if let Some((buffer, cursor_buffer_position)) =
8044 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
8045 {
8046 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
8047 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8048 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8049 return;
8050 }
8051 self.edit_prediction_settings =
8052 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8053 }
8054 }
8055
8056 fn edit_prediction_settings_at_position(
8057 &self,
8058 buffer: &Entity<Buffer>,
8059 buffer_position: language::Anchor,
8060 cx: &App,
8061 ) -> EditPredictionSettings {
8062 if !self.mode.is_full()
8063 || !self.show_edit_predictions_override.unwrap_or(true)
8064 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
8065 {
8066 return EditPredictionSettings::Disabled;
8067 }
8068
8069 let buffer = buffer.read(cx);
8070
8071 let file = buffer.file();
8072
8073 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
8074 return EditPredictionSettings::Disabled;
8075 };
8076
8077 let by_provider = matches!(
8078 self.menu_edit_predictions_policy,
8079 MenuEditPredictionsPolicy::ByProvider
8080 );
8081
8082 let show_in_menu = by_provider
8083 && self
8084 .edit_prediction_provider
8085 .as_ref()
8086 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
8087
8088 let preview_requires_modifier =
8089 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
8090
8091 EditPredictionSettings::Enabled {
8092 show_in_menu,
8093 preview_requires_modifier,
8094 }
8095 }
8096
8097 fn should_show_edit_predictions(&self) -> bool {
8098 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
8099 }
8100
8101 pub fn edit_prediction_preview_is_active(&self) -> bool {
8102 matches!(
8103 self.edit_prediction_preview,
8104 EditPredictionPreview::Active { .. }
8105 )
8106 }
8107
8108 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
8109 let cursor = self.selections.newest_anchor().head();
8110 if let Some((buffer, cursor_position)) =
8111 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
8112 {
8113 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
8114 } else {
8115 false
8116 }
8117 }
8118
8119 pub fn supports_minimap(&self, cx: &App) -> bool {
8120 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
8121 }
8122
8123 fn edit_predictions_enabled_in_buffer(
8124 &self,
8125 buffer: &Entity<Buffer>,
8126 buffer_position: language::Anchor,
8127 cx: &App,
8128 ) -> bool {
8129 maybe!({
8130 if self.read_only(cx) || self.leader_id.is_some() {
8131 return Some(false);
8132 }
8133 let provider = self.edit_prediction_provider()?;
8134 if !provider.is_enabled(buffer, buffer_position, cx) {
8135 return Some(false);
8136 }
8137 let buffer = buffer.read(cx);
8138 let Some(file) = buffer.file() else {
8139 return Some(true);
8140 };
8141 let settings = all_language_settings(Some(file), cx);
8142 Some(settings.edit_predictions_enabled_for_file(file, cx))
8143 })
8144 .unwrap_or(false)
8145 }
8146
8147 pub fn show_edit_prediction(
8148 &mut self,
8149 _: &ShowEditPrediction,
8150 window: &mut Window,
8151 cx: &mut Context<Self>,
8152 ) {
8153 if !self.has_active_edit_prediction() {
8154 self.refresh_edit_prediction(false, true, window, cx);
8155 return;
8156 }
8157
8158 self.update_visible_edit_prediction(window, cx);
8159 }
8160
8161 pub fn display_cursor_names(
8162 &mut self,
8163 _: &DisplayCursorNames,
8164 window: &mut Window,
8165 cx: &mut Context<Self>,
8166 ) {
8167 self.show_cursor_names(window, cx);
8168 }
8169
8170 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8171 self.show_cursor_names = true;
8172 cx.notify();
8173 cx.spawn_in(window, async move |this, cx| {
8174 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
8175 this.update(cx, |this, cx| {
8176 this.show_cursor_names = false;
8177 cx.notify()
8178 })
8179 .ok()
8180 })
8181 .detach();
8182 }
8183
8184 pub fn accept_partial_edit_prediction(
8185 &mut self,
8186 granularity: EditPredictionGranularity,
8187 window: &mut Window,
8188 cx: &mut Context<Self>,
8189 ) {
8190 if self.show_edit_predictions_in_menu() {
8191 self.hide_context_menu(window, cx);
8192 }
8193
8194 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
8195 return;
8196 };
8197
8198 if !matches!(granularity, EditPredictionGranularity::Full) && self.selections.count() != 1 {
8199 return;
8200 }
8201
8202 match &active_edit_prediction.completion {
8203 EditPrediction::MoveWithin { target, .. } => {
8204 let target = *target;
8205
8206 if matches!(granularity, EditPredictionGranularity::Full) {
8207 if let Some(position_map) = &self.last_position_map {
8208 let target_row = target.to_display_point(&position_map.snapshot).row();
8209 let is_visible = position_map.visible_row_range.contains(&target_row);
8210
8211 if is_visible || !self.edit_prediction_requires_modifier() {
8212 self.unfold_ranges(&[target..target], true, false, cx);
8213 self.change_selections(
8214 SelectionEffects::scroll(Autoscroll::newest()),
8215 window,
8216 cx,
8217 |selections| {
8218 selections.select_anchor_ranges([target..target]);
8219 },
8220 );
8221 self.clear_row_highlights::<EditPredictionPreview>();
8222 self.edit_prediction_preview
8223 .set_previous_scroll_position(None);
8224 } else {
8225 // Highlight and request scroll
8226 self.edit_prediction_preview
8227 .set_previous_scroll_position(Some(
8228 position_map.snapshot.scroll_anchor,
8229 ));
8230 self.highlight_rows::<EditPredictionPreview>(
8231 target..target,
8232 cx.theme().colors().editor_highlighted_line_background,
8233 RowHighlightOptions {
8234 autoscroll: true,
8235 ..Default::default()
8236 },
8237 cx,
8238 );
8239 self.request_autoscroll(Autoscroll::fit(), cx);
8240 }
8241 }
8242 } else {
8243 self.change_selections(
8244 SelectionEffects::scroll(Autoscroll::newest()),
8245 window,
8246 cx,
8247 |selections| {
8248 selections.select_anchor_ranges([target..target]);
8249 },
8250 );
8251 }
8252 }
8253 EditPrediction::MoveOutside { snapshot, target } => {
8254 if let Some(workspace) = self.workspace() {
8255 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
8256 .detach_and_log_err(cx);
8257 }
8258 }
8259 EditPrediction::Edit {
8260 edits,
8261 cursor_position,
8262 ..
8263 } => {
8264 self.report_edit_prediction_event(
8265 active_edit_prediction.completion_id.clone(),
8266 true,
8267 cx,
8268 );
8269
8270 match granularity {
8271 EditPredictionGranularity::Full => {
8272 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
8273
8274 // Compute fallback cursor position BEFORE applying the edit,
8275 // so the anchor tracks through the edit correctly
8276 let fallback_cursor_target = {
8277 let snapshot = self.buffer.read(cx).snapshot(cx);
8278 edits.last().unwrap().0.end.bias_right(&snapshot)
8279 };
8280
8281 self.buffer.update(cx, |buffer, cx| {
8282 buffer.edit(edits.iter().cloned(), None, cx)
8283 });
8284
8285 if let Some(provider) = self.edit_prediction_provider() {
8286 provider.accept(cx);
8287 }
8288
8289 // Resolve cursor position after the edit is applied
8290 let cursor_target = if let Some((anchor, offset)) = cursor_position {
8291 // The anchor tracks through the edit, then we add the offset
8292 let snapshot = self.buffer.read(cx).snapshot(cx);
8293 let base_offset = anchor.to_offset(&snapshot).0;
8294 let target_offset =
8295 MultiBufferOffset((base_offset + offset).min(snapshot.len().0));
8296 snapshot.anchor_after(target_offset)
8297 } else {
8298 fallback_cursor_target
8299 };
8300
8301 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
8302 s.select_anchor_ranges([cursor_target..cursor_target]);
8303 });
8304
8305 let selections = self.selections.disjoint_anchors_arc();
8306 if let Some(transaction_id_now) =
8307 self.buffer.read(cx).last_transaction_id(cx)
8308 {
8309 if transaction_id_prev != Some(transaction_id_now) {
8310 self.selection_history
8311 .insert_transaction(transaction_id_now, selections);
8312 }
8313 }
8314
8315 self.update_visible_edit_prediction(window, cx);
8316 if self.active_edit_prediction.is_none() {
8317 self.refresh_edit_prediction(true, true, window, cx);
8318 }
8319 cx.notify();
8320 }
8321 _ => {
8322 let snapshot = self.buffer.read(cx).snapshot(cx);
8323 let cursor_offset = self
8324 .selections
8325 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8326 .head();
8327
8328 let insertion = edits.iter().find_map(|(range, text)| {
8329 let range = range.to_offset(&snapshot);
8330 if range.is_empty() && range.start == cursor_offset {
8331 Some(text)
8332 } else {
8333 None
8334 }
8335 });
8336
8337 if let Some(text) = insertion {
8338 let text_to_insert = match granularity {
8339 EditPredictionGranularity::Word => {
8340 let mut partial = text
8341 .chars()
8342 .by_ref()
8343 .take_while(|c| c.is_alphabetic())
8344 .collect::<String>();
8345 if partial.is_empty() {
8346 partial = text
8347 .chars()
8348 .by_ref()
8349 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
8350 .collect::<String>();
8351 }
8352 partial
8353 }
8354 EditPredictionGranularity::Line => {
8355 if let Some(line) = text.split_inclusive('\n').next() {
8356 line.to_string()
8357 } else {
8358 text.to_string()
8359 }
8360 }
8361 EditPredictionGranularity::Full => unreachable!(),
8362 };
8363
8364 cx.emit(EditorEvent::InputHandled {
8365 utf16_range_to_replace: None,
8366 text: text_to_insert.clone().into(),
8367 });
8368
8369 self.replace_selections(&text_to_insert, None, window, cx, false);
8370 self.refresh_edit_prediction(true, true, window, cx);
8371 cx.notify();
8372 } else {
8373 self.accept_partial_edit_prediction(
8374 EditPredictionGranularity::Full,
8375 window,
8376 cx,
8377 );
8378 }
8379 }
8380 }
8381 }
8382 }
8383 }
8384
8385 pub fn accept_next_word_edit_prediction(
8386 &mut self,
8387 _: &AcceptNextWordEditPrediction,
8388 window: &mut Window,
8389 cx: &mut Context<Self>,
8390 ) {
8391 self.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
8392 }
8393
8394 pub fn accept_next_line_edit_prediction(
8395 &mut self,
8396 _: &AcceptNextLineEditPrediction,
8397 window: &mut Window,
8398 cx: &mut Context<Self>,
8399 ) {
8400 self.accept_partial_edit_prediction(EditPredictionGranularity::Line, window, cx);
8401 }
8402
8403 pub fn accept_edit_prediction(
8404 &mut self,
8405 _: &AcceptEditPrediction,
8406 window: &mut Window,
8407 cx: &mut Context<Self>,
8408 ) {
8409 self.accept_partial_edit_prediction(EditPredictionGranularity::Full, window, cx);
8410 }
8411
8412 fn discard_edit_prediction(
8413 &mut self,
8414 reason: EditPredictionDiscardReason,
8415 cx: &mut Context<Self>,
8416 ) -> bool {
8417 if reason == EditPredictionDiscardReason::Rejected {
8418 let completion_id = self
8419 .active_edit_prediction
8420 .as_ref()
8421 .and_then(|active_completion| active_completion.completion_id.clone());
8422
8423 self.report_edit_prediction_event(completion_id, false, cx);
8424 }
8425
8426 if let Some(provider) = self.edit_prediction_provider() {
8427 provider.discard(reason, cx);
8428 }
8429
8430 self.take_active_edit_prediction(cx)
8431 }
8432
8433 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
8434 let Some(provider) = self.edit_prediction_provider() else {
8435 return;
8436 };
8437
8438 let Some((_, buffer, _)) = self
8439 .buffer
8440 .read(cx)
8441 .excerpt_containing(self.selections.newest_anchor().head(), cx)
8442 else {
8443 return;
8444 };
8445
8446 let extension = buffer
8447 .read(cx)
8448 .file()
8449 .and_then(|file| Some(file.path().extension()?.to_string()));
8450
8451 let event_type = match accepted {
8452 true => "Edit Prediction Accepted",
8453 false => "Edit Prediction Discarded",
8454 };
8455 telemetry::event!(
8456 event_type,
8457 provider = provider.name(),
8458 prediction_id = id,
8459 suggestion_accepted = accepted,
8460 file_extension = extension,
8461 );
8462 }
8463
8464 fn open_editor_at_anchor(
8465 snapshot: &language::BufferSnapshot,
8466 target: language::Anchor,
8467 workspace: &Entity<Workspace>,
8468 window: &mut Window,
8469 cx: &mut App,
8470 ) -> Task<Result<()>> {
8471 workspace.update(cx, |workspace, cx| {
8472 let path = snapshot.file().map(|file| file.full_path(cx));
8473 let Some(path) =
8474 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
8475 else {
8476 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
8477 };
8478 let target = text::ToPoint::to_point(&target, snapshot);
8479 let item = workspace.open_path(path, None, true, window, cx);
8480 window.spawn(cx, async move |cx| {
8481 let Some(editor) = item.await?.downcast::<Editor>() else {
8482 return Ok(());
8483 };
8484 editor
8485 .update_in(cx, |editor, window, cx| {
8486 editor.go_to_singleton_buffer_point(target, window, cx);
8487 })
8488 .ok();
8489 anyhow::Ok(())
8490 })
8491 })
8492 }
8493
8494 pub fn has_active_edit_prediction(&self) -> bool {
8495 self.active_edit_prediction.is_some()
8496 }
8497
8498 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
8499 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
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 = Some(active_edit_prediction);
8506 true
8507 }
8508
8509 /// Returns true when we're displaying the edit prediction popover below the cursor
8510 /// like we are not previewing and the LSP autocomplete menu is visible
8511 /// or we are in `when_holding_modifier` mode.
8512 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
8513 if self.edit_prediction_preview_is_active()
8514 || !self.show_edit_predictions_in_menu()
8515 || !self.edit_predictions_enabled()
8516 {
8517 return false;
8518 }
8519
8520 if self.has_visible_completions_menu() {
8521 return true;
8522 }
8523
8524 has_completion && self.edit_prediction_requires_modifier()
8525 }
8526
8527 fn handle_modifiers_changed(
8528 &mut self,
8529 modifiers: Modifiers,
8530 position_map: &PositionMap,
8531 window: &mut Window,
8532 cx: &mut Context<Self>,
8533 ) {
8534 self.update_edit_prediction_settings(cx);
8535
8536 // Ensure that the edit prediction preview is updated, even when not
8537 // enabled, if there's an active edit prediction preview.
8538 if self.show_edit_predictions_in_menu()
8539 || self.edit_prediction_requires_modifier()
8540 || matches!(
8541 self.edit_prediction_preview,
8542 EditPredictionPreview::Active { .. }
8543 )
8544 {
8545 self.update_edit_prediction_preview(&modifiers, window, cx);
8546 }
8547
8548 self.update_selection_mode(&modifiers, position_map, window, cx);
8549
8550 let mouse_position = window.mouse_position();
8551 if !position_map.text_hitbox.is_hovered(window) {
8552 return;
8553 }
8554
8555 self.update_hovered_link(
8556 position_map.point_for_position(mouse_position),
8557 Some(mouse_position),
8558 &position_map.snapshot,
8559 modifiers,
8560 window,
8561 cx,
8562 )
8563 }
8564
8565 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8566 match EditorSettings::get_global(cx).multi_cursor_modifier {
8567 MultiCursorModifier::Alt => modifiers.secondary(),
8568 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
8569 }
8570 }
8571
8572 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8573 match EditorSettings::get_global(cx).multi_cursor_modifier {
8574 MultiCursorModifier::Alt => modifiers.alt,
8575 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
8576 }
8577 }
8578
8579 fn columnar_selection_mode(
8580 modifiers: &Modifiers,
8581 cx: &mut Context<Self>,
8582 ) -> Option<ColumnarMode> {
8583 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
8584 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
8585 Some(ColumnarMode::FromMouse)
8586 } else if Self::is_alt_pressed(modifiers, cx) {
8587 Some(ColumnarMode::FromSelection)
8588 } else {
8589 None
8590 }
8591 } else {
8592 None
8593 }
8594 }
8595
8596 fn update_selection_mode(
8597 &mut self,
8598 modifiers: &Modifiers,
8599 position_map: &PositionMap,
8600 window: &mut Window,
8601 cx: &mut Context<Self>,
8602 ) {
8603 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
8604 return;
8605 };
8606 if self.selections.pending_anchor().is_none() {
8607 return;
8608 }
8609
8610 let mouse_position = window.mouse_position();
8611 let point_for_position = position_map.point_for_position(mouse_position);
8612 let position = point_for_position.previous_valid;
8613
8614 self.select(
8615 SelectPhase::BeginColumnar {
8616 position,
8617 reset: false,
8618 mode,
8619 goal_column: point_for_position.exact_unclipped.column(),
8620 },
8621 window,
8622 cx,
8623 );
8624 }
8625
8626 fn update_edit_prediction_preview(
8627 &mut self,
8628 modifiers: &Modifiers,
8629 window: &mut Window,
8630 cx: &mut Context<Self>,
8631 ) {
8632 let modifiers_held = self.edit_prediction_preview_modifiers_held(modifiers, window, cx);
8633
8634 if modifiers_held {
8635 if matches!(
8636 self.edit_prediction_preview,
8637 EditPredictionPreview::Inactive { .. }
8638 ) {
8639 self.edit_prediction_preview = EditPredictionPreview::Active {
8640 previous_scroll_position: None,
8641 since: Instant::now(),
8642 };
8643
8644 self.update_visible_edit_prediction(window, cx);
8645 cx.notify();
8646 }
8647 } else if let EditPredictionPreview::Active {
8648 previous_scroll_position,
8649 since,
8650 } = self.edit_prediction_preview
8651 {
8652 if let (Some(previous_scroll_position), Some(position_map)) =
8653 (previous_scroll_position, self.last_position_map.as_ref())
8654 {
8655 self.set_scroll_position(
8656 previous_scroll_position
8657 .scroll_position(&position_map.snapshot.display_snapshot),
8658 window,
8659 cx,
8660 );
8661 }
8662
8663 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8664 released_too_fast: since.elapsed() < Duration::from_millis(200),
8665 };
8666 self.clear_row_highlights::<EditPredictionPreview>();
8667 self.update_visible_edit_prediction(window, cx);
8668 cx.notify();
8669 }
8670 }
8671
8672 fn update_visible_edit_prediction(
8673 &mut self,
8674 _window: &mut Window,
8675 cx: &mut Context<Self>,
8676 ) -> Option<()> {
8677 if self.ime_transaction.is_some() {
8678 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8679 return None;
8680 }
8681
8682 let selection = self.selections.newest_anchor();
8683 let cursor = selection.head();
8684 let multibuffer = self.buffer.read(cx).snapshot(cx);
8685
8686 // Check project-level disable_ai setting for the current buffer
8687 if let Some((buffer, _)) = self.buffer.read(cx).text_anchor_for_position(cursor, cx) {
8688 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
8689 return None;
8690 }
8691 }
8692 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8693 let excerpt_id = cursor.excerpt_id;
8694
8695 let show_in_menu = self.show_edit_predictions_in_menu();
8696 let completions_menu_has_precedence = !show_in_menu
8697 && (self.context_menu.borrow().is_some()
8698 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8699
8700 if completions_menu_has_precedence
8701 || !offset_selection.is_empty()
8702 || self
8703 .active_edit_prediction
8704 .as_ref()
8705 .is_some_and(|completion| {
8706 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8707 return false;
8708 };
8709 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8710 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8711 !invalidation_range.contains(&offset_selection.head())
8712 })
8713 {
8714 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8715 return None;
8716 }
8717
8718 self.take_active_edit_prediction(cx);
8719 let Some(provider) = self.edit_prediction_provider() else {
8720 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8721 return None;
8722 };
8723
8724 let (buffer, cursor_buffer_position) =
8725 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8726
8727 self.edit_prediction_settings =
8728 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8729
8730 self.in_leading_whitespace = multibuffer.is_line_whitespace_upto(cursor);
8731
8732 if self.in_leading_whitespace {
8733 let cursor_point = cursor.to_point(&multibuffer);
8734 let mut suggested_indent = None;
8735 multibuffer.suggested_indents_callback(
8736 cursor_point.row..cursor_point.row + 1,
8737 &mut |_, indent| {
8738 suggested_indent = Some(indent);
8739 ControlFlow::Break(())
8740 },
8741 cx,
8742 );
8743
8744 if let Some(indent) = suggested_indent
8745 && indent.len == cursor_point.column
8746 {
8747 self.in_leading_whitespace = false;
8748 }
8749 }
8750
8751 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8752
8753 let (completion_id, edits, predicted_cursor_position, edit_preview) = match edit_prediction
8754 {
8755 edit_prediction_types::EditPrediction::Local {
8756 id,
8757 edits,
8758 cursor_position,
8759 edit_preview,
8760 } => (id, edits, cursor_position, edit_preview),
8761 edit_prediction_types::EditPrediction::Jump {
8762 id,
8763 snapshot,
8764 target,
8765 } => {
8766 if let Some(provider) = &self.edit_prediction_provider {
8767 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8768 }
8769 self.stale_edit_prediction_in_menu = None;
8770 self.active_edit_prediction = Some(EditPredictionState {
8771 inlay_ids: vec![],
8772 completion: EditPrediction::MoveOutside { snapshot, target },
8773 completion_id: id,
8774 invalidation_range: None,
8775 });
8776 cx.notify();
8777 return Some(());
8778 }
8779 };
8780
8781 let edits = edits
8782 .into_iter()
8783 .flat_map(|(range, new_text)| {
8784 Some((
8785 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8786 new_text,
8787 ))
8788 })
8789 .collect::<Vec<_>>();
8790 if edits.is_empty() {
8791 return None;
8792 }
8793
8794 let cursor_position = predicted_cursor_position.and_then(|predicted| {
8795 let anchor = multibuffer.anchor_in_excerpt(excerpt_id, predicted.anchor)?;
8796 Some((anchor, predicted.offset))
8797 });
8798
8799 let first_edit_start = edits.first().unwrap().0.start;
8800 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8801 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8802
8803 let last_edit_end = edits.last().unwrap().0.end;
8804 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8805 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8806
8807 let cursor_row = cursor.to_point(&multibuffer).row;
8808
8809 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8810
8811 let mut inlay_ids = Vec::new();
8812 let invalidation_row_range;
8813 let move_invalidation_row_range = if cursor_row < edit_start_row {
8814 Some(cursor_row..edit_end_row)
8815 } else if cursor_row > edit_end_row {
8816 Some(edit_start_row..cursor_row)
8817 } else {
8818 None
8819 };
8820 let supports_jump = self
8821 .edit_prediction_provider
8822 .as_ref()
8823 .map(|provider| provider.provider.supports_jump_to_edit())
8824 .unwrap_or(true);
8825
8826 let is_move = supports_jump
8827 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8828 let completion = if is_move {
8829 if let Some(provider) = &self.edit_prediction_provider {
8830 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8831 }
8832 invalidation_row_range =
8833 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8834 let target = first_edit_start;
8835 EditPrediction::MoveWithin { target, snapshot }
8836 } else {
8837 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8838 && !self.edit_predictions_hidden_for_vim_mode;
8839
8840 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8841 if provider.show_tab_accept_marker() {
8842 EditDisplayMode::TabAccept
8843 } else {
8844 EditDisplayMode::Inline
8845 }
8846 } else {
8847 EditDisplayMode::DiffPopover
8848 };
8849
8850 if show_completions_in_buffer {
8851 if let Some(provider) = &self.edit_prediction_provider {
8852 let suggestion_display_type = match display_mode {
8853 EditDisplayMode::DiffPopover => SuggestionDisplayType::DiffPopover,
8854 EditDisplayMode::Inline | EditDisplayMode::TabAccept => {
8855 SuggestionDisplayType::GhostText
8856 }
8857 };
8858 provider.provider.did_show(suggestion_display_type, cx);
8859 }
8860 if edits
8861 .iter()
8862 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8863 {
8864 let mut inlays = Vec::new();
8865 for (range, new_text) in &edits {
8866 let inlay = Inlay::edit_prediction(
8867 post_inc(&mut self.next_inlay_id),
8868 range.start,
8869 new_text.as_ref(),
8870 );
8871 inlay_ids.push(inlay.id);
8872 inlays.push(inlay);
8873 }
8874
8875 self.splice_inlays(&[], inlays, cx);
8876 } else {
8877 let background_color = cx.theme().status().deleted_background;
8878 self.highlight_text(
8879 HighlightKey::EditPredictionHighlight,
8880 edits.iter().map(|(range, _)| range.clone()).collect(),
8881 HighlightStyle {
8882 background_color: Some(background_color),
8883 ..Default::default()
8884 },
8885 cx,
8886 );
8887 }
8888 }
8889
8890 invalidation_row_range = edit_start_row..edit_end_row;
8891
8892 EditPrediction::Edit {
8893 edits,
8894 cursor_position,
8895 edit_preview,
8896 display_mode,
8897 snapshot,
8898 }
8899 };
8900
8901 let invalidation_range = multibuffer
8902 .anchor_before(Point::new(invalidation_row_range.start, 0))
8903 ..multibuffer.anchor_after(Point::new(
8904 invalidation_row_range.end,
8905 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8906 ));
8907
8908 self.stale_edit_prediction_in_menu = None;
8909 self.active_edit_prediction = Some(EditPredictionState {
8910 inlay_ids,
8911 completion,
8912 completion_id,
8913 invalidation_range: Some(invalidation_range),
8914 });
8915
8916 cx.notify();
8917
8918 Some(())
8919 }
8920
8921 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8922 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8923 }
8924
8925 /// Get all display points of breakpoints that will be rendered within editor
8926 ///
8927 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8928 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8929 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8930 fn active_breakpoints(
8931 &self,
8932 range: Range<DisplayRow>,
8933 window: &mut Window,
8934 cx: &mut Context<Self>,
8935 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8936 let mut breakpoint_display_points = HashMap::default();
8937
8938 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8939 return breakpoint_display_points;
8940 };
8941
8942 let snapshot = self.snapshot(window, cx);
8943
8944 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8945 let Some(project) = self.project() else {
8946 return breakpoint_display_points;
8947 };
8948
8949 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8950 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8951
8952 for (buffer_snapshot, range, excerpt_id) in
8953 multi_buffer_snapshot.range_to_buffer_ranges(range.start..=range.end)
8954 {
8955 let Some(buffer) = project
8956 .read(cx)
8957 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8958 else {
8959 continue;
8960 };
8961 let breakpoints = breakpoint_store.read(cx).breakpoints(
8962 &buffer,
8963 Some(
8964 buffer_snapshot.anchor_before(range.start)
8965 ..buffer_snapshot.anchor_after(range.end),
8966 ),
8967 buffer_snapshot,
8968 cx,
8969 );
8970 for (breakpoint, state) in breakpoints {
8971 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8972 let position = multi_buffer_anchor
8973 .to_point(&multi_buffer_snapshot)
8974 .to_display_point(&snapshot);
8975
8976 breakpoint_display_points.insert(
8977 position.row(),
8978 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8979 );
8980 }
8981 }
8982
8983 breakpoint_display_points
8984 }
8985
8986 fn breakpoint_context_menu(
8987 &self,
8988 anchor: Anchor,
8989 window: &mut Window,
8990 cx: &mut Context<Self>,
8991 ) -> Entity<ui::ContextMenu> {
8992 let weak_editor = cx.weak_entity();
8993 let focus_handle = self.focus_handle(cx);
8994
8995 let row = self
8996 .buffer
8997 .read(cx)
8998 .snapshot(cx)
8999 .summary_for_anchor::<Point>(&anchor)
9000 .row;
9001
9002 let breakpoint = self
9003 .breakpoint_at_row(row, window, cx)
9004 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
9005
9006 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
9007 "Edit Log Breakpoint"
9008 } else {
9009 "Set Log Breakpoint"
9010 };
9011
9012 let condition_breakpoint_msg = if breakpoint
9013 .as_ref()
9014 .is_some_and(|bp| bp.1.condition.is_some())
9015 {
9016 "Edit Condition Breakpoint"
9017 } else {
9018 "Set Condition Breakpoint"
9019 };
9020
9021 let hit_condition_breakpoint_msg = if breakpoint
9022 .as_ref()
9023 .is_some_and(|bp| bp.1.hit_condition.is_some())
9024 {
9025 "Edit Hit Condition Breakpoint"
9026 } else {
9027 "Set Hit Condition Breakpoint"
9028 };
9029
9030 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
9031 "Unset Breakpoint"
9032 } else {
9033 "Set Breakpoint"
9034 };
9035
9036 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
9037
9038 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
9039 BreakpointState::Enabled => Some("Disable"),
9040 BreakpointState::Disabled => Some("Enable"),
9041 });
9042
9043 let (anchor, breakpoint) =
9044 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
9045
9046 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
9047 menu.on_blur_subscription(Subscription::new(|| {}))
9048 .context(focus_handle)
9049 .when(run_to_cursor, |this| {
9050 let weak_editor = weak_editor.clone();
9051 this.entry("Run to Cursor", None, move |window, cx| {
9052 weak_editor
9053 .update(cx, |editor, cx| {
9054 editor.change_selections(
9055 SelectionEffects::no_scroll(),
9056 window,
9057 cx,
9058 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
9059 );
9060 })
9061 .ok();
9062
9063 window.dispatch_action(Box::new(RunToCursor), cx);
9064 })
9065 .separator()
9066 })
9067 .when_some(toggle_state_msg, |this, msg| {
9068 this.entry(msg, None, {
9069 let weak_editor = weak_editor.clone();
9070 let breakpoint = breakpoint.clone();
9071 move |_window, cx| {
9072 weak_editor
9073 .update(cx, |this, cx| {
9074 this.edit_breakpoint_at_anchor(
9075 anchor,
9076 breakpoint.as_ref().clone(),
9077 BreakpointEditAction::InvertState,
9078 cx,
9079 );
9080 })
9081 .log_err();
9082 }
9083 })
9084 })
9085 .entry(set_breakpoint_msg, None, {
9086 let weak_editor = weak_editor.clone();
9087 let breakpoint = breakpoint.clone();
9088 move |_window, cx| {
9089 weak_editor
9090 .update(cx, |this, cx| {
9091 this.edit_breakpoint_at_anchor(
9092 anchor,
9093 breakpoint.as_ref().clone(),
9094 BreakpointEditAction::Toggle,
9095 cx,
9096 );
9097 })
9098 .log_err();
9099 }
9100 })
9101 .entry(log_breakpoint_msg, None, {
9102 let breakpoint = breakpoint.clone();
9103 let weak_editor = weak_editor.clone();
9104 move |window, cx| {
9105 weak_editor
9106 .update(cx, |this, cx| {
9107 this.add_edit_breakpoint_block(
9108 anchor,
9109 breakpoint.as_ref(),
9110 BreakpointPromptEditAction::Log,
9111 window,
9112 cx,
9113 );
9114 })
9115 .log_err();
9116 }
9117 })
9118 .entry(condition_breakpoint_msg, None, {
9119 let breakpoint = breakpoint.clone();
9120 let weak_editor = weak_editor.clone();
9121 move |window, cx| {
9122 weak_editor
9123 .update(cx, |this, cx| {
9124 this.add_edit_breakpoint_block(
9125 anchor,
9126 breakpoint.as_ref(),
9127 BreakpointPromptEditAction::Condition,
9128 window,
9129 cx,
9130 );
9131 })
9132 .log_err();
9133 }
9134 })
9135 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
9136 weak_editor
9137 .update(cx, |this, cx| {
9138 this.add_edit_breakpoint_block(
9139 anchor,
9140 breakpoint.as_ref(),
9141 BreakpointPromptEditAction::HitCondition,
9142 window,
9143 cx,
9144 );
9145 })
9146 .log_err();
9147 })
9148 })
9149 }
9150
9151 fn render_breakpoint(
9152 &self,
9153 position: Anchor,
9154 row: DisplayRow,
9155 breakpoint: &Breakpoint,
9156 state: Option<BreakpointSessionState>,
9157 cx: &mut Context<Self>,
9158 ) -> IconButton {
9159 let is_rejected = state.is_some_and(|s| !s.verified);
9160 // Is it a breakpoint that shows up when hovering over gutter?
9161 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
9162 (false, false),
9163 |PhantomBreakpointIndicator {
9164 is_active,
9165 display_row,
9166 collides_with_existing_breakpoint,
9167 }| {
9168 (
9169 is_active && display_row == row,
9170 collides_with_existing_breakpoint,
9171 )
9172 },
9173 );
9174
9175 let (color, icon) = {
9176 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
9177 (false, false) => ui::IconName::DebugBreakpoint,
9178 (true, false) => ui::IconName::DebugLogBreakpoint,
9179 (false, true) => ui::IconName::DebugDisabledBreakpoint,
9180 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
9181 };
9182
9183 let theme_colors = cx.theme().colors();
9184
9185 let color = if is_phantom {
9186 if collides_with_existing {
9187 Color::Custom(
9188 theme_colors
9189 .debugger_accent
9190 .blend(theme_colors.text.opacity(0.6)),
9191 )
9192 } else {
9193 Color::Hint
9194 }
9195 } else if is_rejected {
9196 Color::Disabled
9197 } else {
9198 Color::Debugger
9199 };
9200
9201 (color, icon)
9202 };
9203
9204 let breakpoint = Arc::from(breakpoint.clone());
9205
9206 let alt_as_text = gpui::Keystroke {
9207 modifiers: Modifiers::secondary_key(),
9208 ..Default::default()
9209 };
9210 let primary_action_text = if breakpoint.is_disabled() {
9211 "Enable breakpoint"
9212 } else if is_phantom && !collides_with_existing {
9213 "Set breakpoint"
9214 } else {
9215 "Unset breakpoint"
9216 };
9217 let focus_handle = self.focus_handle.clone();
9218
9219 let meta = if is_rejected {
9220 SharedString::from("No executable code is associated with this line.")
9221 } else if collides_with_existing && !breakpoint.is_disabled() {
9222 SharedString::from(format!(
9223 "{alt_as_text}-click to disable,\nright-click for more options."
9224 ))
9225 } else {
9226 SharedString::from("Right-click for more options.")
9227 };
9228 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
9229 .icon_size(IconSize::XSmall)
9230 .size(ui::ButtonSize::None)
9231 .when(is_rejected, |this| {
9232 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
9233 })
9234 .icon_color(color)
9235 .style(ButtonStyle::Transparent)
9236 .on_click(cx.listener({
9237 move |editor, event: &ClickEvent, window, cx| {
9238 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
9239 BreakpointEditAction::InvertState
9240 } else {
9241 BreakpointEditAction::Toggle
9242 };
9243
9244 window.focus(&editor.focus_handle(cx), cx);
9245 editor.update_breakpoint_collision_on_toggle(row, &edit_action);
9246 editor.edit_breakpoint_at_anchor(
9247 position,
9248 breakpoint.as_ref().clone(),
9249 edit_action,
9250 cx,
9251 );
9252 }
9253 }))
9254 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
9255 editor.set_breakpoint_context_menu(
9256 row,
9257 Some(position),
9258 event.position(),
9259 window,
9260 cx,
9261 );
9262 }))
9263 .tooltip(move |_window, cx| {
9264 Tooltip::with_meta_in(
9265 primary_action_text,
9266 Some(&ToggleBreakpoint),
9267 meta.clone(),
9268 &focus_handle,
9269 cx,
9270 )
9271 })
9272 }
9273
9274 fn build_tasks_context(
9275 project: &Entity<Project>,
9276 buffer: &Entity<Buffer>,
9277 buffer_row: u32,
9278 tasks: &Arc<RunnableTasks>,
9279 cx: &mut Context<Self>,
9280 ) -> Task<Option<task::TaskContext>> {
9281 let position = Point::new(buffer_row, tasks.column);
9282 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
9283 let location = Location {
9284 buffer: buffer.clone(),
9285 range: range_start..range_start,
9286 };
9287 // Fill in the environmental variables from the tree-sitter captures
9288 let mut captured_task_variables = TaskVariables::default();
9289 for (capture_name, value) in tasks.extra_variables.clone() {
9290 captured_task_variables.insert(
9291 task::VariableName::Custom(capture_name.into()),
9292 value.clone(),
9293 );
9294 }
9295 project.update(cx, |project, cx| {
9296 project.task_store().update(cx, |task_store, cx| {
9297 task_store.task_context_for_location(captured_task_variables, location, cx)
9298 })
9299 })
9300 }
9301
9302 pub fn context_menu_visible(&self) -> bool {
9303 !self.edit_prediction_preview_is_active()
9304 && self
9305 .context_menu
9306 .borrow()
9307 .as_ref()
9308 .is_some_and(|menu| menu.visible())
9309 }
9310
9311 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
9312 self.context_menu
9313 .borrow()
9314 .as_ref()
9315 .map(|menu| menu.origin())
9316 }
9317
9318 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
9319 self.context_menu_options = Some(options);
9320 }
9321
9322 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
9323 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
9324
9325 fn render_edit_prediction_popover(
9326 &mut self,
9327 text_bounds: &Bounds<Pixels>,
9328 content_origin: gpui::Point<Pixels>,
9329 right_margin: Pixels,
9330 editor_snapshot: &EditorSnapshot,
9331 visible_row_range: Range<DisplayRow>,
9332 scroll_top: ScrollOffset,
9333 scroll_bottom: ScrollOffset,
9334 line_layouts: &[LineWithInvisibles],
9335 line_height: Pixels,
9336 scroll_position: gpui::Point<ScrollOffset>,
9337 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9338 newest_selection_head: Option<DisplayPoint>,
9339 editor_width: Pixels,
9340 style: &EditorStyle,
9341 window: &mut Window,
9342 cx: &mut App,
9343 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9344 if self.mode().is_minimap() {
9345 return None;
9346 }
9347 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
9348
9349 if self.edit_prediction_visible_in_cursor_popover(true) {
9350 return None;
9351 }
9352
9353 match &active_edit_prediction.completion {
9354 EditPrediction::MoveWithin { target, .. } => {
9355 let target_display_point = target.to_display_point(editor_snapshot);
9356
9357 if self.edit_prediction_requires_modifier() {
9358 if !self.edit_prediction_preview_is_active() {
9359 return None;
9360 }
9361
9362 self.render_edit_prediction_modifier_jump_popover(
9363 text_bounds,
9364 content_origin,
9365 visible_row_range,
9366 line_layouts,
9367 line_height,
9368 scroll_pixel_position,
9369 newest_selection_head,
9370 target_display_point,
9371 window,
9372 cx,
9373 )
9374 } else {
9375 self.render_edit_prediction_eager_jump_popover(
9376 text_bounds,
9377 content_origin,
9378 editor_snapshot,
9379 visible_row_range,
9380 scroll_top,
9381 scroll_bottom,
9382 line_height,
9383 scroll_pixel_position,
9384 target_display_point,
9385 editor_width,
9386 window,
9387 cx,
9388 )
9389 }
9390 }
9391 EditPrediction::Edit {
9392 display_mode: EditDisplayMode::Inline,
9393 ..
9394 } => None,
9395 EditPrediction::Edit {
9396 display_mode: EditDisplayMode::TabAccept,
9397 edits,
9398 ..
9399 } => {
9400 let range = &edits.first()?.0;
9401 let target_display_point = range.end.to_display_point(editor_snapshot);
9402
9403 self.render_edit_prediction_end_of_line_popover(
9404 "Accept",
9405 editor_snapshot,
9406 visible_row_range,
9407 target_display_point,
9408 line_height,
9409 scroll_pixel_position,
9410 content_origin,
9411 editor_width,
9412 window,
9413 cx,
9414 )
9415 }
9416 EditPrediction::Edit {
9417 edits,
9418 edit_preview,
9419 display_mode: EditDisplayMode::DiffPopover,
9420 snapshot,
9421 ..
9422 } => self.render_edit_prediction_diff_popover(
9423 text_bounds,
9424 content_origin,
9425 right_margin,
9426 editor_snapshot,
9427 visible_row_range,
9428 line_layouts,
9429 line_height,
9430 scroll_position,
9431 scroll_pixel_position,
9432 newest_selection_head,
9433 editor_width,
9434 style,
9435 edits,
9436 edit_preview,
9437 snapshot,
9438 window,
9439 cx,
9440 ),
9441 EditPrediction::MoveOutside { snapshot, .. } => {
9442 let mut element = self
9443 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
9444 .into_any();
9445
9446 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9447 let origin_x = text_bounds.size.width - size.width - px(30.);
9448 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
9449 element.prepaint_at(origin, window, cx);
9450
9451 Some((element, origin))
9452 }
9453 }
9454 }
9455
9456 fn render_edit_prediction_modifier_jump_popover(
9457 &mut self,
9458 text_bounds: &Bounds<Pixels>,
9459 content_origin: gpui::Point<Pixels>,
9460 visible_row_range: Range<DisplayRow>,
9461 line_layouts: &[LineWithInvisibles],
9462 line_height: Pixels,
9463 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9464 newest_selection_head: Option<DisplayPoint>,
9465 target_display_point: DisplayPoint,
9466 window: &mut Window,
9467 cx: &mut App,
9468 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9469 let scrolled_content_origin =
9470 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
9471
9472 const SCROLL_PADDING_Y: Pixels = px(12.);
9473
9474 if target_display_point.row() < visible_row_range.start {
9475 return self.render_edit_prediction_scroll_popover(
9476 &|_| SCROLL_PADDING_Y,
9477 IconName::ArrowUp,
9478 visible_row_range,
9479 line_layouts,
9480 newest_selection_head,
9481 scrolled_content_origin,
9482 window,
9483 cx,
9484 );
9485 } else if target_display_point.row() >= visible_row_range.end {
9486 return self.render_edit_prediction_scroll_popover(
9487 &|size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
9488 IconName::ArrowDown,
9489 visible_row_range,
9490 line_layouts,
9491 newest_selection_head,
9492 scrolled_content_origin,
9493 window,
9494 cx,
9495 );
9496 }
9497
9498 const POLE_WIDTH: Pixels = px(2.);
9499
9500 let line_layout =
9501 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
9502 let target_column = target_display_point.column() as usize;
9503
9504 let target_x = line_layout.x_for_index(target_column);
9505 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
9506 - scroll_pixel_position.y;
9507
9508 let flag_on_right = target_x < text_bounds.size.width / 2.;
9509
9510 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
9511 border_color.l += 0.001;
9512
9513 let mut element = v_flex()
9514 .items_end()
9515 .when(flag_on_right, |el| el.items_start())
9516 .child(if flag_on_right {
9517 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9518 .rounded_bl(px(0.))
9519 .rounded_tl(px(0.))
9520 .border_l_2()
9521 .border_color(border_color)
9522 } else {
9523 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9524 .rounded_br(px(0.))
9525 .rounded_tr(px(0.))
9526 .border_r_2()
9527 .border_color(border_color)
9528 })
9529 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9530 .into_any();
9531
9532 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9533
9534 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9535 - point(
9536 if flag_on_right {
9537 POLE_WIDTH
9538 } else {
9539 size.width - POLE_WIDTH
9540 },
9541 size.height - line_height,
9542 );
9543
9544 origin.x = origin.x.max(content_origin.x);
9545
9546 element.prepaint_at(origin, window, cx);
9547
9548 Some((element, origin))
9549 }
9550
9551 fn render_edit_prediction_scroll_popover(
9552 &mut self,
9553 to_y: &dyn Fn(Size<Pixels>) -> Pixels,
9554 scroll_icon: IconName,
9555 visible_row_range: Range<DisplayRow>,
9556 line_layouts: &[LineWithInvisibles],
9557 newest_selection_head: Option<DisplayPoint>,
9558 scrolled_content_origin: gpui::Point<Pixels>,
9559 window: &mut Window,
9560 cx: &mut App,
9561 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9562 let mut element = self
9563 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9564 .into_any();
9565
9566 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9567
9568 let cursor = newest_selection_head?;
9569 let cursor_row_layout =
9570 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9571 let cursor_column = cursor.column() as usize;
9572
9573 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9574
9575 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9576
9577 element.prepaint_at(origin, window, cx);
9578 Some((element, origin))
9579 }
9580
9581 fn render_edit_prediction_eager_jump_popover(
9582 &mut self,
9583 text_bounds: &Bounds<Pixels>,
9584 content_origin: gpui::Point<Pixels>,
9585 editor_snapshot: &EditorSnapshot,
9586 visible_row_range: Range<DisplayRow>,
9587 scroll_top: ScrollOffset,
9588 scroll_bottom: ScrollOffset,
9589 line_height: Pixels,
9590 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9591 target_display_point: DisplayPoint,
9592 editor_width: Pixels,
9593 window: &mut Window,
9594 cx: &mut App,
9595 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9596 if target_display_point.row().as_f64() < scroll_top {
9597 let mut element = self
9598 .render_edit_prediction_line_popover(
9599 "Jump to Edit",
9600 Some(IconName::ArrowUp),
9601 window,
9602 cx,
9603 )
9604 .into_any();
9605
9606 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9607 let offset = point(
9608 (text_bounds.size.width - size.width) / 2.,
9609 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9610 );
9611
9612 let origin = text_bounds.origin + offset;
9613 element.prepaint_at(origin, window, cx);
9614 Some((element, origin))
9615 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9616 let mut element = self
9617 .render_edit_prediction_line_popover(
9618 "Jump to Edit",
9619 Some(IconName::ArrowDown),
9620 window,
9621 cx,
9622 )
9623 .into_any();
9624
9625 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9626 let offset = point(
9627 (text_bounds.size.width - size.width) / 2.,
9628 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9629 );
9630
9631 let origin = text_bounds.origin + offset;
9632 element.prepaint_at(origin, window, cx);
9633 Some((element, origin))
9634 } else {
9635 self.render_edit_prediction_end_of_line_popover(
9636 "Jump to Edit",
9637 editor_snapshot,
9638 visible_row_range,
9639 target_display_point,
9640 line_height,
9641 scroll_pixel_position,
9642 content_origin,
9643 editor_width,
9644 window,
9645 cx,
9646 )
9647 }
9648 }
9649
9650 fn render_edit_prediction_end_of_line_popover(
9651 self: &mut Editor,
9652 label: &'static str,
9653 editor_snapshot: &EditorSnapshot,
9654 visible_row_range: Range<DisplayRow>,
9655 target_display_point: DisplayPoint,
9656 line_height: Pixels,
9657 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9658 content_origin: gpui::Point<Pixels>,
9659 editor_width: Pixels,
9660 window: &mut Window,
9661 cx: &mut App,
9662 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9663 let target_line_end = DisplayPoint::new(
9664 target_display_point.row(),
9665 editor_snapshot.line_len(target_display_point.row()),
9666 );
9667
9668 let mut element = self
9669 .render_edit_prediction_line_popover(label, None, window, cx)
9670 .into_any();
9671
9672 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9673
9674 let line_origin =
9675 self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
9676
9677 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9678 let mut origin = start_point
9679 + line_origin
9680 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9681 origin.x = origin.x.max(content_origin.x);
9682
9683 let max_x = content_origin.x + editor_width - size.width;
9684
9685 if origin.x > max_x {
9686 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9687
9688 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9689 origin.y += offset;
9690 IconName::ArrowUp
9691 } else {
9692 origin.y -= offset;
9693 IconName::ArrowDown
9694 };
9695
9696 element = self
9697 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9698 .into_any();
9699
9700 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9701
9702 origin.x = content_origin.x + editor_width - size.width - px(2.);
9703 }
9704
9705 element.prepaint_at(origin, window, cx);
9706 Some((element, origin))
9707 }
9708
9709 fn render_edit_prediction_diff_popover(
9710 self: &Editor,
9711 text_bounds: &Bounds<Pixels>,
9712 content_origin: gpui::Point<Pixels>,
9713 right_margin: Pixels,
9714 editor_snapshot: &EditorSnapshot,
9715 visible_row_range: Range<DisplayRow>,
9716 line_layouts: &[LineWithInvisibles],
9717 line_height: Pixels,
9718 scroll_position: gpui::Point<ScrollOffset>,
9719 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9720 newest_selection_head: Option<DisplayPoint>,
9721 editor_width: Pixels,
9722 style: &EditorStyle,
9723 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9724 edit_preview: &Option<language::EditPreview>,
9725 snapshot: &language::BufferSnapshot,
9726 window: &mut Window,
9727 cx: &mut App,
9728 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9729 let edit_start = edits
9730 .first()
9731 .unwrap()
9732 .0
9733 .start
9734 .to_display_point(editor_snapshot);
9735 let edit_end = edits
9736 .last()
9737 .unwrap()
9738 .0
9739 .end
9740 .to_display_point(editor_snapshot);
9741
9742 let is_visible = visible_row_range.contains(&edit_start.row())
9743 || visible_row_range.contains(&edit_end.row());
9744 if !is_visible {
9745 return None;
9746 }
9747
9748 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9749 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9750 } else {
9751 // Fallback for providers without edit_preview
9752 crate::edit_prediction_fallback_text(edits, cx)
9753 };
9754
9755 let styled_text = highlighted_edits.to_styled_text(&style.text);
9756 let line_count = highlighted_edits.text.lines().count();
9757
9758 const BORDER_WIDTH: Pixels = px(1.);
9759
9760 let keybind = self.render_edit_prediction_keybind(window, cx);
9761 let has_keybind = keybind.is_some();
9762
9763 let mut element = h_flex()
9764 .items_start()
9765 .child(
9766 h_flex()
9767 .bg(cx.theme().colors().editor_background)
9768 .border(BORDER_WIDTH)
9769 .shadow_xs()
9770 .border_color(cx.theme().colors().border)
9771 .rounded_l_lg()
9772 .when(line_count > 1, |el| el.rounded_br_lg())
9773 .pr_1()
9774 .child(styled_text),
9775 )
9776 .child(
9777 h_flex()
9778 .h(line_height + BORDER_WIDTH * 2.)
9779 .px_1p5()
9780 .gap_1()
9781 // Workaround: For some reason, there's a gap if we don't do this
9782 .ml(-BORDER_WIDTH)
9783 .shadow(vec![gpui::BoxShadow {
9784 color: gpui::black().opacity(0.05),
9785 offset: point(px(1.), px(1.)),
9786 blur_radius: px(2.),
9787 spread_radius: px(0.),
9788 }])
9789 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9790 .border(BORDER_WIDTH)
9791 .border_color(cx.theme().colors().border)
9792 .rounded_r_lg()
9793 .id("edit_prediction_diff_popover_keybind")
9794 .when(!has_keybind, |el| {
9795 let status_colors = cx.theme().status();
9796
9797 el.bg(status_colors.error_background)
9798 .border_color(status_colors.error.opacity(0.6))
9799 .child(Icon::new(IconName::Info).color(Color::Error))
9800 .cursor_default()
9801 .hoverable_tooltip(move |_window, cx| {
9802 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9803 })
9804 })
9805 .children(keybind),
9806 )
9807 .into_any();
9808
9809 let longest_row =
9810 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9811 let longest_line_width = if visible_row_range.contains(&longest_row) {
9812 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9813 } else {
9814 layout_line(
9815 longest_row,
9816 editor_snapshot,
9817 style,
9818 editor_width,
9819 |_| false,
9820 window,
9821 cx,
9822 )
9823 .width
9824 };
9825
9826 let viewport_bounds =
9827 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9828 right: -right_margin,
9829 ..Default::default()
9830 });
9831
9832 let x_after_longest = Pixels::from(
9833 ScrollPixelOffset::from(
9834 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9835 ) - scroll_pixel_position.x,
9836 );
9837
9838 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9839
9840 // Fully visible if it can be displayed within the window (allow overlapping other
9841 // panes). However, this is only allowed if the popover starts within text_bounds.
9842 let can_position_to_the_right = x_after_longest < text_bounds.right()
9843 && x_after_longest + element_bounds.width < viewport_bounds.right();
9844
9845 let mut origin = if can_position_to_the_right {
9846 point(
9847 x_after_longest,
9848 text_bounds.origin.y
9849 + Pixels::from(
9850 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9851 - scroll_pixel_position.y,
9852 ),
9853 )
9854 } else {
9855 let cursor_row = newest_selection_head.map(|head| head.row());
9856 let above_edit = edit_start
9857 .row()
9858 .0
9859 .checked_sub(line_count as u32)
9860 .map(DisplayRow);
9861 let below_edit = Some(edit_end.row() + 1);
9862 let above_cursor =
9863 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9864 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9865
9866 // Place the edit popover adjacent to the edit if there is a location
9867 // available that is onscreen and does not obscure the cursor. Otherwise,
9868 // place it adjacent to the cursor.
9869 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9870 .into_iter()
9871 .flatten()
9872 .find(|&start_row| {
9873 let end_row = start_row + line_count as u32;
9874 visible_row_range.contains(&start_row)
9875 && visible_row_range.contains(&end_row)
9876 && cursor_row
9877 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9878 })?;
9879
9880 content_origin
9881 + point(
9882 Pixels::from(-scroll_pixel_position.x),
9883 Pixels::from(
9884 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9885 ),
9886 )
9887 };
9888
9889 origin.x -= BORDER_WIDTH;
9890
9891 window.with_content_mask(
9892 Some(gpui::ContentMask {
9893 bounds: *text_bounds,
9894 }),
9895 |window| {
9896 window.defer_draw(element, origin, 1, Some(window.content_mask()));
9897 },
9898 );
9899
9900 // Do not return an element, since it will already be drawn due to defer_draw.
9901 None
9902 }
9903
9904 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9905 px(30.)
9906 }
9907
9908 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9909 if self.read_only(cx) {
9910 cx.theme().players().read_only()
9911 } else {
9912 self.style.as_ref().unwrap().local_player
9913 }
9914 }
9915
9916 fn render_edit_prediction_inline_keystroke(
9917 &self,
9918 keystroke: &gpui::KeybindingKeystroke,
9919 modifiers_color: Color,
9920 cx: &App,
9921 ) -> AnyElement {
9922 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9923
9924 h_flex()
9925 .px_0p5()
9926 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9927 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9928 .text_size(TextSize::XSmall.rems(cx))
9929 .child(h_flex().children(ui::render_modifiers(
9930 keystroke.modifiers(),
9931 PlatformStyle::platform(),
9932 Some(modifiers_color),
9933 Some(IconSize::XSmall.rems().into()),
9934 true,
9935 )))
9936 .when(is_platform_style_mac, |parent| {
9937 parent.child(keystroke.key().to_string())
9938 })
9939 .when(!is_platform_style_mac, |parent| {
9940 parent.child(
9941 Key::new(util::capitalize(keystroke.key()), Some(Color::Default))
9942 .size(Some(IconSize::XSmall.rems().into())),
9943 )
9944 })
9945 .into_any()
9946 }
9947
9948 fn render_edit_prediction_popover_keystroke(
9949 &self,
9950 keystroke: &gpui::KeybindingKeystroke,
9951 color: Color,
9952 cx: &App,
9953 ) -> AnyElement {
9954 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9955
9956 if keystroke.modifiers().modified() {
9957 h_flex()
9958 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9959 .when(is_platform_style_mac, |parent| parent.gap_1())
9960 .child(h_flex().children(ui::render_modifiers(
9961 keystroke.modifiers(),
9962 PlatformStyle::platform(),
9963 Some(color),
9964 None,
9965 false,
9966 )))
9967 .into_any()
9968 } else {
9969 Key::new(util::capitalize(keystroke.key()), Some(color))
9970 .size(Some(IconSize::XSmall.rems().into()))
9971 .into_any_element()
9972 }
9973 }
9974
9975 fn render_edit_prediction_keybind(
9976 &self,
9977 window: &mut Window,
9978 cx: &mut App,
9979 ) -> Option<AnyElement> {
9980 let keybind_display =
9981 self.edit_prediction_keybind_display(EditPredictionKeybindSurface::Inline, window, cx);
9982 let keystroke = keybind_display.displayed_keystroke.as_ref()?;
9983
9984 let modifiers_color = if *keystroke.modifiers() == window.modifiers() {
9985 Color::Accent
9986 } else {
9987 Color::Muted
9988 };
9989
9990 Some(self.render_edit_prediction_inline_keystroke(keystroke, modifiers_color, cx))
9991 }
9992
9993 fn render_edit_prediction_line_popover(
9994 &self,
9995 label: impl Into<SharedString>,
9996 icon: Option<IconName>,
9997 window: &mut Window,
9998 cx: &mut App,
9999 ) -> Stateful<Div> {
10000 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
10001
10002 let keybind = self.render_edit_prediction_keybind(window, cx);
10003 let has_keybind = keybind.is_some();
10004 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10005
10006 h_flex()
10007 .id("ep-line-popover")
10008 .py_0p5()
10009 .pl_1()
10010 .pr(padding_right)
10011 .gap_1()
10012 .rounded_md()
10013 .border_1()
10014 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10015 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
10016 .shadow_xs()
10017 .when(!has_keybind, |el| {
10018 let status_colors = cx.theme().status();
10019
10020 el.bg(status_colors.error_background)
10021 .border_color(status_colors.error.opacity(0.6))
10022 .pl_2()
10023 .child(Icon::new(icons.error).color(Color::Error))
10024 .cursor_default()
10025 .hoverable_tooltip(move |_window, cx| {
10026 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
10027 })
10028 })
10029 .children(keybind)
10030 .child(
10031 Label::new(label)
10032 .size(LabelSize::Small)
10033 .when(!has_keybind, |el| {
10034 el.color(cx.theme().status().error.into()).strikethrough()
10035 }),
10036 )
10037 .when(!has_keybind, |el| {
10038 el.child(
10039 h_flex().ml_1().child(
10040 Icon::new(IconName::Info)
10041 .size(IconSize::Small)
10042 .color(cx.theme().status().error.into()),
10043 ),
10044 )
10045 })
10046 .when_some(icon, |element, icon| {
10047 element.child(
10048 div()
10049 .mt(px(1.5))
10050 .child(Icon::new(icon).size(IconSize::Small)),
10051 )
10052 })
10053 }
10054
10055 fn render_edit_prediction_jump_outside_popover(
10056 &self,
10057 snapshot: &BufferSnapshot,
10058 window: &mut Window,
10059 cx: &mut App,
10060 ) -> Stateful<Div> {
10061 let keybind = self.render_edit_prediction_keybind(window, cx);
10062 let has_keybind = keybind.is_some();
10063 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10064
10065 let file_name = snapshot
10066 .file()
10067 .map(|file| SharedString::new(file.file_name(cx)))
10068 .unwrap_or(SharedString::new_static("untitled"));
10069
10070 h_flex()
10071 .id("ep-jump-outside-popover")
10072 .py_1()
10073 .px_2()
10074 .gap_1()
10075 .rounded_md()
10076 .border_1()
10077 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10078 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
10079 .shadow_xs()
10080 .when(!has_keybind, |el| {
10081 let status_colors = cx.theme().status();
10082
10083 el.bg(status_colors.error_background)
10084 .border_color(status_colors.error.opacity(0.6))
10085 .pl_2()
10086 .child(Icon::new(icons.error).color(Color::Error))
10087 .cursor_default()
10088 .hoverable_tooltip(move |_window, cx| {
10089 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
10090 })
10091 })
10092 .children(keybind)
10093 .child(
10094 Label::new(file_name)
10095 .size(LabelSize::Small)
10096 .buffer_font(cx)
10097 .when(!has_keybind, |el| {
10098 el.color(cx.theme().status().error.into()).strikethrough()
10099 }),
10100 )
10101 .when(!has_keybind, |el| {
10102 el.child(
10103 h_flex().ml_1().child(
10104 Icon::new(IconName::Info)
10105 .size(IconSize::Small)
10106 .color(cx.theme().status().error.into()),
10107 ),
10108 )
10109 })
10110 .child(
10111 div()
10112 .mt(px(1.5))
10113 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
10114 )
10115 }
10116
10117 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
10118 let accent_color = cx.theme().colors().text_accent;
10119 let editor_bg_color = cx.theme().colors().editor_background;
10120 editor_bg_color.blend(accent_color.opacity(0.1))
10121 }
10122
10123 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
10124 let accent_color = cx.theme().colors().text_accent;
10125 let editor_bg_color = cx.theme().colors().editor_background;
10126 editor_bg_color.blend(accent_color.opacity(0.6))
10127 }
10128 fn get_prediction_provider_icons(
10129 provider: &Option<RegisteredEditPredictionDelegate>,
10130 cx: &App,
10131 ) -> edit_prediction_types::EditPredictionIconSet {
10132 match provider {
10133 Some(provider) => provider.provider.icons(cx),
10134 None => edit_prediction_types::EditPredictionIconSet::new(IconName::ZedPredict),
10135 }
10136 }
10137
10138 fn render_edit_prediction_cursor_popover(
10139 &self,
10140 min_width: Pixels,
10141 max_width: Pixels,
10142 cursor_point: Point,
10143 style: &EditorStyle,
10144 window: &mut Window,
10145 cx: &mut Context<Editor>,
10146 ) -> Option<AnyElement> {
10147 let provider = self.edit_prediction_provider.as_ref()?;
10148 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10149
10150 let is_refreshing = provider.provider.is_refreshing(cx);
10151
10152 fn pending_completion_container(icon: IconName) -> Div {
10153 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
10154 }
10155
10156 let completion = match &self.active_edit_prediction {
10157 Some(prediction) => {
10158 if !self.has_visible_completions_menu() {
10159 const RADIUS: Pixels = px(6.);
10160 const BORDER_WIDTH: Pixels = px(1.);
10161 let keybind_display = self.edit_prediction_keybind_display(
10162 EditPredictionKeybindSurface::CursorPopoverCompact,
10163 window,
10164 cx,
10165 );
10166
10167 return Some(
10168 h_flex()
10169 .elevation_2(cx)
10170 .border(BORDER_WIDTH)
10171 .border_color(cx.theme().colors().border)
10172 .when(keybind_display.missing_accept_keystroke, |el| {
10173 el.border_color(cx.theme().status().error)
10174 })
10175 .rounded(RADIUS)
10176 .rounded_tl(px(0.))
10177 .overflow_hidden()
10178 .child(div().px_1p5().child(match &prediction.completion {
10179 EditPrediction::MoveWithin { target, snapshot } => {
10180 use text::ToPoint as _;
10181 if target.text_anchor.to_point(snapshot).row > cursor_point.row
10182 {
10183 Icon::new(icons.down)
10184 } else {
10185 Icon::new(icons.up)
10186 }
10187 }
10188 EditPrediction::MoveOutside { .. } => {
10189 // TODO [zeta2] custom icon for external jump?
10190 Icon::new(icons.base)
10191 }
10192 EditPrediction::Edit { .. } => Icon::new(icons.base),
10193 }))
10194 .child(
10195 h_flex()
10196 .gap_1()
10197 .py_1()
10198 .px_2()
10199 .rounded_r(RADIUS - BORDER_WIDTH)
10200 .border_l_1()
10201 .border_color(cx.theme().colors().border)
10202 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10203 .when(keybind_display.show_hold_label, |el| {
10204 el.child(
10205 Label::new("Hold")
10206 .size(LabelSize::Small)
10207 .when(
10208 keybind_display.missing_accept_keystroke,
10209 |el| el.strikethrough(),
10210 )
10211 .line_height_style(LineHeightStyle::UiLabel),
10212 )
10213 })
10214 .id("edit_prediction_cursor_popover_keybind")
10215 .when(keybind_display.missing_accept_keystroke, |el| {
10216 let status_colors = cx.theme().status();
10217
10218 el.bg(status_colors.error_background)
10219 .border_color(status_colors.error.opacity(0.6))
10220 .child(Icon::new(IconName::Info).color(Color::Error))
10221 .cursor_default()
10222 .hoverable_tooltip(move |_window, cx| {
10223 cx.new(|_| MissingEditPredictionKeybindingTooltip)
10224 .into()
10225 })
10226 })
10227 .when_some(
10228 keybind_display.displayed_keystroke.as_ref(),
10229 |el, compact_keystroke| {
10230 el.child(self.render_edit_prediction_popover_keystroke(
10231 compact_keystroke,
10232 Color::Default,
10233 cx,
10234 ))
10235 },
10236 ),
10237 )
10238 .into_any(),
10239 );
10240 }
10241
10242 self.render_edit_prediction_cursor_popover_preview(
10243 prediction,
10244 cursor_point,
10245 style,
10246 cx,
10247 )?
10248 }
10249
10250 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
10251 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
10252 stale_completion,
10253 cursor_point,
10254 style,
10255 cx,
10256 )?,
10257
10258 None => pending_completion_container(icons.base)
10259 .child(Label::new("...").size(LabelSize::Small)),
10260 },
10261
10262 None => pending_completion_container(icons.base)
10263 .child(Label::new("...").size(LabelSize::Small)),
10264 };
10265
10266 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
10267 completion
10268 .with_animation(
10269 "loading-completion",
10270 Animation::new(Duration::from_secs(2))
10271 .repeat()
10272 .with_easing(pulsating_between(0.4, 0.8)),
10273 |label, delta| label.opacity(delta),
10274 )
10275 .into_any_element()
10276 } else {
10277 completion.into_any_element()
10278 };
10279
10280 let has_completion = self.active_edit_prediction.is_some();
10281 let keybind_display = self.edit_prediction_keybind_display(
10282 EditPredictionKeybindSurface::CursorPopoverExpanded,
10283 window,
10284 cx,
10285 );
10286
10287 Some(
10288 h_flex()
10289 .min_w(min_width)
10290 .max_w(max_width)
10291 .flex_1()
10292 .elevation_2(cx)
10293 .border_color(cx.theme().colors().border)
10294 .child(
10295 div()
10296 .flex_1()
10297 .py_1()
10298 .px_2()
10299 .overflow_hidden()
10300 .child(completion),
10301 )
10302 .when_some(
10303 keybind_display.displayed_keystroke.as_ref(),
10304 |el, keystroke| {
10305 let key_color = if !has_completion {
10306 Color::Muted
10307 } else {
10308 Color::Default
10309 };
10310
10311 if keybind_display.action == EditPredictionKeybindAction::Preview {
10312 el.child(
10313 h_flex()
10314 .h_full()
10315 .border_l_1()
10316 .rounded_r_lg()
10317 .border_color(cx.theme().colors().border)
10318 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10319 .gap_1()
10320 .py_1()
10321 .px_2()
10322 .child(self.render_edit_prediction_popover_keystroke(
10323 keystroke, key_color, cx,
10324 ))
10325 .child(Label::new("Preview").into_any_element())
10326 .opacity(if has_completion { 1.0 } else { 0.4 }),
10327 )
10328 } else {
10329 el.child(
10330 h_flex()
10331 .h_full()
10332 .border_l_1()
10333 .rounded_r_lg()
10334 .border_color(cx.theme().colors().border)
10335 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10336 .gap_1()
10337 .py_1()
10338 .px_2()
10339 .child(self.render_edit_prediction_popover_keystroke(
10340 keystroke, key_color, cx,
10341 ))
10342 .opacity(if has_completion { 1.0 } else { 0.4 }),
10343 )
10344 }
10345 },
10346 )
10347 .into_any(),
10348 )
10349 }
10350
10351 fn render_edit_prediction_cursor_popover_preview(
10352 &self,
10353 completion: &EditPredictionState,
10354 cursor_point: Point,
10355 style: &EditorStyle,
10356 cx: &mut Context<Editor>,
10357 ) -> Option<Div> {
10358 use text::ToPoint as _;
10359
10360 fn render_relative_row_jump(
10361 prefix: impl Into<String>,
10362 current_row: u32,
10363 target_row: u32,
10364 ) -> Div {
10365 let (row_diff, arrow) = if target_row < current_row {
10366 (current_row - target_row, IconName::ArrowUp)
10367 } else {
10368 (target_row - current_row, IconName::ArrowDown)
10369 };
10370
10371 h_flex()
10372 .child(
10373 Label::new(format!("{}{}", prefix.into(), row_diff))
10374 .color(Color::Muted)
10375 .size(LabelSize::Small),
10376 )
10377 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
10378 }
10379
10380 let supports_jump = self
10381 .edit_prediction_provider
10382 .as_ref()
10383 .map(|provider| provider.provider.supports_jump_to_edit())
10384 .unwrap_or(true);
10385
10386 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10387
10388 match &completion.completion {
10389 EditPrediction::MoveWithin {
10390 target, snapshot, ..
10391 } => {
10392 if !supports_jump {
10393 return None;
10394 }
10395
10396 Some(
10397 h_flex()
10398 .px_2()
10399 .gap_2()
10400 .flex_1()
10401 .child(
10402 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
10403 Icon::new(icons.down)
10404 } else {
10405 Icon::new(icons.up)
10406 },
10407 )
10408 .child(Label::new("Jump to Edit")),
10409 )
10410 }
10411 EditPrediction::MoveOutside { snapshot, .. } => {
10412 let file_name = snapshot
10413 .file()
10414 .map(|file| file.file_name(cx))
10415 .unwrap_or("untitled");
10416 Some(
10417 h_flex()
10418 .px_2()
10419 .gap_2()
10420 .flex_1()
10421 .child(Icon::new(icons.base))
10422 .child(Label::new(format!("Jump to {file_name}"))),
10423 )
10424 }
10425 EditPrediction::Edit {
10426 edits,
10427 edit_preview,
10428 snapshot,
10429 ..
10430 } => {
10431 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
10432
10433 let (highlighted_edits, has_more_lines) =
10434 if let Some(edit_preview) = edit_preview.as_ref() {
10435 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
10436 .first_line_preview()
10437 } else {
10438 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
10439 };
10440
10441 let styled_text = gpui::StyledText::new(highlighted_edits.text)
10442 .with_default_highlights(&style.text, highlighted_edits.highlights);
10443
10444 let preview = h_flex()
10445 .gap_1()
10446 .min_w_16()
10447 .child(styled_text)
10448 .when(has_more_lines, |parent| parent.child("…"));
10449
10450 let left = if supports_jump && first_edit_row != cursor_point.row {
10451 render_relative_row_jump("", cursor_point.row, first_edit_row)
10452 .into_any_element()
10453 } else {
10454 Icon::new(icons.base).into_any_element()
10455 };
10456
10457 Some(
10458 h_flex()
10459 .h_full()
10460 .flex_1()
10461 .gap_2()
10462 .pr_1()
10463 .overflow_x_hidden()
10464 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
10465 .child(left)
10466 .child(preview),
10467 )
10468 }
10469 }
10470 }
10471
10472 pub fn render_context_menu(
10473 &mut self,
10474 max_height_in_lines: u32,
10475 window: &mut Window,
10476 cx: &mut Context<Editor>,
10477 ) -> Option<AnyElement> {
10478 let menu = self.context_menu.borrow();
10479 let menu = menu.as_ref()?;
10480 if !menu.visible() {
10481 return None;
10482 };
10483 self.style
10484 .as_ref()
10485 .map(|style| menu.render(style, max_height_in_lines, window, cx))
10486 }
10487
10488 fn render_context_menu_aside(
10489 &mut self,
10490 max_size: Size<Pixels>,
10491 window: &mut Window,
10492 cx: &mut Context<Editor>,
10493 ) -> Option<AnyElement> {
10494 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
10495 if menu.visible() {
10496 menu.render_aside(max_size, window, cx)
10497 } else {
10498 None
10499 }
10500 })
10501 }
10502
10503 fn hide_context_menu(
10504 &mut self,
10505 window: &mut Window,
10506 cx: &mut Context<Self>,
10507 ) -> Option<CodeContextMenu> {
10508 cx.notify();
10509 self.completion_tasks.clear();
10510 let context_menu = self.context_menu.borrow_mut().take();
10511 self.stale_edit_prediction_in_menu.take();
10512 self.update_visible_edit_prediction(window, cx);
10513 if let Some(CodeContextMenu::Completions(_)) = &context_menu
10514 && let Some(completion_provider) = &self.completion_provider
10515 {
10516 completion_provider.selection_changed(None, window, cx);
10517 }
10518 context_menu
10519 }
10520
10521 fn show_snippet_choices(
10522 &mut self,
10523 choices: &Vec<String>,
10524 selection: Range<Anchor>,
10525 cx: &mut Context<Self>,
10526 ) {
10527 let Some((_, buffer, _)) = self
10528 .buffer()
10529 .read(cx)
10530 .excerpt_containing(selection.start, cx)
10531 else {
10532 return;
10533 };
10534 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
10535 else {
10536 return;
10537 };
10538 if buffer != end_buffer {
10539 log::error!("expected anchor range to have matching buffer IDs");
10540 return;
10541 }
10542
10543 let id = post_inc(&mut self.next_completion_id);
10544 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
10545 let mut context_menu = self.context_menu.borrow_mut();
10546 let old_menu = context_menu.take();
10547 *context_menu = Some(CodeContextMenu::Completions(
10548 CompletionsMenu::new_snippet_choices(
10549 id,
10550 true,
10551 choices,
10552 selection,
10553 buffer,
10554 old_menu.map(|menu| menu.primary_scroll_handle()),
10555 snippet_sort_order,
10556 ),
10557 ));
10558 }
10559
10560 pub fn insert_snippet(
10561 &mut self,
10562 insertion_ranges: &[Range<MultiBufferOffset>],
10563 snippet: Snippet,
10564 window: &mut Window,
10565 cx: &mut Context<Self>,
10566 ) -> Result<()> {
10567 struct Tabstop<T> {
10568 is_end_tabstop: bool,
10569 ranges: Vec<Range<T>>,
10570 choices: Option<Vec<String>>,
10571 }
10572
10573 let tabstops = self.buffer.update(cx, |buffer, cx| {
10574 let snippet_text: Arc<str> = snippet.text.clone().into();
10575 let edits = insertion_ranges
10576 .iter()
10577 .cloned()
10578 .map(|range| (range, snippet_text.clone()));
10579 let autoindent_mode = AutoindentMode::Block {
10580 original_indent_columns: Vec::new(),
10581 };
10582 buffer.edit(edits, Some(autoindent_mode), cx);
10583
10584 let snapshot = &*buffer.read(cx);
10585 let snippet = &snippet;
10586 snippet
10587 .tabstops
10588 .iter()
10589 .map(|tabstop| {
10590 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10591 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10592 });
10593 let mut tabstop_ranges = tabstop
10594 .ranges
10595 .iter()
10596 .flat_map(|tabstop_range| {
10597 let mut delta = 0_isize;
10598 insertion_ranges.iter().map(move |insertion_range| {
10599 let insertion_start = insertion_range.start + delta;
10600 delta += snippet.text.len() as isize
10601 - (insertion_range.end - insertion_range.start) as isize;
10602
10603 let start =
10604 (insertion_start + tabstop_range.start).min(snapshot.len());
10605 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10606 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10607 })
10608 })
10609 .collect::<Vec<_>>();
10610 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10611
10612 Tabstop {
10613 is_end_tabstop,
10614 ranges: tabstop_ranges,
10615 choices: tabstop.choices.clone(),
10616 }
10617 })
10618 .collect::<Vec<_>>()
10619 });
10620 if let Some(tabstop) = tabstops.first() {
10621 self.change_selections(Default::default(), window, cx, |s| {
10622 // Reverse order so that the first range is the newest created selection.
10623 // Completions will use it and autoscroll will prioritize it.
10624 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10625 });
10626
10627 if let Some(choices) = &tabstop.choices
10628 && let Some(selection) = tabstop.ranges.first()
10629 {
10630 self.show_snippet_choices(choices, selection.clone(), cx)
10631 }
10632
10633 // If we're already at the last tabstop and it's at the end of the snippet,
10634 // we're done, we don't need to keep the state around.
10635 if !tabstop.is_end_tabstop {
10636 let choices = tabstops
10637 .iter()
10638 .map(|tabstop| tabstop.choices.clone())
10639 .collect();
10640
10641 let ranges = tabstops
10642 .into_iter()
10643 .map(|tabstop| tabstop.ranges)
10644 .collect::<Vec<_>>();
10645
10646 self.snippet_stack.push(SnippetState {
10647 active_index: 0,
10648 ranges,
10649 choices,
10650 });
10651 }
10652
10653 // Check whether the just-entered snippet ends with an auto-closable bracket.
10654 if self.autoclose_regions.is_empty() {
10655 let snapshot = self.buffer.read(cx).snapshot(cx);
10656 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10657 let selection_head = selection.head();
10658 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10659 continue;
10660 };
10661
10662 let mut bracket_pair = None;
10663 let max_lookup_length = scope
10664 .brackets()
10665 .map(|(pair, _)| {
10666 pair.start
10667 .as_str()
10668 .chars()
10669 .count()
10670 .max(pair.end.as_str().chars().count())
10671 })
10672 .max();
10673 if let Some(max_lookup_length) = max_lookup_length {
10674 let next_text = snapshot
10675 .chars_at(selection_head)
10676 .take(max_lookup_length)
10677 .collect::<String>();
10678 let prev_text = snapshot
10679 .reversed_chars_at(selection_head)
10680 .take(max_lookup_length)
10681 .collect::<String>();
10682
10683 for (pair, enabled) in scope.brackets() {
10684 if enabled
10685 && pair.close
10686 && prev_text.starts_with(pair.start.as_str())
10687 && next_text.starts_with(pair.end.as_str())
10688 {
10689 bracket_pair = Some(pair.clone());
10690 break;
10691 }
10692 }
10693 }
10694
10695 if let Some(pair) = bracket_pair {
10696 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10697 let autoclose_enabled =
10698 self.use_autoclose && snapshot_settings.use_autoclose;
10699 if autoclose_enabled {
10700 let start = snapshot.anchor_after(selection_head);
10701 let end = snapshot.anchor_after(selection_head);
10702 self.autoclose_regions.push(AutocloseRegion {
10703 selection_id: selection.id,
10704 range: start..end,
10705 pair,
10706 });
10707 }
10708 }
10709 }
10710 }
10711 }
10712 Ok(())
10713 }
10714
10715 pub fn move_to_next_snippet_tabstop(
10716 &mut self,
10717 window: &mut Window,
10718 cx: &mut Context<Self>,
10719 ) -> bool {
10720 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10721 }
10722
10723 pub fn move_to_prev_snippet_tabstop(
10724 &mut self,
10725 window: &mut Window,
10726 cx: &mut Context<Self>,
10727 ) -> bool {
10728 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10729 }
10730
10731 pub fn move_to_snippet_tabstop(
10732 &mut self,
10733 bias: Bias,
10734 window: &mut Window,
10735 cx: &mut Context<Self>,
10736 ) -> bool {
10737 if let Some(mut snippet) = self.snippet_stack.pop() {
10738 match bias {
10739 Bias::Left => {
10740 if snippet.active_index > 0 {
10741 snippet.active_index -= 1;
10742 } else {
10743 self.snippet_stack.push(snippet);
10744 return false;
10745 }
10746 }
10747 Bias::Right => {
10748 if snippet.active_index + 1 < snippet.ranges.len() {
10749 snippet.active_index += 1;
10750 } else {
10751 self.snippet_stack.push(snippet);
10752 return false;
10753 }
10754 }
10755 }
10756 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10757 self.change_selections(Default::default(), window, cx, |s| {
10758 // Reverse order so that the first range is the newest created selection.
10759 // Completions will use it and autoscroll will prioritize it.
10760 s.select_ranges(current_ranges.iter().rev().cloned())
10761 });
10762
10763 if let Some(choices) = &snippet.choices[snippet.active_index]
10764 && let Some(selection) = current_ranges.first()
10765 {
10766 self.show_snippet_choices(choices, selection.clone(), cx);
10767 }
10768
10769 // If snippet state is not at the last tabstop, push it back on the stack
10770 if snippet.active_index + 1 < snippet.ranges.len() {
10771 self.snippet_stack.push(snippet);
10772 }
10773 return true;
10774 }
10775 }
10776
10777 false
10778 }
10779
10780 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10781 self.transact(window, cx, |this, window, cx| {
10782 this.select_all(&SelectAll, window, cx);
10783 this.insert("", window, cx);
10784 });
10785 }
10786
10787 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10788 if self.read_only(cx) {
10789 return;
10790 }
10791 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10792 self.transact(window, cx, |this, window, cx| {
10793 this.select_autoclose_pair(window, cx);
10794
10795 let linked_edits = this.linked_edits_for_selections(Arc::from(""), cx);
10796
10797 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10798 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10799 for selection in &mut selections {
10800 if selection.is_empty() {
10801 let old_head = selection.head();
10802 let mut new_head =
10803 movement::left(&display_map, old_head.to_display_point(&display_map))
10804 .to_point(&display_map);
10805 if let Some((buffer, line_buffer_range)) = display_map
10806 .buffer_snapshot()
10807 .buffer_line_for_row(MultiBufferRow(old_head.row))
10808 {
10809 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10810 let indent_len = match indent_size.kind {
10811 IndentKind::Space => {
10812 buffer.settings_at(line_buffer_range.start, cx).tab_size
10813 }
10814 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10815 };
10816 if old_head.column <= indent_size.len && old_head.column > 0 {
10817 let indent_len = indent_len.get();
10818 new_head = cmp::min(
10819 new_head,
10820 MultiBufferPoint::new(
10821 old_head.row,
10822 ((old_head.column - 1) / indent_len) * indent_len,
10823 ),
10824 );
10825 }
10826 }
10827
10828 selection.set_head(new_head, SelectionGoal::None);
10829 }
10830 }
10831
10832 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10833 this.insert("", window, cx);
10834 linked_edits.apply_with_left_expansion(cx);
10835 this.refresh_edit_prediction(true, false, window, cx);
10836 refresh_linked_ranges(this, window, cx);
10837 });
10838 }
10839
10840 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10841 if self.read_only(cx) {
10842 return;
10843 }
10844 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10845 self.transact(window, cx, |this, window, cx| {
10846 this.change_selections(Default::default(), window, cx, |s| {
10847 s.move_with(&mut |map, selection| {
10848 if selection.is_empty() {
10849 let cursor = movement::right(map, selection.head());
10850 selection.end = cursor;
10851 selection.reversed = true;
10852 selection.goal = SelectionGoal::None;
10853 }
10854 })
10855 });
10856 let linked_edits = this.linked_edits_for_selections(Arc::from(""), cx);
10857 this.insert("", window, cx);
10858 linked_edits.apply(cx);
10859 this.refresh_edit_prediction(true, false, window, cx);
10860 refresh_linked_ranges(this, window, cx);
10861 });
10862 }
10863
10864 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10865 if self.mode.is_single_line() {
10866 cx.propagate();
10867 return;
10868 }
10869
10870 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10871 if self.move_to_prev_snippet_tabstop(window, cx) {
10872 return;
10873 }
10874 self.outdent(&Outdent, window, cx);
10875 }
10876
10877 pub fn next_snippet_tabstop(
10878 &mut self,
10879 _: &NextSnippetTabstop,
10880 window: &mut Window,
10881 cx: &mut Context<Self>,
10882 ) {
10883 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10884 cx.propagate();
10885 return;
10886 }
10887
10888 if self.move_to_next_snippet_tabstop(window, cx) {
10889 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10890 return;
10891 }
10892 cx.propagate();
10893 }
10894
10895 pub fn previous_snippet_tabstop(
10896 &mut self,
10897 _: &PreviousSnippetTabstop,
10898 window: &mut Window,
10899 cx: &mut Context<Self>,
10900 ) {
10901 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10902 cx.propagate();
10903 return;
10904 }
10905
10906 if self.move_to_prev_snippet_tabstop(window, cx) {
10907 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10908 return;
10909 }
10910 cx.propagate();
10911 }
10912
10913 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10914 if self.mode.is_single_line() {
10915 cx.propagate();
10916 return;
10917 }
10918
10919 if self.move_to_next_snippet_tabstop(window, cx) {
10920 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10921 return;
10922 }
10923 if self.read_only(cx) {
10924 return;
10925 }
10926 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10927 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10928 let buffer = self.buffer.read(cx);
10929 let snapshot = buffer.snapshot(cx);
10930 let rows_iter = selections.iter().map(|s| s.head().row);
10931 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10932
10933 let has_some_cursor_in_whitespace = selections
10934 .iter()
10935 .filter(|selection| selection.is_empty())
10936 .any(|selection| {
10937 let cursor = selection.head();
10938 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10939 cursor.column < current_indent.len
10940 });
10941
10942 let mut edits = Vec::new();
10943 let mut prev_edited_row = 0;
10944 let mut row_delta = 0;
10945 for selection in &mut selections {
10946 if selection.start.row != prev_edited_row {
10947 row_delta = 0;
10948 }
10949 prev_edited_row = selection.end.row;
10950
10951 // If cursor is after a list prefix, make selection non-empty to trigger line indent
10952 if selection.is_empty() {
10953 let cursor = selection.head();
10954 let settings = buffer.language_settings_at(cursor, cx);
10955 if settings.indent_list_on_tab {
10956 if let Some(language) = snapshot.language_scope_at(Point::new(cursor.row, 0)) {
10957 if is_list_prefix_row(MultiBufferRow(cursor.row), &snapshot, &language) {
10958 row_delta = Self::indent_selection(
10959 buffer, &snapshot, selection, &mut edits, row_delta, cx,
10960 );
10961 continue;
10962 }
10963 }
10964 }
10965 }
10966
10967 // If the selection is non-empty, then increase the indentation of the selected lines.
10968 if !selection.is_empty() {
10969 row_delta =
10970 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10971 continue;
10972 }
10973
10974 let cursor = selection.head();
10975 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10976 if let Some(suggested_indent) =
10977 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10978 {
10979 // Don't do anything if already at suggested indent
10980 // and there is any other cursor which is not
10981 if has_some_cursor_in_whitespace
10982 && cursor.column == current_indent.len
10983 && current_indent.len == suggested_indent.len
10984 {
10985 continue;
10986 }
10987
10988 // Adjust line and move cursor to suggested indent
10989 // if cursor is not at suggested indent
10990 if cursor.column < suggested_indent.len
10991 && cursor.column <= current_indent.len
10992 && current_indent.len <= suggested_indent.len
10993 {
10994 selection.start = Point::new(cursor.row, suggested_indent.len);
10995 selection.end = selection.start;
10996 if row_delta == 0 {
10997 edits.extend(Buffer::edit_for_indent_size_adjustment(
10998 cursor.row,
10999 current_indent,
11000 suggested_indent,
11001 ));
11002 row_delta = suggested_indent.len - current_indent.len;
11003 }
11004 continue;
11005 }
11006
11007 // If current indent is more than suggested indent
11008 // only move cursor to current indent and skip indent
11009 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
11010 selection.start = Point::new(cursor.row, current_indent.len);
11011 selection.end = selection.start;
11012 continue;
11013 }
11014 }
11015
11016 // Otherwise, insert a hard or soft tab.
11017 let settings = buffer.language_settings_at(cursor, cx);
11018 let tab_size = if settings.hard_tabs {
11019 IndentSize::tab()
11020 } else {
11021 let tab_size = settings.tab_size.get();
11022 let indent_remainder = snapshot
11023 .text_for_range(Point::new(cursor.row, 0)..cursor)
11024 .flat_map(str::chars)
11025 .fold(row_delta % tab_size, |counter: u32, c| {
11026 if c == '\t' {
11027 0
11028 } else {
11029 (counter + 1) % tab_size
11030 }
11031 });
11032
11033 let chars_to_next_tab_stop = tab_size - indent_remainder;
11034 IndentSize::spaces(chars_to_next_tab_stop)
11035 };
11036 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
11037 selection.end = selection.start;
11038 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
11039 row_delta += tab_size.len;
11040 }
11041
11042 self.transact(window, cx, |this, window, cx| {
11043 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
11044 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11045 this.refresh_edit_prediction(true, false, window, cx);
11046 });
11047 }
11048
11049 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
11050 if self.read_only(cx) {
11051 return;
11052 }
11053 if self.mode.is_single_line() {
11054 cx.propagate();
11055 return;
11056 }
11057
11058 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11059 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
11060 let mut prev_edited_row = 0;
11061 let mut row_delta = 0;
11062 let mut edits = Vec::new();
11063 let buffer = self.buffer.read(cx);
11064 let snapshot = buffer.snapshot(cx);
11065 for selection in &mut selections {
11066 if selection.start.row != prev_edited_row {
11067 row_delta = 0;
11068 }
11069 prev_edited_row = selection.end.row;
11070
11071 row_delta =
11072 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
11073 }
11074
11075 self.transact(window, cx, |this, window, cx| {
11076 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
11077 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11078 });
11079 }
11080
11081 fn indent_selection(
11082 buffer: &MultiBuffer,
11083 snapshot: &MultiBufferSnapshot,
11084 selection: &mut Selection<Point>,
11085 edits: &mut Vec<(Range<Point>, String)>,
11086 delta_for_start_row: u32,
11087 cx: &App,
11088 ) -> u32 {
11089 let settings = buffer.language_settings_at(selection.start, cx);
11090 let tab_size = settings.tab_size.get();
11091 let indent_kind = if settings.hard_tabs {
11092 IndentKind::Tab
11093 } else {
11094 IndentKind::Space
11095 };
11096 let mut start_row = selection.start.row;
11097 let mut end_row = selection.end.row + 1;
11098
11099 // If a selection ends at the beginning of a line, don't indent
11100 // that last line.
11101 if selection.end.column == 0 && selection.end.row > selection.start.row {
11102 end_row -= 1;
11103 }
11104
11105 // Avoid re-indenting a row that has already been indented by a
11106 // previous selection, but still update this selection's column
11107 // to reflect that indentation.
11108 if delta_for_start_row > 0 {
11109 start_row += 1;
11110 selection.start.column += delta_for_start_row;
11111 if selection.end.row == selection.start.row {
11112 selection.end.column += delta_for_start_row;
11113 }
11114 }
11115
11116 let mut delta_for_end_row = 0;
11117 let has_multiple_rows = start_row + 1 != end_row;
11118 for row in start_row..end_row {
11119 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
11120 let indent_delta = match (current_indent.kind, indent_kind) {
11121 (IndentKind::Space, IndentKind::Space) => {
11122 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
11123 IndentSize::spaces(columns_to_next_tab_stop)
11124 }
11125 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
11126 (_, IndentKind::Tab) => IndentSize::tab(),
11127 };
11128
11129 let start = if has_multiple_rows || current_indent.len < selection.start.column {
11130 0
11131 } else {
11132 selection.start.column
11133 };
11134 let row_start = Point::new(row, start);
11135 edits.push((
11136 row_start..row_start,
11137 indent_delta.chars().collect::<String>(),
11138 ));
11139
11140 // Update this selection's endpoints to reflect the indentation.
11141 if row == selection.start.row {
11142 selection.start.column += indent_delta.len;
11143 }
11144 if row == selection.end.row {
11145 selection.end.column += indent_delta.len;
11146 delta_for_end_row = indent_delta.len;
11147 }
11148 }
11149
11150 if selection.start.row == selection.end.row {
11151 delta_for_start_row + delta_for_end_row
11152 } else {
11153 delta_for_end_row
11154 }
11155 }
11156
11157 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
11158 if self.read_only(cx) {
11159 return;
11160 }
11161 if self.mode.is_single_line() {
11162 cx.propagate();
11163 return;
11164 }
11165
11166 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11167 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11168 let selections = self.selections.all::<Point>(&display_map);
11169 let mut deletion_ranges = Vec::new();
11170 let mut last_outdent = None;
11171 {
11172 let buffer = self.buffer.read(cx);
11173 let snapshot = buffer.snapshot(cx);
11174 for selection in &selections {
11175 let settings = buffer.language_settings_at(selection.start, cx);
11176 let tab_size = settings.tab_size.get();
11177 let mut rows = selection.spanned_rows(false, &display_map);
11178
11179 // Avoid re-outdenting a row that has already been outdented by a
11180 // previous selection.
11181 if let Some(last_row) = last_outdent
11182 && last_row == rows.start
11183 {
11184 rows.start = rows.start.next_row();
11185 }
11186 let has_multiple_rows = rows.len() > 1;
11187 for row in rows.iter_rows() {
11188 let indent_size = snapshot.indent_size_for_line(row);
11189 if indent_size.len > 0 {
11190 let deletion_len = match indent_size.kind {
11191 IndentKind::Space => {
11192 let columns_to_prev_tab_stop = indent_size.len % tab_size;
11193 if columns_to_prev_tab_stop == 0 {
11194 tab_size
11195 } else {
11196 columns_to_prev_tab_stop
11197 }
11198 }
11199 IndentKind::Tab => 1,
11200 };
11201 let start = if has_multiple_rows
11202 || deletion_len > selection.start.column
11203 || indent_size.len < selection.start.column
11204 {
11205 0
11206 } else {
11207 selection.start.column - deletion_len
11208 };
11209 deletion_ranges.push(
11210 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
11211 );
11212 last_outdent = Some(row);
11213 }
11214 }
11215 }
11216 }
11217
11218 self.transact(window, cx, |this, window, cx| {
11219 this.buffer.update(cx, |buffer, cx| {
11220 let empty_str: Arc<str> = Arc::default();
11221 buffer.edit(
11222 deletion_ranges
11223 .into_iter()
11224 .map(|range| (range, empty_str.clone())),
11225 None,
11226 cx,
11227 );
11228 });
11229 let selections = this
11230 .selections
11231 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11232 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11233 });
11234 }
11235
11236 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
11237 if self.read_only(cx) {
11238 return;
11239 }
11240 if self.mode.is_single_line() {
11241 cx.propagate();
11242 return;
11243 }
11244
11245 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11246 let selections = self
11247 .selections
11248 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11249 .into_iter()
11250 .map(|s| s.range());
11251
11252 self.transact(window, cx, |this, window, cx| {
11253 this.buffer.update(cx, |buffer, cx| {
11254 buffer.autoindent_ranges(selections, cx);
11255 });
11256 let selections = this
11257 .selections
11258 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11259 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11260 });
11261 }
11262
11263 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
11264 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11265 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11266 let selections = self.selections.all::<Point>(&display_map);
11267
11268 let mut new_cursors = Vec::new();
11269 let mut edit_ranges = Vec::new();
11270 let mut selections = selections.iter().peekable();
11271 while let Some(selection) = selections.next() {
11272 let mut rows = selection.spanned_rows(false, &display_map);
11273
11274 // Accumulate contiguous regions of rows that we want to delete.
11275 while let Some(next_selection) = selections.peek() {
11276 let next_rows = next_selection.spanned_rows(false, &display_map);
11277 if next_rows.start <= rows.end {
11278 rows.end = next_rows.end;
11279 selections.next().unwrap();
11280 } else {
11281 break;
11282 }
11283 }
11284
11285 let buffer = display_map.buffer_snapshot();
11286 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
11287 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
11288 // If there's a line after the range, delete the \n from the end of the row range
11289 (
11290 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
11291 rows.end,
11292 )
11293 } else {
11294 // If there isn't a line after the range, delete the \n from the line before the
11295 // start of the row range
11296 edit_start = edit_start.saturating_sub_usize(1);
11297 (buffer.len(), rows.start.previous_row())
11298 };
11299
11300 let text_layout_details = self.text_layout_details(window, cx);
11301 let x = display_map.x_for_display_point(
11302 selection.head().to_display_point(&display_map),
11303 &text_layout_details,
11304 );
11305 let row = Point::new(target_row.0, 0)
11306 .to_display_point(&display_map)
11307 .row();
11308 let column = display_map.display_column_for_x(row, x, &text_layout_details);
11309
11310 new_cursors.push((
11311 selection.id,
11312 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
11313 SelectionGoal::None,
11314 ));
11315 edit_ranges.push(edit_start..edit_end);
11316 }
11317
11318 self.transact(window, cx, |this, window, cx| {
11319 let buffer = this.buffer.update(cx, |buffer, cx| {
11320 let empty_str: Arc<str> = Arc::default();
11321 buffer.edit(
11322 edit_ranges
11323 .into_iter()
11324 .map(|range| (range, empty_str.clone())),
11325 None,
11326 cx,
11327 );
11328 buffer.snapshot(cx)
11329 });
11330 let new_selections = new_cursors
11331 .into_iter()
11332 .map(|(id, cursor, goal)| {
11333 let cursor = cursor.to_point(&buffer);
11334 Selection {
11335 id,
11336 start: cursor,
11337 end: cursor,
11338 reversed: false,
11339 goal,
11340 }
11341 })
11342 .collect();
11343
11344 this.change_selections(Default::default(), window, cx, |s| {
11345 s.select(new_selections);
11346 });
11347 });
11348 }
11349
11350 pub fn join_lines_impl(
11351 &mut self,
11352 insert_whitespace: bool,
11353 window: &mut Window,
11354 cx: &mut Context<Self>,
11355 ) {
11356 if self.read_only(cx) {
11357 return;
11358 }
11359 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
11360 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
11361 let start = MultiBufferRow(selection.start.row);
11362 // Treat single line selections as if they include the next line. Otherwise this action
11363 // would do nothing for single line selections individual cursors.
11364 let end = if selection.start.row == selection.end.row {
11365 MultiBufferRow(selection.start.row + 1)
11366 } else if selection.end.column == 0 {
11367 // If the selection ends at the start of a line, it's logically at the end of the
11368 // previous line (plus its newline).
11369 // Don't include the end line unless there's only one line selected.
11370 if selection.start.row + 1 == selection.end.row {
11371 MultiBufferRow(selection.end.row)
11372 } else {
11373 MultiBufferRow(selection.end.row - 1)
11374 }
11375 } else {
11376 MultiBufferRow(selection.end.row)
11377 };
11378
11379 if let Some(last_row_range) = row_ranges.last_mut()
11380 && start <= last_row_range.end
11381 {
11382 last_row_range.end = end;
11383 continue;
11384 }
11385 row_ranges.push(start..end);
11386 }
11387
11388 let snapshot = self.buffer.read(cx).snapshot(cx);
11389 let mut cursor_positions = Vec::new();
11390 for row_range in &row_ranges {
11391 let anchor = snapshot.anchor_before(Point::new(
11392 row_range.end.previous_row().0,
11393 snapshot.line_len(row_range.end.previous_row()),
11394 ));
11395 cursor_positions.push(anchor..anchor);
11396 }
11397
11398 self.transact(window, cx, |this, window, cx| {
11399 for row_range in row_ranges.into_iter().rev() {
11400 for row in row_range.iter_rows().rev() {
11401 let end_of_line = Point::new(row.0, snapshot.line_len(row));
11402 let next_line_row = row.next_row();
11403 let indent = snapshot.indent_size_for_line(next_line_row);
11404 let mut join_start_column = indent.len;
11405
11406 if let Some(language_scope) =
11407 snapshot.language_scope_at(Point::new(next_line_row.0, indent.len))
11408 {
11409 let line_end =
11410 Point::new(next_line_row.0, snapshot.line_len(next_line_row));
11411 let line_text_after_indent = snapshot
11412 .text_for_range(Point::new(next_line_row.0, indent.len)..line_end)
11413 .collect::<String>();
11414
11415 if !line_text_after_indent.is_empty() {
11416 let block_prefix = language_scope
11417 .block_comment()
11418 .map(|c| c.prefix.as_ref())
11419 .filter(|p| !p.is_empty());
11420 let doc_prefix = language_scope
11421 .documentation_comment()
11422 .map(|c| c.prefix.as_ref())
11423 .filter(|p| !p.is_empty());
11424 let all_prefixes = language_scope
11425 .line_comment_prefixes()
11426 .iter()
11427 .map(|p| p.as_ref())
11428 .chain(block_prefix)
11429 .chain(doc_prefix)
11430 .chain(language_scope.unordered_list().iter().map(|p| p.as_ref()));
11431
11432 let mut longest_prefix_len = None;
11433 for prefix in all_prefixes {
11434 let trimmed = prefix.trim_end();
11435 if line_text_after_indent.starts_with(trimmed) {
11436 let candidate_len =
11437 if line_text_after_indent.starts_with(prefix) {
11438 prefix.len()
11439 } else {
11440 trimmed.len()
11441 };
11442 if longest_prefix_len.map_or(true, |len| candidate_len > len) {
11443 longest_prefix_len = Some(candidate_len);
11444 }
11445 }
11446 }
11447
11448 if let Some(prefix_len) = longest_prefix_len {
11449 join_start_column =
11450 join_start_column.saturating_add(prefix_len as u32);
11451 }
11452 }
11453 }
11454
11455 let start_of_next_line = Point::new(next_line_row.0, join_start_column);
11456
11457 let replace = if snapshot.line_len(next_line_row) > join_start_column
11458 && insert_whitespace
11459 {
11460 " "
11461 } else {
11462 ""
11463 };
11464
11465 this.buffer.update(cx, |buffer, cx| {
11466 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
11467 });
11468 }
11469 }
11470
11471 this.change_selections(Default::default(), window, cx, |s| {
11472 s.select_anchor_ranges(cursor_positions)
11473 });
11474 });
11475 }
11476
11477 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
11478 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11479 self.join_lines_impl(true, window, cx);
11480 }
11481
11482 pub fn sort_lines_case_sensitive(
11483 &mut self,
11484 _: &SortLinesCaseSensitive,
11485 window: &mut Window,
11486 cx: &mut Context<Self>,
11487 ) {
11488 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
11489 }
11490
11491 pub fn sort_lines_by_length(
11492 &mut self,
11493 _: &SortLinesByLength,
11494 window: &mut Window,
11495 cx: &mut Context<Self>,
11496 ) {
11497 self.manipulate_immutable_lines(window, cx, |lines| {
11498 lines.sort_by_key(|&line| line.chars().count())
11499 })
11500 }
11501
11502 pub fn sort_lines_case_insensitive(
11503 &mut self,
11504 _: &SortLinesCaseInsensitive,
11505 window: &mut Window,
11506 cx: &mut Context<Self>,
11507 ) {
11508 self.manipulate_immutable_lines(window, cx, |lines| {
11509 lines.sort_by_key(|line| line.to_lowercase())
11510 })
11511 }
11512
11513 pub fn unique_lines_case_insensitive(
11514 &mut self,
11515 _: &UniqueLinesCaseInsensitive,
11516 window: &mut Window,
11517 cx: &mut Context<Self>,
11518 ) {
11519 self.manipulate_immutable_lines(window, cx, |lines| {
11520 let mut seen = HashSet::default();
11521 lines.retain(|line| seen.insert(line.to_lowercase()));
11522 })
11523 }
11524
11525 pub fn unique_lines_case_sensitive(
11526 &mut self,
11527 _: &UniqueLinesCaseSensitive,
11528 window: &mut Window,
11529 cx: &mut Context<Self>,
11530 ) {
11531 self.manipulate_immutable_lines(window, cx, |lines| {
11532 let mut seen = HashSet::default();
11533 lines.retain(|line| seen.insert(*line));
11534 })
11535 }
11536
11537 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
11538 let snapshot = self.buffer.read(cx).snapshot(cx);
11539 for selection in self.selections.disjoint_anchors_arc().iter() {
11540 if snapshot
11541 .language_at(selection.start)
11542 .and_then(|lang| lang.config().wrap_characters.as_ref())
11543 .is_some()
11544 {
11545 return true;
11546 }
11547 }
11548 false
11549 }
11550
11551 fn wrap_selections_in_tag(
11552 &mut self,
11553 _: &WrapSelectionsInTag,
11554 window: &mut Window,
11555 cx: &mut Context<Self>,
11556 ) {
11557 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11558
11559 let snapshot = self.buffer.read(cx).snapshot(cx);
11560
11561 let mut edits = Vec::new();
11562 let mut boundaries = Vec::new();
11563
11564 for selection in self
11565 .selections
11566 .all_adjusted(&self.display_snapshot(cx))
11567 .iter()
11568 {
11569 let Some(wrap_config) = snapshot
11570 .language_at(selection.start)
11571 .and_then(|lang| lang.config().wrap_characters.clone())
11572 else {
11573 continue;
11574 };
11575
11576 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
11577 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
11578
11579 let start_before = snapshot.anchor_before(selection.start);
11580 let end_after = snapshot.anchor_after(selection.end);
11581
11582 edits.push((start_before..start_before, open_tag));
11583 edits.push((end_after..end_after, close_tag));
11584
11585 boundaries.push((
11586 start_before,
11587 end_after,
11588 wrap_config.start_prefix.len(),
11589 wrap_config.end_suffix.len(),
11590 ));
11591 }
11592
11593 if edits.is_empty() {
11594 return;
11595 }
11596
11597 self.transact(window, cx, |this, window, cx| {
11598 let buffer = this.buffer.update(cx, |buffer, cx| {
11599 buffer.edit(edits, None, cx);
11600 buffer.snapshot(cx)
11601 });
11602
11603 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
11604 for (start_before, end_after, start_prefix_len, end_suffix_len) in
11605 boundaries.into_iter()
11606 {
11607 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
11608 let close_offset = end_after
11609 .to_offset(&buffer)
11610 .saturating_sub_usize(end_suffix_len);
11611 new_selections.push(open_offset..open_offset);
11612 new_selections.push(close_offset..close_offset);
11613 }
11614
11615 this.change_selections(Default::default(), window, cx, |s| {
11616 s.select_ranges(new_selections);
11617 });
11618
11619 this.request_autoscroll(Autoscroll::fit(), cx);
11620 });
11621 }
11622
11623 pub fn toggle_read_only(
11624 &mut self,
11625 _: &workspace::ToggleReadOnlyFile,
11626 _: &mut Window,
11627 cx: &mut Context<Self>,
11628 ) {
11629 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
11630 buffer.update(cx, |buffer, cx| {
11631 buffer.set_capability(
11632 match buffer.capability() {
11633 Capability::ReadWrite => Capability::Read,
11634 Capability::Read => Capability::ReadWrite,
11635 Capability::ReadOnly => Capability::ReadOnly,
11636 },
11637 cx,
11638 );
11639 })
11640 }
11641 }
11642
11643 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11644 let Some(project) = self.project.clone() else {
11645 return;
11646 };
11647 let task = self.reload(project, window, cx);
11648 self.detach_and_notify_err(task, window, cx);
11649 }
11650
11651 pub fn restore_file(
11652 &mut self,
11653 _: &::git::RestoreFile,
11654 window: &mut Window,
11655 cx: &mut Context<Self>,
11656 ) {
11657 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11658 let mut buffer_ids = HashSet::default();
11659 let snapshot = self.buffer().read(cx).snapshot(cx);
11660 for selection in self
11661 .selections
11662 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11663 {
11664 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11665 }
11666
11667 let buffer = self.buffer().read(cx);
11668 let ranges = buffer_ids
11669 .into_iter()
11670 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11671 .collect::<Vec<_>>();
11672
11673 self.restore_hunks_in_ranges(ranges, window, cx);
11674 }
11675
11676 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11677 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11678 let selections = self
11679 .selections
11680 .all(&self.display_snapshot(cx))
11681 .into_iter()
11682 .map(|s| s.range())
11683 .collect();
11684 self.restore_hunks_in_ranges(selections, window, cx);
11685 }
11686
11687 /// Restores the diff hunks in the editor's selections and moves the cursor
11688 /// to the next diff hunk. Wraps around to the beginning of the buffer if
11689 /// not all diff hunks are expanded.
11690 pub fn restore_and_next(
11691 &mut self,
11692 _: &::git::RestoreAndNext,
11693 window: &mut Window,
11694 cx: &mut Context<Self>,
11695 ) {
11696 let selections = self
11697 .selections
11698 .all(&self.display_snapshot(cx))
11699 .into_iter()
11700 .map(|selection| selection.range())
11701 .collect();
11702
11703 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11704 self.restore_hunks_in_ranges(selections, window, cx);
11705
11706 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
11707 let wrap_around = !all_diff_hunks_expanded;
11708 let snapshot = self.snapshot(window, cx);
11709 let position = self
11710 .selections
11711 .newest::<Point>(&snapshot.display_snapshot)
11712 .head();
11713
11714 self.go_to_hunk_before_or_after_position(
11715 &snapshot,
11716 position,
11717 Direction::Next,
11718 wrap_around,
11719 window,
11720 cx,
11721 );
11722 }
11723
11724 pub fn restore_hunks_in_ranges(
11725 &mut self,
11726 ranges: Vec<Range<Point>>,
11727 window: &mut Window,
11728 cx: &mut Context<Editor>,
11729 ) {
11730 if self.delegate_stage_and_restore {
11731 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11732 if !hunks.is_empty() {
11733 cx.emit(EditorEvent::RestoreRequested { hunks });
11734 }
11735 return;
11736 }
11737 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11738 self.transact(window, cx, |editor, window, cx| {
11739 editor.restore_diff_hunks(hunks, cx);
11740 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
11741 selections.refresh()
11742 });
11743 });
11744 }
11745
11746 pub(crate) fn restore_diff_hunks(&self, hunks: Vec<MultiBufferDiffHunk>, cx: &mut App) {
11747 let mut revert_changes = HashMap::default();
11748 let chunk_by = hunks.into_iter().chunk_by(|hunk| hunk.buffer_id);
11749 for (buffer_id, hunks) in &chunk_by {
11750 let hunks = hunks.collect::<Vec<_>>();
11751 for hunk in &hunks {
11752 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11753 }
11754 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11755 }
11756 if !revert_changes.is_empty() {
11757 self.buffer().update(cx, |multi_buffer, cx| {
11758 for (buffer_id, changes) in revert_changes {
11759 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
11760 buffer.update(cx, |buffer, cx| {
11761 buffer.edit(
11762 changes
11763 .into_iter()
11764 .map(|(range, text)| (range, text.to_string())),
11765 None,
11766 cx,
11767 );
11768 });
11769 }
11770 }
11771 });
11772 }
11773 }
11774
11775 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11776 if let Some(status) = self
11777 .addons
11778 .iter()
11779 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11780 {
11781 return Some(status);
11782 }
11783 self.project
11784 .as_ref()?
11785 .read(cx)
11786 .status_for_buffer_id(buffer_id, cx)
11787 }
11788
11789 pub fn open_active_item_in_terminal(
11790 &mut self,
11791 _: &OpenInTerminal,
11792 window: &mut Window,
11793 cx: &mut Context<Self>,
11794 ) {
11795 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11796 let project_path = buffer.read(cx).project_path(cx)?;
11797 let project = self.project()?.read(cx);
11798 let entry = project.entry_for_path(&project_path, cx)?;
11799 let parent = match &entry.canonical_path {
11800 Some(canonical_path) => canonical_path.to_path_buf(),
11801 None => project.absolute_path(&project_path, cx)?,
11802 }
11803 .parent()?
11804 .to_path_buf();
11805 Some(parent)
11806 }) {
11807 window.dispatch_action(
11808 OpenTerminal {
11809 working_directory,
11810 local: false,
11811 }
11812 .boxed_clone(),
11813 cx,
11814 );
11815 }
11816 }
11817
11818 fn set_breakpoint_context_menu(
11819 &mut self,
11820 display_row: DisplayRow,
11821 position: Option<Anchor>,
11822 clicked_point: gpui::Point<Pixels>,
11823 window: &mut Window,
11824 cx: &mut Context<Self>,
11825 ) {
11826 let source = self
11827 .buffer
11828 .read(cx)
11829 .snapshot(cx)
11830 .anchor_before(Point::new(display_row.0, 0u32));
11831
11832 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11833
11834 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11835 self,
11836 source,
11837 clicked_point,
11838 context_menu,
11839 window,
11840 cx,
11841 );
11842 }
11843
11844 fn add_edit_breakpoint_block(
11845 &mut self,
11846 anchor: Anchor,
11847 breakpoint: &Breakpoint,
11848 edit_action: BreakpointPromptEditAction,
11849 window: &mut Window,
11850 cx: &mut Context<Self>,
11851 ) {
11852 let weak_editor = cx.weak_entity();
11853 let bp_prompt = cx.new(|cx| {
11854 BreakpointPromptEditor::new(
11855 weak_editor,
11856 anchor,
11857 breakpoint.clone(),
11858 edit_action,
11859 window,
11860 cx,
11861 )
11862 });
11863
11864 let height = bp_prompt.update(cx, |this, cx| {
11865 this.prompt
11866 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11867 });
11868 let cloned_prompt = bp_prompt.clone();
11869 let blocks = vec![BlockProperties {
11870 style: BlockStyle::Sticky,
11871 placement: BlockPlacement::Above(anchor),
11872 height: Some(height),
11873 render: Arc::new(move |cx| {
11874 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11875 cloned_prompt.clone().into_any_element()
11876 }),
11877 priority: 0,
11878 }];
11879
11880 let focus_handle = bp_prompt.focus_handle(cx);
11881 window.focus(&focus_handle, cx);
11882
11883 let block_ids = self.insert_blocks(blocks, None, cx);
11884 bp_prompt.update(cx, |prompt, _| {
11885 prompt.add_block_ids(block_ids);
11886 });
11887 }
11888
11889 pub(crate) fn breakpoint_at_row(
11890 &self,
11891 row: u32,
11892 window: &mut Window,
11893 cx: &mut Context<Self>,
11894 ) -> Option<(Anchor, Breakpoint)> {
11895 let snapshot = self.snapshot(window, cx);
11896 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11897
11898 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11899 }
11900
11901 pub(crate) fn breakpoint_at_anchor(
11902 &self,
11903 breakpoint_position: Anchor,
11904 snapshot: &EditorSnapshot,
11905 cx: &mut Context<Self>,
11906 ) -> Option<(Anchor, Breakpoint)> {
11907 let buffer = self
11908 .buffer
11909 .read(cx)
11910 .buffer_for_anchor(breakpoint_position, cx)?;
11911
11912 let enclosing_excerpt = breakpoint_position.excerpt_id;
11913 let buffer_snapshot = buffer.read(cx).snapshot();
11914
11915 let row = buffer_snapshot
11916 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11917 .row;
11918
11919 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11920 let anchor_end = snapshot
11921 .buffer_snapshot()
11922 .anchor_after(Point::new(row, line_len));
11923
11924 self.breakpoint_store
11925 .as_ref()?
11926 .read_with(cx, |breakpoint_store, cx| {
11927 breakpoint_store
11928 .breakpoints(
11929 &buffer,
11930 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11931 &buffer_snapshot,
11932 cx,
11933 )
11934 .next()
11935 .and_then(|(bp, _)| {
11936 let breakpoint_row = buffer_snapshot
11937 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11938 .row;
11939
11940 if breakpoint_row == row {
11941 snapshot
11942 .buffer_snapshot()
11943 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11944 .map(|position| (position, bp.bp.clone()))
11945 } else {
11946 None
11947 }
11948 })
11949 })
11950 }
11951
11952 pub fn edit_log_breakpoint(
11953 &mut self,
11954 _: &EditLogBreakpoint,
11955 window: &mut Window,
11956 cx: &mut Context<Self>,
11957 ) {
11958 if self.breakpoint_store.is_none() {
11959 return;
11960 }
11961
11962 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11963 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11964 message: None,
11965 state: BreakpointState::Enabled,
11966 condition: None,
11967 hit_condition: None,
11968 });
11969
11970 self.add_edit_breakpoint_block(
11971 anchor,
11972 &breakpoint,
11973 BreakpointPromptEditAction::Log,
11974 window,
11975 cx,
11976 );
11977 }
11978 }
11979
11980 fn breakpoints_at_cursors(
11981 &self,
11982 window: &mut Window,
11983 cx: &mut Context<Self>,
11984 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11985 let snapshot = self.snapshot(window, cx);
11986 let cursors = self
11987 .selections
11988 .disjoint_anchors_arc()
11989 .iter()
11990 .map(|selection| {
11991 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11992
11993 let breakpoint_position = self
11994 .breakpoint_at_row(cursor_position.row, window, cx)
11995 .map(|bp| bp.0)
11996 .unwrap_or_else(|| {
11997 snapshot
11998 .display_snapshot
11999 .buffer_snapshot()
12000 .anchor_after(Point::new(cursor_position.row, 0))
12001 });
12002
12003 let breakpoint = self
12004 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
12005 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
12006
12007 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
12008 })
12009 // 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.
12010 .collect::<HashMap<Anchor, _>>();
12011
12012 cursors.into_iter().collect()
12013 }
12014
12015 pub fn enable_breakpoint(
12016 &mut self,
12017 _: &crate::actions::EnableBreakpoint,
12018 window: &mut Window,
12019 cx: &mut Context<Self>,
12020 ) {
12021 if self.breakpoint_store.is_none() {
12022 return;
12023 }
12024
12025 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
12026 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
12027 continue;
12028 };
12029 self.edit_breakpoint_at_anchor(
12030 anchor,
12031 breakpoint,
12032 BreakpointEditAction::InvertState,
12033 cx,
12034 );
12035 }
12036 }
12037
12038 pub fn align_selections(
12039 &mut self,
12040 _: &crate::actions::AlignSelections,
12041 window: &mut Window,
12042 cx: &mut Context<Self>,
12043 ) {
12044 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12045
12046 let display_snapshot = self.display_snapshot(cx);
12047
12048 struct CursorData {
12049 anchor: Anchor,
12050 point: Point,
12051 }
12052 let cursor_data: Vec<CursorData> = self
12053 .selections
12054 .disjoint_anchors()
12055 .iter()
12056 .map(|selection| {
12057 let anchor = if selection.reversed {
12058 selection.head()
12059 } else {
12060 selection.tail()
12061 };
12062 CursorData {
12063 anchor: anchor,
12064 point: anchor.to_point(&display_snapshot.buffer_snapshot()),
12065 }
12066 })
12067 .collect();
12068
12069 let rows_anchors_count: Vec<usize> = cursor_data
12070 .iter()
12071 .map(|cursor| cursor.point.row)
12072 .chunk_by(|&row| row)
12073 .into_iter()
12074 .map(|(_, group)| group.count())
12075 .collect();
12076 let max_columns = rows_anchors_count.iter().max().copied().unwrap_or(0);
12077 let mut rows_column_offset = vec![0; rows_anchors_count.len()];
12078 let mut edits = Vec::new();
12079
12080 for column_idx in 0..max_columns {
12081 let mut cursor_index = 0;
12082
12083 // Calculate target_column => position that the selections will go
12084 let mut target_column = 0;
12085 for (row_idx, cursor_count) in rows_anchors_count.iter().enumerate() {
12086 // Skip rows that don't have this column
12087 if column_idx >= *cursor_count {
12088 cursor_index += cursor_count;
12089 continue;
12090 }
12091
12092 let point = &cursor_data[cursor_index + column_idx].point;
12093 let adjusted_column = point.column + rows_column_offset[row_idx];
12094 if adjusted_column > target_column {
12095 target_column = adjusted_column;
12096 }
12097 cursor_index += cursor_count;
12098 }
12099
12100 // Collect edits for this column
12101 cursor_index = 0;
12102 for (row_idx, cursor_count) in rows_anchors_count.iter().enumerate() {
12103 // Skip rows that don't have this column
12104 if column_idx >= *cursor_count {
12105 cursor_index += *cursor_count;
12106 continue;
12107 }
12108
12109 let point = &cursor_data[cursor_index + column_idx].point;
12110 let spaces_needed = target_column - point.column - rows_column_offset[row_idx];
12111 if spaces_needed > 0 {
12112 let anchor = cursor_data[cursor_index + column_idx]
12113 .anchor
12114 .bias_left(&display_snapshot);
12115 edits.push((anchor..anchor, " ".repeat(spaces_needed as usize)));
12116 }
12117 rows_column_offset[row_idx] += spaces_needed;
12118
12119 cursor_index += *cursor_count;
12120 }
12121 }
12122
12123 if !edits.is_empty() {
12124 self.transact(window, cx, |editor, _window, cx| {
12125 editor.edit(edits, cx);
12126 });
12127 }
12128 }
12129
12130 pub fn disable_breakpoint(
12131 &mut self,
12132 _: &crate::actions::DisableBreakpoint,
12133 window: &mut Window,
12134 cx: &mut Context<Self>,
12135 ) {
12136 if self.breakpoint_store.is_none() {
12137 return;
12138 }
12139
12140 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
12141 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
12142 continue;
12143 };
12144 self.edit_breakpoint_at_anchor(
12145 anchor,
12146 breakpoint,
12147 BreakpointEditAction::InvertState,
12148 cx,
12149 );
12150 }
12151 }
12152
12153 pub fn toggle_breakpoint(
12154 &mut self,
12155 _: &crate::actions::ToggleBreakpoint,
12156 window: &mut Window,
12157 cx: &mut Context<Self>,
12158 ) {
12159 if self.breakpoint_store.is_none() {
12160 return;
12161 }
12162
12163 let snapshot = self.snapshot(window, cx);
12164 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
12165 if self.gutter_breakpoint_indicator.0.is_some() {
12166 let display_row = anchor
12167 .to_point(snapshot.buffer_snapshot())
12168 .to_display_point(&snapshot.display_snapshot)
12169 .row();
12170 self.update_breakpoint_collision_on_toggle(
12171 display_row,
12172 &BreakpointEditAction::Toggle,
12173 );
12174 }
12175
12176 if let Some(breakpoint) = breakpoint {
12177 self.edit_breakpoint_at_anchor(
12178 anchor,
12179 breakpoint,
12180 BreakpointEditAction::Toggle,
12181 cx,
12182 );
12183 } else {
12184 self.edit_breakpoint_at_anchor(
12185 anchor,
12186 Breakpoint::new_standard(),
12187 BreakpointEditAction::Toggle,
12188 cx,
12189 );
12190 }
12191 }
12192 }
12193
12194 fn update_breakpoint_collision_on_toggle(
12195 &mut self,
12196 display_row: DisplayRow,
12197 edit_action: &BreakpointEditAction,
12198 ) {
12199 if let Some(ref mut breakpoint_indicator) = self.gutter_breakpoint_indicator.0 {
12200 if breakpoint_indicator.display_row == display_row
12201 && matches!(edit_action, BreakpointEditAction::Toggle)
12202 {
12203 breakpoint_indicator.collides_with_existing_breakpoint =
12204 !breakpoint_indicator.collides_with_existing_breakpoint;
12205 }
12206 }
12207 }
12208
12209 pub fn edit_breakpoint_at_anchor(
12210 &mut self,
12211 breakpoint_position: Anchor,
12212 breakpoint: Breakpoint,
12213 edit_action: BreakpointEditAction,
12214 cx: &mut Context<Self>,
12215 ) {
12216 let Some(breakpoint_store) = &self.breakpoint_store else {
12217 return;
12218 };
12219
12220 let Some(buffer) = self
12221 .buffer
12222 .read(cx)
12223 .buffer_for_anchor(breakpoint_position, cx)
12224 else {
12225 return;
12226 };
12227
12228 breakpoint_store.update(cx, |breakpoint_store, cx| {
12229 breakpoint_store.toggle_breakpoint(
12230 buffer,
12231 BreakpointWithPosition {
12232 position: breakpoint_position.text_anchor,
12233 bp: breakpoint,
12234 },
12235 edit_action,
12236 cx,
12237 );
12238 });
12239
12240 cx.notify();
12241 }
12242
12243 #[cfg(any(test, feature = "test-support"))]
12244 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
12245 self.breakpoint_store.clone()
12246 }
12247
12248 pub fn prepare_restore_change(
12249 &self,
12250 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
12251 hunk: &MultiBufferDiffHunk,
12252 cx: &mut App,
12253 ) -> Option<()> {
12254 if hunk.is_created_file() {
12255 return None;
12256 }
12257 let buffer = self.buffer.read(cx);
12258 let diff = buffer.diff_for(hunk.buffer_id)?;
12259 let buffer = buffer.buffer(hunk.buffer_id)?;
12260 let buffer = buffer.read(cx);
12261 let original_text = diff
12262 .read(cx)
12263 .base_text(cx)
12264 .as_rope()
12265 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
12266 let buffer_snapshot = buffer.snapshot();
12267 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
12268 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
12269 probe
12270 .0
12271 .start
12272 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
12273 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
12274 }) {
12275 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
12276 Some(())
12277 } else {
12278 None
12279 }
12280 }
12281
12282 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
12283 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
12284 }
12285
12286 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
12287 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
12288 }
12289
12290 pub fn rotate_selections_forward(
12291 &mut self,
12292 _: &RotateSelectionsForward,
12293 window: &mut Window,
12294 cx: &mut Context<Self>,
12295 ) {
12296 self.rotate_selections(window, cx, false)
12297 }
12298
12299 pub fn rotate_selections_backward(
12300 &mut self,
12301 _: &RotateSelectionsBackward,
12302 window: &mut Window,
12303 cx: &mut Context<Self>,
12304 ) {
12305 self.rotate_selections(window, cx, true)
12306 }
12307
12308 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
12309 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12310 let display_snapshot = self.display_snapshot(cx);
12311 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
12312
12313 if selections.len() < 2 {
12314 return;
12315 }
12316
12317 let (edits, new_selections) = {
12318 let buffer = self.buffer.read(cx).read(cx);
12319 let has_selections = selections.iter().any(|s| !s.is_empty());
12320 if has_selections {
12321 let mut selected_texts: Vec<String> = selections
12322 .iter()
12323 .map(|selection| {
12324 buffer
12325 .text_for_range(selection.start..selection.end)
12326 .collect()
12327 })
12328 .collect();
12329
12330 if reverse {
12331 selected_texts.rotate_left(1);
12332 } else {
12333 selected_texts.rotate_right(1);
12334 }
12335
12336 let mut offset_delta: i64 = 0;
12337 let mut new_selections = Vec::new();
12338 let edits: Vec<_> = selections
12339 .iter()
12340 .zip(selected_texts.iter())
12341 .map(|(selection, new_text)| {
12342 let old_len = (selection.end.0 - selection.start.0) as i64;
12343 let new_len = new_text.len() as i64;
12344 let adjusted_start =
12345 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
12346 let adjusted_end =
12347 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
12348
12349 new_selections.push(Selection {
12350 id: selection.id,
12351 start: adjusted_start,
12352 end: adjusted_end,
12353 reversed: selection.reversed,
12354 goal: selection.goal,
12355 });
12356
12357 offset_delta += new_len - old_len;
12358 (selection.start..selection.end, new_text.clone())
12359 })
12360 .collect();
12361 (edits, new_selections)
12362 } else {
12363 let mut all_rows: Vec<u32> = selections
12364 .iter()
12365 .map(|selection| buffer.offset_to_point(selection.start).row)
12366 .collect();
12367 all_rows.sort_unstable();
12368 all_rows.dedup();
12369
12370 if all_rows.len() < 2 {
12371 return;
12372 }
12373
12374 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
12375 .iter()
12376 .map(|&row| {
12377 let start = Point::new(row, 0);
12378 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12379 buffer.point_to_offset(start)..buffer.point_to_offset(end)
12380 })
12381 .collect();
12382
12383 let mut line_texts: Vec<String> = line_ranges
12384 .iter()
12385 .map(|range| buffer.text_for_range(range.clone()).collect())
12386 .collect();
12387
12388 if reverse {
12389 line_texts.rotate_left(1);
12390 } else {
12391 line_texts.rotate_right(1);
12392 }
12393
12394 let edits = line_ranges
12395 .iter()
12396 .zip(line_texts.iter())
12397 .map(|(range, new_text)| (range.clone(), new_text.clone()))
12398 .collect();
12399
12400 let num_rows = all_rows.len();
12401 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
12402 .iter()
12403 .enumerate()
12404 .map(|(i, &row)| (row, i))
12405 .collect();
12406
12407 // Compute new line start offsets after rotation (handles CRLF)
12408 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
12409 let first_line_start = line_ranges[0].start.0;
12410 let mut new_line_starts: Vec<usize> = vec![first_line_start];
12411 for text in line_texts.iter().take(num_rows - 1) {
12412 let prev_start = *new_line_starts.last().unwrap();
12413 new_line_starts.push(prev_start + text.len() + newline_len);
12414 }
12415
12416 let new_selections = selections
12417 .iter()
12418 .map(|selection| {
12419 let point = buffer.offset_to_point(selection.start);
12420 let old_index = row_to_index[&point.row];
12421 let new_index = if reverse {
12422 (old_index + num_rows - 1) % num_rows
12423 } else {
12424 (old_index + 1) % num_rows
12425 };
12426 let new_offset =
12427 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
12428 Selection {
12429 id: selection.id,
12430 start: new_offset,
12431 end: new_offset,
12432 reversed: selection.reversed,
12433 goal: selection.goal,
12434 }
12435 })
12436 .collect();
12437
12438 (edits, new_selections)
12439 }
12440 };
12441
12442 self.transact(window, cx, |this, window, cx| {
12443 this.buffer.update(cx, |buffer, cx| {
12444 buffer.edit(edits, None, cx);
12445 });
12446 this.change_selections(Default::default(), window, cx, |s| {
12447 s.select(new_selections);
12448 });
12449 });
12450 }
12451
12452 fn manipulate_lines<M>(
12453 &mut self,
12454 window: &mut Window,
12455 cx: &mut Context<Self>,
12456 mut manipulate: M,
12457 ) where
12458 M: FnMut(&str) -> LineManipulationResult,
12459 {
12460 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12461
12462 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12463 let buffer = self.buffer.read(cx).snapshot(cx);
12464
12465 let mut edits = Vec::new();
12466
12467 let selections = self.selections.all::<Point>(&display_map);
12468 let mut selections = selections.iter().peekable();
12469 let mut contiguous_row_selections = Vec::new();
12470 let mut new_selections = Vec::new();
12471 let mut added_lines = 0;
12472 let mut removed_lines = 0;
12473
12474 while let Some(selection) = selections.next() {
12475 let (start_row, end_row) = consume_contiguous_rows(
12476 &mut contiguous_row_selections,
12477 selection,
12478 &display_map,
12479 &mut selections,
12480 );
12481
12482 let start_point = Point::new(start_row.0, 0);
12483 let end_point = Point::new(
12484 end_row.previous_row().0,
12485 buffer.line_len(end_row.previous_row()),
12486 );
12487 let text = buffer
12488 .text_for_range(start_point..end_point)
12489 .collect::<String>();
12490
12491 let LineManipulationResult {
12492 new_text,
12493 line_count_before,
12494 line_count_after,
12495 } = manipulate(&text);
12496
12497 edits.push((start_point..end_point, new_text));
12498
12499 // Selections must change based on added and removed line count
12500 let start_row =
12501 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
12502 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
12503 new_selections.push(Selection {
12504 id: selection.id,
12505 start: start_row,
12506 end: end_row,
12507 goal: SelectionGoal::None,
12508 reversed: selection.reversed,
12509 });
12510
12511 if line_count_after > line_count_before {
12512 added_lines += line_count_after - line_count_before;
12513 } else if line_count_before > line_count_after {
12514 removed_lines += line_count_before - line_count_after;
12515 }
12516 }
12517
12518 self.transact(window, cx, |this, window, cx| {
12519 let buffer = this.buffer.update(cx, |buffer, cx| {
12520 buffer.edit(edits, None, cx);
12521 buffer.snapshot(cx)
12522 });
12523
12524 // Recalculate offsets on newly edited buffer
12525 let new_selections = new_selections
12526 .iter()
12527 .map(|s| {
12528 let start_point = Point::new(s.start.0, 0);
12529 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
12530 Selection {
12531 id: s.id,
12532 start: buffer.point_to_offset(start_point),
12533 end: buffer.point_to_offset(end_point),
12534 goal: s.goal,
12535 reversed: s.reversed,
12536 }
12537 })
12538 .collect();
12539
12540 this.change_selections(Default::default(), window, cx, |s| {
12541 s.select(new_selections);
12542 });
12543
12544 this.request_autoscroll(Autoscroll::fit(), cx);
12545 });
12546 }
12547
12548 fn manipulate_immutable_lines<Fn>(
12549 &mut self,
12550 window: &mut Window,
12551 cx: &mut Context<Self>,
12552 mut callback: Fn,
12553 ) where
12554 Fn: FnMut(&mut Vec<&str>),
12555 {
12556 self.manipulate_lines(window, cx, |text| {
12557 let mut lines: Vec<&str> = text.split('\n').collect();
12558 let line_count_before = lines.len();
12559
12560 callback(&mut lines);
12561
12562 LineManipulationResult {
12563 new_text: lines.join("\n"),
12564 line_count_before,
12565 line_count_after: lines.len(),
12566 }
12567 });
12568 }
12569
12570 fn manipulate_mutable_lines<Fn>(
12571 &mut self,
12572 window: &mut Window,
12573 cx: &mut Context<Self>,
12574 mut callback: Fn,
12575 ) where
12576 Fn: FnMut(&mut Vec<Cow<'_, str>>),
12577 {
12578 self.manipulate_lines(window, cx, |text| {
12579 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
12580 let line_count_before = lines.len();
12581
12582 callback(&mut lines);
12583
12584 LineManipulationResult {
12585 new_text: lines.join("\n"),
12586 line_count_before,
12587 line_count_after: lines.len(),
12588 }
12589 });
12590 }
12591
12592 pub fn convert_indentation_to_spaces(
12593 &mut self,
12594 _: &ConvertIndentationToSpaces,
12595 window: &mut Window,
12596 cx: &mut Context<Self>,
12597 ) {
12598 let settings = self.buffer.read(cx).language_settings(cx);
12599 let tab_size = settings.tab_size.get() as usize;
12600
12601 self.manipulate_mutable_lines(window, cx, |lines| {
12602 // Allocates a reasonably sized scratch buffer once for the whole loop
12603 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12604 // Avoids recomputing spaces that could be inserted many times
12605 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12606 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12607 .collect();
12608
12609 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12610 let mut chars = line.as_ref().chars();
12611 let mut col = 0;
12612 let mut changed = false;
12613
12614 for ch in chars.by_ref() {
12615 match ch {
12616 ' ' => {
12617 reindented_line.push(' ');
12618 col += 1;
12619 }
12620 '\t' => {
12621 // \t are converted to spaces depending on the current column
12622 let spaces_len = tab_size - (col % tab_size);
12623 reindented_line.extend(&space_cache[spaces_len - 1]);
12624 col += spaces_len;
12625 changed = true;
12626 }
12627 _ => {
12628 // If we dont append before break, the character is consumed
12629 reindented_line.push(ch);
12630 break;
12631 }
12632 }
12633 }
12634
12635 if !changed {
12636 reindented_line.clear();
12637 continue;
12638 }
12639 // Append the rest of the line and replace old reference with new one
12640 reindented_line.extend(chars);
12641 *line = Cow::Owned(reindented_line.clone());
12642 reindented_line.clear();
12643 }
12644 });
12645 }
12646
12647 pub fn convert_indentation_to_tabs(
12648 &mut self,
12649 _: &ConvertIndentationToTabs,
12650 window: &mut Window,
12651 cx: &mut Context<Self>,
12652 ) {
12653 let settings = self.buffer.read(cx).language_settings(cx);
12654 let tab_size = settings.tab_size.get() as usize;
12655
12656 self.manipulate_mutable_lines(window, cx, |lines| {
12657 // Allocates a reasonably sized buffer once for the whole loop
12658 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12659 // Avoids recomputing spaces that could be inserted many times
12660 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12661 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12662 .collect();
12663
12664 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12665 let mut chars = line.chars();
12666 let mut spaces_count = 0;
12667 let mut first_non_indent_char = None;
12668 let mut changed = false;
12669
12670 for ch in chars.by_ref() {
12671 match ch {
12672 ' ' => {
12673 // Keep track of spaces. Append \t when we reach tab_size
12674 spaces_count += 1;
12675 changed = true;
12676 if spaces_count == tab_size {
12677 reindented_line.push('\t');
12678 spaces_count = 0;
12679 }
12680 }
12681 '\t' => {
12682 reindented_line.push('\t');
12683 spaces_count = 0;
12684 }
12685 _ => {
12686 // Dont append it yet, we might have remaining spaces
12687 first_non_indent_char = Some(ch);
12688 break;
12689 }
12690 }
12691 }
12692
12693 if !changed {
12694 reindented_line.clear();
12695 continue;
12696 }
12697 // Remaining spaces that didn't make a full tab stop
12698 if spaces_count > 0 {
12699 reindented_line.extend(&space_cache[spaces_count - 1]);
12700 }
12701 // If we consume an extra character that was not indentation, add it back
12702 if let Some(extra_char) = first_non_indent_char {
12703 reindented_line.push(extra_char);
12704 }
12705 // Append the rest of the line and replace old reference with new one
12706 reindented_line.extend(chars);
12707 *line = Cow::Owned(reindented_line.clone());
12708 reindented_line.clear();
12709 }
12710 });
12711 }
12712
12713 pub fn convert_to_upper_case(
12714 &mut self,
12715 _: &ConvertToUpperCase,
12716 window: &mut Window,
12717 cx: &mut Context<Self>,
12718 ) {
12719 self.manipulate_text(window, cx, |text| text.to_uppercase())
12720 }
12721
12722 pub fn convert_to_lower_case(
12723 &mut self,
12724 _: &ConvertToLowerCase,
12725 window: &mut Window,
12726 cx: &mut Context<Self>,
12727 ) {
12728 self.manipulate_text(window, cx, |text| text.to_lowercase())
12729 }
12730
12731 pub fn convert_to_title_case(
12732 &mut self,
12733 _: &ConvertToTitleCase,
12734 window: &mut Window,
12735 cx: &mut Context<Self>,
12736 ) {
12737 self.manipulate_text(window, cx, |text| {
12738 Self::convert_text_case(text, Case::Title)
12739 })
12740 }
12741
12742 pub fn convert_to_snake_case(
12743 &mut self,
12744 _: &ConvertToSnakeCase,
12745 window: &mut Window,
12746 cx: &mut Context<Self>,
12747 ) {
12748 self.manipulate_text(window, cx, |text| {
12749 Self::convert_text_case(text, Case::Snake)
12750 })
12751 }
12752
12753 pub fn convert_to_kebab_case(
12754 &mut self,
12755 _: &ConvertToKebabCase,
12756 window: &mut Window,
12757 cx: &mut Context<Self>,
12758 ) {
12759 self.manipulate_text(window, cx, |text| {
12760 Self::convert_text_case(text, Case::Kebab)
12761 })
12762 }
12763
12764 pub fn convert_to_upper_camel_case(
12765 &mut self,
12766 _: &ConvertToUpperCamelCase,
12767 window: &mut Window,
12768 cx: &mut Context<Self>,
12769 ) {
12770 self.manipulate_text(window, cx, |text| {
12771 Self::convert_text_case(text, Case::UpperCamel)
12772 })
12773 }
12774
12775 pub fn convert_to_lower_camel_case(
12776 &mut self,
12777 _: &ConvertToLowerCamelCase,
12778 window: &mut Window,
12779 cx: &mut Context<Self>,
12780 ) {
12781 self.manipulate_text(window, cx, |text| {
12782 Self::convert_text_case(text, Case::Camel)
12783 })
12784 }
12785
12786 pub fn convert_to_opposite_case(
12787 &mut self,
12788 _: &ConvertToOppositeCase,
12789 window: &mut Window,
12790 cx: &mut Context<Self>,
12791 ) {
12792 self.manipulate_text(window, cx, |text| {
12793 text.chars()
12794 .fold(String::with_capacity(text.len()), |mut t, c| {
12795 if c.is_uppercase() {
12796 t.extend(c.to_lowercase());
12797 } else {
12798 t.extend(c.to_uppercase());
12799 }
12800 t
12801 })
12802 })
12803 }
12804
12805 pub fn convert_to_sentence_case(
12806 &mut self,
12807 _: &ConvertToSentenceCase,
12808 window: &mut Window,
12809 cx: &mut Context<Self>,
12810 ) {
12811 self.manipulate_text(window, cx, |text| {
12812 Self::convert_text_case(text, Case::Sentence)
12813 })
12814 }
12815
12816 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
12817 self.manipulate_text(window, cx, |text| {
12818 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
12819 if has_upper_case_characters {
12820 text.to_lowercase()
12821 } else {
12822 text.to_uppercase()
12823 }
12824 })
12825 }
12826
12827 pub fn convert_to_rot13(
12828 &mut self,
12829 _: &ConvertToRot13,
12830 window: &mut Window,
12831 cx: &mut Context<Self>,
12832 ) {
12833 self.manipulate_text(window, cx, |text| {
12834 text.chars()
12835 .map(|c| match c {
12836 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
12837 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
12838 _ => c,
12839 })
12840 .collect()
12841 })
12842 }
12843
12844 fn convert_text_case(text: &str, case: Case) -> String {
12845 text.lines()
12846 .map(|line| {
12847 let trimmed_start = line.trim_start();
12848 let leading = &line[..line.len() - trimmed_start.len()];
12849 let trimmed = trimmed_start.trim_end();
12850 let trailing = &trimmed_start[trimmed.len()..];
12851 format!("{}{}{}", leading, trimmed.to_case(case), trailing)
12852 })
12853 .join("\n")
12854 }
12855
12856 pub fn convert_to_rot47(
12857 &mut self,
12858 _: &ConvertToRot47,
12859 window: &mut Window,
12860 cx: &mut Context<Self>,
12861 ) {
12862 self.manipulate_text(window, cx, |text| {
12863 text.chars()
12864 .map(|c| {
12865 let code_point = c as u32;
12866 if code_point >= 33 && code_point <= 126 {
12867 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
12868 }
12869 c
12870 })
12871 .collect()
12872 })
12873 }
12874
12875 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
12876 where
12877 Fn: FnMut(&str) -> String,
12878 {
12879 let buffer = self.buffer.read(cx).snapshot(cx);
12880
12881 let mut new_selections = Vec::new();
12882 let mut edits = Vec::new();
12883 let mut selection_adjustment = 0isize;
12884
12885 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
12886 let selection_is_empty = selection.is_empty();
12887
12888 let (start, end) = if selection_is_empty {
12889 let (word_range, _) = buffer.surrounding_word(selection.start, None);
12890 (word_range.start, word_range.end)
12891 } else {
12892 (
12893 buffer.point_to_offset(selection.start),
12894 buffer.point_to_offset(selection.end),
12895 )
12896 };
12897
12898 let text = buffer.text_for_range(start..end).collect::<String>();
12899 let old_length = text.len() as isize;
12900 let text = callback(&text);
12901
12902 new_selections.push(Selection {
12903 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
12904 end: MultiBufferOffset(
12905 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
12906 ),
12907 goal: SelectionGoal::None,
12908 id: selection.id,
12909 reversed: selection.reversed,
12910 });
12911
12912 selection_adjustment += old_length - text.len() as isize;
12913
12914 edits.push((start..end, text));
12915 }
12916
12917 self.transact(window, cx, |this, window, cx| {
12918 this.buffer.update(cx, |buffer, cx| {
12919 buffer.edit(edits, None, cx);
12920 });
12921
12922 this.change_selections(Default::default(), window, cx, |s| {
12923 s.select(new_selections);
12924 });
12925
12926 this.request_autoscroll(Autoscroll::fit(), cx);
12927 });
12928 }
12929
12930 pub fn move_selection_on_drop(
12931 &mut self,
12932 selection: &Selection<Anchor>,
12933 target: DisplayPoint,
12934 is_cut: bool,
12935 window: &mut Window,
12936 cx: &mut Context<Self>,
12937 ) {
12938 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12939 let buffer = display_map.buffer_snapshot();
12940 let mut edits = Vec::new();
12941 let insert_point = display_map
12942 .clip_point(target, Bias::Left)
12943 .to_point(&display_map);
12944 let text = buffer
12945 .text_for_range(selection.start..selection.end)
12946 .collect::<String>();
12947 if is_cut {
12948 edits.push(((selection.start..selection.end), String::new()));
12949 }
12950 let insert_anchor = buffer.anchor_before(insert_point);
12951 edits.push(((insert_anchor..insert_anchor), text));
12952 let last_edit_start = insert_anchor.bias_left(buffer);
12953 let last_edit_end = insert_anchor.bias_right(buffer);
12954 self.transact(window, cx, |this, window, cx| {
12955 this.buffer.update(cx, |buffer, cx| {
12956 buffer.edit(edits, None, cx);
12957 });
12958 this.change_selections(Default::default(), window, cx, |s| {
12959 s.select_anchor_ranges([last_edit_start..last_edit_end]);
12960 });
12961 });
12962 }
12963
12964 pub fn clear_selection_drag_state(&mut self) {
12965 self.selection_drag_state = SelectionDragState::None;
12966 }
12967
12968 pub fn duplicate(
12969 &mut self,
12970 upwards: bool,
12971 whole_lines: bool,
12972 window: &mut Window,
12973 cx: &mut Context<Self>,
12974 ) {
12975 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12976
12977 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12978 let buffer = display_map.buffer_snapshot();
12979 let selections = self.selections.all::<Point>(&display_map);
12980
12981 let mut edits = Vec::new();
12982 let mut selections_iter = selections.iter().peekable();
12983 while let Some(selection) = selections_iter.next() {
12984 let mut rows = selection.spanned_rows(false, &display_map);
12985 // duplicate line-wise
12986 if whole_lines || selection.start == selection.end {
12987 // Avoid duplicating the same lines twice.
12988 while let Some(next_selection) = selections_iter.peek() {
12989 let next_rows = next_selection.spanned_rows(false, &display_map);
12990 if next_rows.start < rows.end {
12991 rows.end = next_rows.end;
12992 selections_iter.next().unwrap();
12993 } else {
12994 break;
12995 }
12996 }
12997
12998 // Copy the text from the selected row region and splice it either at the start
12999 // or end of the region.
13000 let start = Point::new(rows.start.0, 0);
13001 let end = Point::new(
13002 rows.end.previous_row().0,
13003 buffer.line_len(rows.end.previous_row()),
13004 );
13005
13006 let mut text = buffer.text_for_range(start..end).collect::<String>();
13007
13008 let insert_location = if upwards {
13009 // When duplicating upward, we need to insert before the current line.
13010 // If we're on the last line and it doesn't end with a newline,
13011 // we need to add a newline before the duplicated content.
13012 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
13013 && buffer.max_point().column > 0
13014 && !text.ends_with('\n');
13015
13016 if needs_leading_newline {
13017 text.insert(0, '\n');
13018 end
13019 } else {
13020 text.push('\n');
13021 Point::new(rows.start.0, 0)
13022 }
13023 } else {
13024 text.push('\n');
13025 start
13026 };
13027 edits.push((insert_location..insert_location, text));
13028 } else {
13029 // duplicate character-wise
13030 let start = selection.start;
13031 let end = selection.end;
13032 let text = buffer.text_for_range(start..end).collect::<String>();
13033 edits.push((selection.end..selection.end, text));
13034 }
13035 }
13036
13037 self.transact(window, cx, |this, window, cx| {
13038 this.buffer.update(cx, |buffer, cx| {
13039 buffer.edit(edits, None, cx);
13040 });
13041
13042 // When duplicating upward with whole lines, move the cursor to the duplicated line
13043 if upwards && whole_lines {
13044 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
13045
13046 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13047 let mut new_ranges = Vec::new();
13048 let selections = s.all::<Point>(&display_map);
13049 let mut selections_iter = selections.iter().peekable();
13050
13051 while let Some(first_selection) = selections_iter.next() {
13052 // Group contiguous selections together to find the total row span
13053 let mut group_selections = vec![first_selection];
13054 let mut rows = first_selection.spanned_rows(false, &display_map);
13055
13056 while let Some(next_selection) = selections_iter.peek() {
13057 let next_rows = next_selection.spanned_rows(false, &display_map);
13058 if next_rows.start < rows.end {
13059 rows.end = next_rows.end;
13060 group_selections.push(selections_iter.next().unwrap());
13061 } else {
13062 break;
13063 }
13064 }
13065
13066 let row_count = rows.end.0 - rows.start.0;
13067
13068 // Move all selections in this group up by the total number of duplicated rows
13069 for selection in group_selections {
13070 let new_start = Point::new(
13071 selection.start.row.saturating_sub(row_count),
13072 selection.start.column,
13073 );
13074
13075 let new_end = Point::new(
13076 selection.end.row.saturating_sub(row_count),
13077 selection.end.column,
13078 );
13079
13080 new_ranges.push(new_start..new_end);
13081 }
13082 }
13083
13084 s.select_ranges(new_ranges);
13085 });
13086 }
13087
13088 this.request_autoscroll(Autoscroll::fit(), cx);
13089 });
13090 }
13091
13092 pub fn duplicate_line_up(
13093 &mut self,
13094 _: &DuplicateLineUp,
13095 window: &mut Window,
13096 cx: &mut Context<Self>,
13097 ) {
13098 self.duplicate(true, true, window, cx);
13099 }
13100
13101 pub fn duplicate_line_down(
13102 &mut self,
13103 _: &DuplicateLineDown,
13104 window: &mut Window,
13105 cx: &mut Context<Self>,
13106 ) {
13107 self.duplicate(false, true, window, cx);
13108 }
13109
13110 pub fn duplicate_selection(
13111 &mut self,
13112 _: &DuplicateSelection,
13113 window: &mut Window,
13114 cx: &mut Context<Self>,
13115 ) {
13116 self.duplicate(false, false, window, cx);
13117 }
13118
13119 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
13120 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13121 if self.mode.is_single_line() {
13122 cx.propagate();
13123 return;
13124 }
13125
13126 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13127 let buffer = self.buffer.read(cx).snapshot(cx);
13128
13129 let mut edits = Vec::new();
13130 let mut unfold_ranges = Vec::new();
13131 let mut refold_creases = Vec::new();
13132
13133 let selections = self.selections.all::<Point>(&display_map);
13134 let mut selections = selections.iter().peekable();
13135 let mut contiguous_row_selections = Vec::new();
13136 let mut new_selections = Vec::new();
13137
13138 while let Some(selection) = selections.next() {
13139 // Find all the selections that span a contiguous row range
13140 let (start_row, end_row) = consume_contiguous_rows(
13141 &mut contiguous_row_selections,
13142 selection,
13143 &display_map,
13144 &mut selections,
13145 );
13146
13147 // Move the text spanned by the row range to be before the line preceding the row range
13148 if start_row.0 > 0 {
13149 let range_to_move = Point::new(
13150 start_row.previous_row().0,
13151 buffer.line_len(start_row.previous_row()),
13152 )
13153 ..Point::new(
13154 end_row.previous_row().0,
13155 buffer.line_len(end_row.previous_row()),
13156 );
13157 let insertion_point = display_map
13158 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
13159 .0;
13160
13161 // Don't move lines across excerpts
13162 if buffer
13163 .excerpt_containing(insertion_point..range_to_move.end)
13164 .is_some()
13165 {
13166 let text = buffer
13167 .text_for_range(range_to_move.clone())
13168 .flat_map(|s| s.chars())
13169 .skip(1)
13170 .chain(['\n'])
13171 .collect::<String>();
13172
13173 edits.push((
13174 buffer.anchor_after(range_to_move.start)
13175 ..buffer.anchor_before(range_to_move.end),
13176 String::new(),
13177 ));
13178 let insertion_anchor = buffer.anchor_after(insertion_point);
13179 edits.push((insertion_anchor..insertion_anchor, text));
13180
13181 let row_delta = range_to_move.start.row - insertion_point.row + 1;
13182
13183 // Move selections up
13184 new_selections.extend(contiguous_row_selections.drain(..).map(
13185 |mut selection| {
13186 selection.start.row -= row_delta;
13187 selection.end.row -= row_delta;
13188 selection
13189 },
13190 ));
13191
13192 // Move folds up
13193 unfold_ranges.push(range_to_move.clone());
13194 for fold in display_map.folds_in_range(
13195 buffer.anchor_before(range_to_move.start)
13196 ..buffer.anchor_after(range_to_move.end),
13197 ) {
13198 let mut start = fold.range.start.to_point(&buffer);
13199 let mut end = fold.range.end.to_point(&buffer);
13200 start.row -= row_delta;
13201 end.row -= row_delta;
13202 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
13203 }
13204 }
13205 }
13206
13207 // If we didn't move line(s), preserve the existing selections
13208 new_selections.append(&mut contiguous_row_selections);
13209 }
13210
13211 self.transact(window, cx, |this, window, cx| {
13212 this.unfold_ranges(&unfold_ranges, true, true, cx);
13213 this.buffer.update(cx, |buffer, cx| {
13214 for (range, text) in edits {
13215 buffer.edit([(range, text)], None, cx);
13216 }
13217 });
13218 this.fold_creases(refold_creases, true, window, cx);
13219 this.change_selections(Default::default(), window, cx, |s| {
13220 s.select(new_selections);
13221 })
13222 });
13223 }
13224
13225 pub fn move_line_down(
13226 &mut self,
13227 _: &MoveLineDown,
13228 window: &mut Window,
13229 cx: &mut Context<Self>,
13230 ) {
13231 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13232 if self.mode.is_single_line() {
13233 cx.propagate();
13234 return;
13235 }
13236
13237 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13238 let buffer = self.buffer.read(cx).snapshot(cx);
13239
13240 let mut edits = Vec::new();
13241 let mut unfold_ranges = Vec::new();
13242 let mut refold_creases = Vec::new();
13243
13244 let selections = self.selections.all::<Point>(&display_map);
13245 let mut selections = selections.iter().peekable();
13246 let mut contiguous_row_selections = Vec::new();
13247 let mut new_selections = Vec::new();
13248
13249 while let Some(selection) = selections.next() {
13250 // Find all the selections that span a contiguous row range
13251 let (start_row, end_row) = consume_contiguous_rows(
13252 &mut contiguous_row_selections,
13253 selection,
13254 &display_map,
13255 &mut selections,
13256 );
13257
13258 // Move the text spanned by the row range to be after the last line of the row range
13259 if end_row.0 <= buffer.max_point().row {
13260 let range_to_move =
13261 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
13262 let insertion_point = display_map
13263 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
13264 .0;
13265
13266 // Don't move lines across excerpt boundaries
13267 if buffer
13268 .excerpt_containing(range_to_move.start..insertion_point)
13269 .is_some()
13270 {
13271 let mut text = String::from("\n");
13272 text.extend(buffer.text_for_range(range_to_move.clone()));
13273 text.pop(); // Drop trailing newline
13274 edits.push((
13275 buffer.anchor_after(range_to_move.start)
13276 ..buffer.anchor_before(range_to_move.end),
13277 String::new(),
13278 ));
13279 let insertion_anchor = buffer.anchor_after(insertion_point);
13280 edits.push((insertion_anchor..insertion_anchor, text));
13281
13282 let row_delta = insertion_point.row - range_to_move.end.row + 1;
13283
13284 // Move selections down
13285 new_selections.extend(contiguous_row_selections.drain(..).map(
13286 |mut selection| {
13287 selection.start.row += row_delta;
13288 selection.end.row += row_delta;
13289 selection
13290 },
13291 ));
13292
13293 // Move folds down
13294 unfold_ranges.push(range_to_move.clone());
13295 for fold in display_map.folds_in_range(
13296 buffer.anchor_before(range_to_move.start)
13297 ..buffer.anchor_after(range_to_move.end),
13298 ) {
13299 let mut start = fold.range.start.to_point(&buffer);
13300 let mut end = fold.range.end.to_point(&buffer);
13301 start.row += row_delta;
13302 end.row += row_delta;
13303 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
13304 }
13305 }
13306 }
13307
13308 // If we didn't move line(s), preserve the existing selections
13309 new_selections.append(&mut contiguous_row_selections);
13310 }
13311
13312 self.transact(window, cx, |this, window, cx| {
13313 this.unfold_ranges(&unfold_ranges, true, true, cx);
13314 this.buffer.update(cx, |buffer, cx| {
13315 for (range, text) in edits {
13316 buffer.edit([(range, text)], None, cx);
13317 }
13318 });
13319 this.fold_creases(refold_creases, true, window, cx);
13320 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
13321 });
13322 }
13323
13324 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
13325 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13326 let text_layout_details = &self.text_layout_details(window, cx);
13327 self.transact(window, cx, |this, window, cx| {
13328 let edits = this.change_selections(Default::default(), window, cx, |s| {
13329 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
13330 s.move_with(&mut |display_map, selection| {
13331 if !selection.is_empty() {
13332 return;
13333 }
13334
13335 let mut head = selection.head();
13336 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
13337 if head.column() == display_map.line_len(head.row()) {
13338 transpose_offset = display_map
13339 .buffer_snapshot()
13340 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13341 }
13342
13343 if transpose_offset == MultiBufferOffset(0) {
13344 return;
13345 }
13346
13347 *head.column_mut() += 1;
13348 head = display_map.clip_point(head, Bias::Right);
13349 let goal = SelectionGoal::HorizontalPosition(
13350 display_map
13351 .x_for_display_point(head, text_layout_details)
13352 .into(),
13353 );
13354 selection.collapse_to(head, goal);
13355
13356 let transpose_start = display_map
13357 .buffer_snapshot()
13358 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13359 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
13360 let transpose_end = display_map
13361 .buffer_snapshot()
13362 .clip_offset(transpose_offset + 1usize, Bias::Right);
13363 if let Some(ch) = display_map
13364 .buffer_snapshot()
13365 .chars_at(transpose_start)
13366 .next()
13367 {
13368 edits.push((transpose_start..transpose_offset, String::new()));
13369 edits.push((transpose_end..transpose_end, ch.to_string()));
13370 }
13371 }
13372 });
13373 edits
13374 });
13375 this.buffer
13376 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13377 let selections = this
13378 .selections
13379 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13380 this.change_selections(Default::default(), window, cx, |s| {
13381 s.select(selections);
13382 });
13383 });
13384 }
13385
13386 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
13387 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13388 if self.mode.is_single_line() {
13389 cx.propagate();
13390 return;
13391 }
13392
13393 self.rewrap_impl(RewrapOptions::default(), cx)
13394 }
13395
13396 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
13397 let buffer = self.buffer.read(cx).snapshot(cx);
13398 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13399
13400 #[derive(Clone, Debug, PartialEq)]
13401 enum CommentFormat {
13402 /// single line comment, with prefix for line
13403 Line(String),
13404 /// single line within a block comment, with prefix for line
13405 BlockLine(String),
13406 /// a single line of a block comment that includes the initial delimiter
13407 BlockCommentWithStart(BlockCommentConfig),
13408 /// a single line of a block comment that includes the ending delimiter
13409 BlockCommentWithEnd(BlockCommentConfig),
13410 }
13411
13412 // Split selections to respect paragraph, indent, and comment prefix boundaries.
13413 let wrap_ranges = selections.into_iter().flat_map(|selection| {
13414 let language_settings = buffer.language_settings_at(selection.head(), cx);
13415 let language_scope = buffer.language_scope_at(selection.head());
13416
13417 let indent_and_prefix_for_row =
13418 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
13419 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
13420 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
13421 &language_scope
13422 {
13423 let indent_end = Point::new(row, indent.len);
13424 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13425 let line_text_after_indent = buffer
13426 .text_for_range(indent_end..line_end)
13427 .collect::<String>();
13428
13429 let is_within_comment_override = buffer
13430 .language_scope_at(indent_end)
13431 .is_some_and(|scope| scope.override_name() == Some("comment"));
13432 let comment_delimiters = if is_within_comment_override {
13433 // we are within a comment syntax node, but we don't
13434 // yet know what kind of comment: block, doc or line
13435 match (
13436 language_scope.documentation_comment(),
13437 language_scope.block_comment(),
13438 ) {
13439 (Some(config), _) | (_, Some(config))
13440 if buffer.contains_str_at(indent_end, &config.start) =>
13441 {
13442 Some(CommentFormat::BlockCommentWithStart(config.clone()))
13443 }
13444 (Some(config), _) | (_, Some(config))
13445 if line_text_after_indent.ends_with(config.end.as_ref()) =>
13446 {
13447 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
13448 }
13449 (Some(config), _) | (_, Some(config))
13450 if buffer.contains_str_at(indent_end, &config.prefix) =>
13451 {
13452 Some(CommentFormat::BlockLine(config.prefix.to_string()))
13453 }
13454 (_, _) => language_scope
13455 .line_comment_prefixes()
13456 .iter()
13457 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13458 .map(|prefix| CommentFormat::Line(prefix.to_string())),
13459 }
13460 } else {
13461 // we not in an overridden comment node, but we may
13462 // be within a non-overridden line comment node
13463 language_scope
13464 .line_comment_prefixes()
13465 .iter()
13466 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13467 .map(|prefix| CommentFormat::Line(prefix.to_string()))
13468 };
13469
13470 let rewrap_prefix = language_scope
13471 .rewrap_prefixes()
13472 .iter()
13473 .find_map(|prefix_regex| {
13474 prefix_regex.find(&line_text_after_indent).map(|mat| {
13475 if mat.start() == 0 {
13476 Some(mat.as_str().to_string())
13477 } else {
13478 None
13479 }
13480 })
13481 })
13482 .flatten();
13483 (comment_delimiters, rewrap_prefix)
13484 } else {
13485 (None, None)
13486 };
13487 (indent, comment_prefix, rewrap_prefix)
13488 };
13489
13490 let mut start_row = selection.start.row;
13491 let mut end_row = selection.end.row;
13492
13493 if selection.is_empty() {
13494 let cursor_row = selection.start.row;
13495
13496 let (mut indent_size, comment_prefix, _) = indent_and_prefix_for_row(cursor_row);
13497 let line_prefix = match &comment_prefix {
13498 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
13499 Some(prefix.as_str())
13500 }
13501 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
13502 prefix, ..
13503 })) => Some(prefix.as_ref()),
13504 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13505 start: _,
13506 end: _,
13507 prefix,
13508 tab_size,
13509 })) => {
13510 indent_size.len += tab_size;
13511 Some(prefix.as_ref())
13512 }
13513 None => None,
13514 };
13515 let indent_prefix = indent_size.chars().collect::<String>();
13516 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
13517
13518 'expand_upwards: while start_row > 0 {
13519 let prev_row = start_row - 1;
13520 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
13521 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
13522 && !buffer.is_line_blank(MultiBufferRow(prev_row))
13523 {
13524 start_row = prev_row;
13525 } else {
13526 break 'expand_upwards;
13527 }
13528 }
13529
13530 'expand_downwards: while end_row < buffer.max_point().row {
13531 let next_row = end_row + 1;
13532 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
13533 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
13534 && !buffer.is_line_blank(MultiBufferRow(next_row))
13535 {
13536 end_row = next_row;
13537 } else {
13538 break 'expand_downwards;
13539 }
13540 }
13541 }
13542
13543 let mut non_blank_rows_iter = (start_row..=end_row)
13544 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
13545 .peekable();
13546
13547 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
13548 row
13549 } else {
13550 return Vec::new();
13551 };
13552
13553 let mut ranges = Vec::new();
13554
13555 let mut current_range_start = first_row;
13556 let mut prev_row = first_row;
13557 let (
13558 mut current_range_indent,
13559 mut current_range_comment_delimiters,
13560 mut current_range_rewrap_prefix,
13561 ) = indent_and_prefix_for_row(first_row);
13562
13563 for row in non_blank_rows_iter.skip(1) {
13564 let has_paragraph_break = row > prev_row + 1;
13565
13566 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
13567 indent_and_prefix_for_row(row);
13568
13569 let has_indent_change = row_indent != current_range_indent;
13570 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
13571
13572 let has_boundary_change = has_comment_change
13573 || row_rewrap_prefix.is_some()
13574 || (has_indent_change && current_range_comment_delimiters.is_some());
13575
13576 if has_paragraph_break || has_boundary_change {
13577 ranges.push((
13578 language_settings.clone(),
13579 Point::new(current_range_start, 0)
13580 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13581 current_range_indent,
13582 current_range_comment_delimiters.clone(),
13583 current_range_rewrap_prefix.clone(),
13584 ));
13585 current_range_start = row;
13586 current_range_indent = row_indent;
13587 current_range_comment_delimiters = row_comment_delimiters;
13588 current_range_rewrap_prefix = row_rewrap_prefix;
13589 }
13590 prev_row = row;
13591 }
13592
13593 ranges.push((
13594 language_settings.clone(),
13595 Point::new(current_range_start, 0)
13596 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13597 current_range_indent,
13598 current_range_comment_delimiters,
13599 current_range_rewrap_prefix,
13600 ));
13601
13602 ranges
13603 });
13604
13605 let mut edits = Vec::new();
13606 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
13607
13608 for (language_settings, wrap_range, mut indent_size, comment_prefix, rewrap_prefix) in
13609 wrap_ranges
13610 {
13611 let start_row = wrap_range.start.row;
13612 let end_row = wrap_range.end.row;
13613
13614 // Skip selections that overlap with a range that has already been rewrapped.
13615 let selection_range = start_row..end_row;
13616 if rewrapped_row_ranges
13617 .iter()
13618 .any(|range| range.overlaps(&selection_range))
13619 {
13620 continue;
13621 }
13622
13623 let tab_size = language_settings.tab_size;
13624
13625 let (line_prefix, inside_comment) = match &comment_prefix {
13626 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
13627 (Some(prefix.as_str()), true)
13628 }
13629 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
13630 (Some(prefix.as_ref()), true)
13631 }
13632 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13633 start: _,
13634 end: _,
13635 prefix,
13636 tab_size,
13637 })) => {
13638 indent_size.len += tab_size;
13639 (Some(prefix.as_ref()), true)
13640 }
13641 None => (None, false),
13642 };
13643 let indent_prefix = indent_size.chars().collect::<String>();
13644 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
13645
13646 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
13647 RewrapBehavior::InComments => inside_comment,
13648 RewrapBehavior::InSelections => !wrap_range.is_empty(),
13649 RewrapBehavior::Anywhere => true,
13650 };
13651
13652 let should_rewrap = options.override_language_settings
13653 || allow_rewrap_based_on_language
13654 || self.hard_wrap.is_some();
13655 if !should_rewrap {
13656 continue;
13657 }
13658
13659 let start = Point::new(start_row, 0);
13660 let start_offset = ToOffset::to_offset(&start, &buffer);
13661 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
13662 let selection_text = buffer.text_for_range(start..end).collect::<String>();
13663 let mut first_line_delimiter = None;
13664 let mut last_line_delimiter = None;
13665 let Some(lines_without_prefixes) = selection_text
13666 .lines()
13667 .enumerate()
13668 .map(|(ix, line)| {
13669 let line_trimmed = line.trim_start();
13670 if rewrap_prefix.is_some() && ix > 0 {
13671 Ok(line_trimmed)
13672 } else if let Some(
13673 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13674 start,
13675 prefix,
13676 end,
13677 tab_size,
13678 })
13679 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
13680 start,
13681 prefix,
13682 end,
13683 tab_size,
13684 }),
13685 ) = &comment_prefix
13686 {
13687 let line_trimmed = line_trimmed
13688 .strip_prefix(start.as_ref())
13689 .map(|s| {
13690 let mut indent_size = indent_size;
13691 indent_size.len -= tab_size;
13692 let indent_prefix: String = indent_size.chars().collect();
13693 first_line_delimiter = Some((indent_prefix, start));
13694 s.trim_start()
13695 })
13696 .unwrap_or(line_trimmed);
13697 let line_trimmed = line_trimmed
13698 .strip_suffix(end.as_ref())
13699 .map(|s| {
13700 last_line_delimiter = Some(end);
13701 s.trim_end()
13702 })
13703 .unwrap_or(line_trimmed);
13704 let line_trimmed = line_trimmed
13705 .strip_prefix(prefix.as_ref())
13706 .unwrap_or(line_trimmed);
13707 Ok(line_trimmed)
13708 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
13709 line_trimmed.strip_prefix(prefix).with_context(|| {
13710 format!("line did not start with prefix {prefix:?}: {line:?}")
13711 })
13712 } else {
13713 line_trimmed
13714 .strip_prefix(&line_prefix.trim_start())
13715 .with_context(|| {
13716 format!("line did not start with prefix {line_prefix:?}: {line:?}")
13717 })
13718 }
13719 })
13720 .collect::<Result<Vec<_>, _>>()
13721 .log_err()
13722 else {
13723 continue;
13724 };
13725
13726 let wrap_column = options.line_length.or(self.hard_wrap).unwrap_or_else(|| {
13727 buffer
13728 .language_settings_at(Point::new(start_row, 0), cx)
13729 .preferred_line_length as usize
13730 });
13731
13732 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
13733 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
13734 } else {
13735 line_prefix.clone()
13736 };
13737
13738 let wrapped_text = {
13739 let mut wrapped_text = wrap_with_prefix(
13740 line_prefix,
13741 subsequent_lines_prefix,
13742 lines_without_prefixes.join("\n"),
13743 wrap_column,
13744 tab_size,
13745 options.preserve_existing_whitespace,
13746 );
13747
13748 if let Some((indent, delimiter)) = first_line_delimiter {
13749 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
13750 }
13751 if let Some(last_line) = last_line_delimiter {
13752 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
13753 }
13754
13755 wrapped_text
13756 };
13757
13758 // TODO: should always use char-based diff while still supporting cursor behavior that
13759 // matches vim.
13760 let mut diff_options = DiffOptions::default();
13761 if options.override_language_settings {
13762 diff_options.max_word_diff_len = 0;
13763 diff_options.max_word_diff_line_count = 0;
13764 } else {
13765 diff_options.max_word_diff_len = usize::MAX;
13766 diff_options.max_word_diff_line_count = usize::MAX;
13767 }
13768
13769 for (old_range, new_text) in
13770 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
13771 {
13772 let edit_start = buffer.anchor_after(start_offset + old_range.start);
13773 let edit_end = buffer.anchor_after(start_offset + old_range.end);
13774 edits.push((edit_start..edit_end, new_text));
13775 }
13776
13777 rewrapped_row_ranges.push(start_row..=end_row);
13778 }
13779
13780 self.buffer
13781 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13782 }
13783
13784 pub fn cut_common(
13785 &mut self,
13786 cut_no_selection_line: bool,
13787 window: &mut Window,
13788 cx: &mut Context<Self>,
13789 ) -> ClipboardItem {
13790 let mut text = String::new();
13791 let buffer = self.buffer.read(cx).snapshot(cx);
13792 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13793 let mut clipboard_selections = Vec::with_capacity(selections.len());
13794 {
13795 let max_point = buffer.max_point();
13796 let mut is_first = true;
13797 let mut prev_selection_was_entire_line = false;
13798 for selection in &mut selections {
13799 let is_entire_line =
13800 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
13801 if is_entire_line {
13802 selection.start = Point::new(selection.start.row, 0);
13803 if !selection.is_empty() && selection.end.column == 0 {
13804 selection.end = cmp::min(max_point, selection.end);
13805 } else {
13806 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
13807 }
13808 selection.goal = SelectionGoal::None;
13809 }
13810 if is_first {
13811 is_first = false;
13812 } else if !prev_selection_was_entire_line {
13813 text += "\n";
13814 }
13815 prev_selection_was_entire_line = is_entire_line;
13816 let mut len = 0;
13817 for chunk in buffer.text_for_range(selection.start..selection.end) {
13818 text.push_str(chunk);
13819 len += chunk.len();
13820 }
13821
13822 clipboard_selections.push(ClipboardSelection::for_buffer(
13823 len,
13824 is_entire_line,
13825 selection.range(),
13826 &buffer,
13827 self.project.as_ref(),
13828 cx,
13829 ));
13830 }
13831 }
13832
13833 self.transact(window, cx, |this, window, cx| {
13834 this.change_selections(Default::default(), window, cx, |s| {
13835 s.select(selections);
13836 });
13837 this.insert("", window, cx);
13838 });
13839 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
13840 }
13841
13842 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
13843 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13844 let item = self.cut_common(true, window, cx);
13845 cx.write_to_clipboard(item);
13846 }
13847
13848 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
13849 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13850 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13851 s.move_with(&mut |snapshot, sel| {
13852 if sel.is_empty() {
13853 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
13854 }
13855 if sel.is_empty() {
13856 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13857 }
13858 });
13859 });
13860 let item = self.cut_common(false, window, cx);
13861 cx.set_global(KillRing(item))
13862 }
13863
13864 pub fn kill_ring_yank(
13865 &mut self,
13866 _: &KillRingYank,
13867 window: &mut Window,
13868 cx: &mut Context<Self>,
13869 ) {
13870 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13871 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
13872 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
13873 (kill_ring.text().to_string(), kill_ring.metadata_json())
13874 } else {
13875 return;
13876 }
13877 } else {
13878 return;
13879 };
13880 self.do_paste(&text, metadata, false, window, cx);
13881 }
13882
13883 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
13884 self.do_copy(true, cx);
13885 }
13886
13887 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
13888 self.do_copy(false, cx);
13889 }
13890
13891 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
13892 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13893 let buffer = self.buffer.read(cx).read(cx);
13894 let mut text = String::new();
13895 let mut clipboard_selections = Vec::with_capacity(selections.len());
13896
13897 let max_point = buffer.max_point();
13898 let mut is_first = true;
13899 for selection in &selections {
13900 let mut start = selection.start;
13901 let mut end = selection.end;
13902 let is_entire_line = selection.is_empty() || self.selections.line_mode();
13903 let mut add_trailing_newline = false;
13904 if is_entire_line {
13905 start = Point::new(start.row, 0);
13906 let next_line_start = Point::new(end.row + 1, 0);
13907 if next_line_start <= max_point {
13908 end = next_line_start;
13909 } else {
13910 // We're on the last line without a trailing newline.
13911 // Copy to the end of the line and add a newline afterwards.
13912 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
13913 add_trailing_newline = true;
13914 }
13915 }
13916
13917 let mut trimmed_selections = Vec::new();
13918 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
13919 let row = MultiBufferRow(start.row);
13920 let first_indent = buffer.indent_size_for_line(row);
13921 if first_indent.len == 0 || start.column > first_indent.len {
13922 trimmed_selections.push(start..end);
13923 } else {
13924 trimmed_selections.push(
13925 Point::new(row.0, first_indent.len)
13926 ..Point::new(row.0, buffer.line_len(row)),
13927 );
13928 for row in start.row + 1..=end.row {
13929 let mut line_len = buffer.line_len(MultiBufferRow(row));
13930 if row == end.row {
13931 line_len = end.column;
13932 }
13933 if line_len == 0 {
13934 trimmed_selections.push(Point::new(row, 0)..Point::new(row, line_len));
13935 continue;
13936 }
13937 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
13938 if row_indent_size.len >= first_indent.len {
13939 trimmed_selections
13940 .push(Point::new(row, first_indent.len)..Point::new(row, line_len));
13941 } else {
13942 trimmed_selections.clear();
13943 trimmed_selections.push(start..end);
13944 break;
13945 }
13946 }
13947 }
13948 } else {
13949 trimmed_selections.push(start..end);
13950 }
13951
13952 let is_multiline_trim = trimmed_selections.len() > 1;
13953 let mut selection_len: usize = 0;
13954 let prev_selection_was_entire_line = is_entire_line && !is_multiline_trim;
13955
13956 for trimmed_range in trimmed_selections {
13957 if is_first {
13958 is_first = false;
13959 } else if is_multiline_trim || !prev_selection_was_entire_line {
13960 text.push('\n');
13961 if is_multiline_trim {
13962 selection_len += 1;
13963 }
13964 }
13965 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
13966 text.push_str(chunk);
13967 selection_len += chunk.len();
13968 }
13969 if add_trailing_newline {
13970 text.push('\n');
13971 selection_len += 1;
13972 }
13973 }
13974
13975 clipboard_selections.push(ClipboardSelection::for_buffer(
13976 selection_len,
13977 is_entire_line,
13978 start..end,
13979 &buffer,
13980 self.project.as_ref(),
13981 cx,
13982 ));
13983 }
13984
13985 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
13986 text,
13987 clipboard_selections,
13988 ));
13989 }
13990
13991 pub fn do_paste(
13992 &mut self,
13993 text: &String,
13994 clipboard_selections: Option<Vec<ClipboardSelection>>,
13995 handle_entire_lines: bool,
13996 window: &mut Window,
13997 cx: &mut Context<Self>,
13998 ) {
13999 if self.read_only(cx) {
14000 return;
14001 }
14002
14003 let clipboard_text = Cow::Borrowed(text.as_str());
14004
14005 self.transact(window, cx, |this, window, cx| {
14006 let had_active_edit_prediction = this.has_active_edit_prediction();
14007 let display_map = this.display_snapshot(cx);
14008 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
14009 let cursor_offset = this
14010 .selections
14011 .last::<MultiBufferOffset>(&display_map)
14012 .head();
14013
14014 if let Some(mut clipboard_selections) = clipboard_selections {
14015 let all_selections_were_entire_line =
14016 clipboard_selections.iter().all(|s| s.is_entire_line);
14017 let first_selection_indent_column =
14018 clipboard_selections.first().map(|s| s.first_line_indent);
14019 if clipboard_selections.len() != old_selections.len() {
14020 clipboard_selections.drain(..);
14021 }
14022 let mut auto_indent_on_paste = true;
14023
14024 this.buffer.update(cx, |buffer, cx| {
14025 let snapshot = buffer.read(cx);
14026 auto_indent_on_paste = snapshot
14027 .language_settings_at(cursor_offset, cx)
14028 .auto_indent_on_paste;
14029
14030 let mut start_offset = 0;
14031 let mut edits = Vec::new();
14032 let mut original_indent_columns = Vec::new();
14033 for (ix, selection) in old_selections.iter().enumerate() {
14034 let to_insert;
14035 let entire_line;
14036 let original_indent_column;
14037 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
14038 let end_offset = start_offset + clipboard_selection.len;
14039 to_insert = &clipboard_text[start_offset..end_offset];
14040 entire_line = clipboard_selection.is_entire_line;
14041 start_offset = if entire_line {
14042 end_offset
14043 } else {
14044 end_offset + 1
14045 };
14046 original_indent_column = Some(clipboard_selection.first_line_indent);
14047 } else {
14048 to_insert = &*clipboard_text;
14049 entire_line = all_selections_were_entire_line;
14050 original_indent_column = first_selection_indent_column
14051 }
14052
14053 let (range, to_insert) =
14054 if selection.is_empty() && handle_entire_lines && entire_line {
14055 // If the corresponding selection was empty when this slice of the
14056 // clipboard text was written, then the entire line containing the
14057 // selection was copied. If this selection is also currently empty,
14058 // then paste the line before the current line of the buffer.
14059 let column = selection.start.to_point(&snapshot).column as usize;
14060 let line_start = selection.start - column;
14061 (line_start..line_start, Cow::Borrowed(to_insert))
14062 } else {
14063 let language = snapshot.language_at(selection.head());
14064 let range = selection.range();
14065 if let Some(language) = language
14066 && language.name() == "Markdown"
14067 {
14068 edit_for_markdown_paste(
14069 &snapshot,
14070 range,
14071 to_insert,
14072 url::Url::parse(to_insert).ok(),
14073 )
14074 } else {
14075 (range, Cow::Borrowed(to_insert))
14076 }
14077 };
14078
14079 edits.push((range, to_insert));
14080 original_indent_columns.push(original_indent_column);
14081 }
14082 drop(snapshot);
14083
14084 buffer.edit(
14085 edits,
14086 if auto_indent_on_paste {
14087 Some(AutoindentMode::Block {
14088 original_indent_columns,
14089 })
14090 } else {
14091 None
14092 },
14093 cx,
14094 );
14095 });
14096
14097 let selections = this
14098 .selections
14099 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
14100 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14101 } else {
14102 let url = url::Url::parse(&clipboard_text).ok();
14103
14104 let auto_indent_mode = if !clipboard_text.is_empty() {
14105 Some(AutoindentMode::Block {
14106 original_indent_columns: Vec::new(),
14107 })
14108 } else {
14109 None
14110 };
14111
14112 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
14113 let snapshot = buffer.snapshot(cx);
14114
14115 let anchors = old_selections
14116 .iter()
14117 .map(|s| {
14118 let anchor = snapshot.anchor_after(s.head());
14119 s.map(|_| anchor)
14120 })
14121 .collect::<Vec<_>>();
14122
14123 let mut edits = Vec::new();
14124
14125 // When pasting text without metadata (e.g. copied from an
14126 // external editor using multiple cursors) and the number of
14127 // lines matches the number of selections, distribute one
14128 // line per cursor instead of pasting the whole text at each.
14129 let lines: Vec<&str> = clipboard_text.split('\n').collect();
14130 let distribute_lines =
14131 old_selections.len() > 1 && lines.len() == old_selections.len();
14132
14133 for (ix, selection) in old_selections.iter().enumerate() {
14134 let language = snapshot.language_at(selection.head());
14135 let range = selection.range();
14136
14137 let text_for_cursor: &str = if distribute_lines {
14138 lines[ix]
14139 } else {
14140 &clipboard_text
14141 };
14142
14143 let (edit_range, edit_text) = if let Some(language) = language
14144 && language.name() == "Markdown"
14145 {
14146 edit_for_markdown_paste(&snapshot, range, text_for_cursor, url.clone())
14147 } else {
14148 (range, Cow::Borrowed(text_for_cursor))
14149 };
14150
14151 edits.push((edit_range, edit_text));
14152 }
14153
14154 drop(snapshot);
14155 buffer.edit(edits, auto_indent_mode, cx);
14156
14157 anchors
14158 });
14159
14160 this.change_selections(Default::default(), window, cx, |s| {
14161 s.select_anchors(selection_anchors);
14162 });
14163 }
14164
14165 // 🤔 | .. | show_in_menu |
14166 // | .. | true true
14167 // | had_edit_prediction | false true
14168
14169 let trigger_in_words =
14170 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
14171
14172 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
14173 });
14174 }
14175
14176 pub fn diff_clipboard_with_selection(
14177 &mut self,
14178 _: &DiffClipboardWithSelection,
14179 window: &mut Window,
14180 cx: &mut Context<Self>,
14181 ) {
14182 let selections = self
14183 .selections
14184 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
14185
14186 if selections.is_empty() {
14187 log::warn!("There should always be at least one selection in Zed. This is a bug.");
14188 return;
14189 };
14190
14191 let clipboard_text = cx.read_from_clipboard().and_then(|item| {
14192 item.entries().iter().find_map(|entry| match entry {
14193 ClipboardEntry::String(text) => Some(text.text().to_string()),
14194 _ => None,
14195 })
14196 });
14197
14198 let Some(clipboard_text) = clipboard_text else {
14199 log::warn!("Clipboard doesn't contain text.");
14200 return;
14201 };
14202
14203 window.dispatch_action(
14204 Box::new(DiffClipboardWithSelectionData {
14205 clipboard_text,
14206 editor: cx.entity(),
14207 }),
14208 cx,
14209 );
14210 }
14211
14212 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
14213 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14214 if let Some(item) = cx.read_from_clipboard() {
14215 let clipboard_string = item.entries().iter().find_map(|entry| match entry {
14216 ClipboardEntry::String(s) => Some(s),
14217 _ => None,
14218 });
14219 match clipboard_string {
14220 Some(clipboard_string) => self.do_paste(
14221 clipboard_string.text(),
14222 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
14223 true,
14224 window,
14225 cx,
14226 ),
14227 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
14228 }
14229 }
14230 }
14231
14232 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
14233 if self.read_only(cx) {
14234 return;
14235 }
14236
14237 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14238
14239 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
14240 if let Some((selections, _)) =
14241 self.selection_history.transaction(transaction_id).cloned()
14242 {
14243 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14244 s.select_anchors(selections.to_vec());
14245 });
14246 } else {
14247 log::error!(
14248 "No entry in selection_history found for undo. \
14249 This may correspond to a bug where undo does not update the selection. \
14250 If this is occurring, please add details to \
14251 https://github.com/zed-industries/zed/issues/22692"
14252 );
14253 }
14254 self.request_autoscroll(Autoscroll::fit(), cx);
14255 self.unmark_text(window, cx);
14256 self.refresh_edit_prediction(true, false, window, cx);
14257 cx.emit(EditorEvent::Edited { transaction_id });
14258 cx.emit(EditorEvent::TransactionUndone { transaction_id });
14259 }
14260 }
14261
14262 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
14263 if self.read_only(cx) {
14264 return;
14265 }
14266
14267 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14268
14269 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
14270 if let Some((_, Some(selections))) =
14271 self.selection_history.transaction(transaction_id).cloned()
14272 {
14273 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14274 s.select_anchors(selections.to_vec());
14275 });
14276 } else {
14277 log::error!(
14278 "No entry in selection_history found for redo. \
14279 This may correspond to a bug where undo does not update the selection. \
14280 If this is occurring, please add details to \
14281 https://github.com/zed-industries/zed/issues/22692"
14282 );
14283 }
14284 self.request_autoscroll(Autoscroll::fit(), cx);
14285 self.unmark_text(window, cx);
14286 self.refresh_edit_prediction(true, false, window, cx);
14287 cx.emit(EditorEvent::Edited { transaction_id });
14288 }
14289 }
14290
14291 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
14292 self.buffer
14293 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
14294 }
14295
14296 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
14297 self.buffer
14298 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
14299 }
14300
14301 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
14302 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14303 self.change_selections(Default::default(), window, cx, |s| {
14304 s.move_with(&mut |map, selection| {
14305 let cursor = if selection.is_empty() {
14306 movement::left(map, selection.start)
14307 } else {
14308 selection.start
14309 };
14310 selection.collapse_to(cursor, SelectionGoal::None);
14311 });
14312 })
14313 }
14314
14315 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
14316 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14317 self.change_selections(Default::default(), window, cx, |s| {
14318 s.move_heads_with(&mut |map, head, _| (movement::left(map, head), SelectionGoal::None));
14319 })
14320 }
14321
14322 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
14323 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14324 self.change_selections(Default::default(), window, cx, |s| {
14325 s.move_with(&mut |map, selection| {
14326 let cursor = if selection.is_empty() {
14327 movement::right(map, selection.end)
14328 } else {
14329 selection.end
14330 };
14331 selection.collapse_to(cursor, SelectionGoal::None)
14332 });
14333 })
14334 }
14335
14336 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
14337 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14338 self.change_selections(Default::default(), window, cx, |s| {
14339 s.move_heads_with(&mut |map, head, _| {
14340 (movement::right(map, head), SelectionGoal::None)
14341 });
14342 });
14343 }
14344
14345 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
14346 if self.take_rename(true, window, cx).is_some() {
14347 return;
14348 }
14349
14350 if self.mode.is_single_line() {
14351 cx.propagate();
14352 return;
14353 }
14354
14355 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14356
14357 let text_layout_details = &self.text_layout_details(window, cx);
14358 let selection_count = self.selections.count();
14359 let first_selection = self.selections.first_anchor();
14360
14361 self.change_selections(Default::default(), window, cx, |s| {
14362 s.move_with(&mut |map, selection| {
14363 if !selection.is_empty() {
14364 selection.goal = SelectionGoal::None;
14365 }
14366 let (cursor, goal) = movement::up(
14367 map,
14368 selection.start,
14369 selection.goal,
14370 false,
14371 text_layout_details,
14372 );
14373 selection.collapse_to(cursor, goal);
14374 });
14375 });
14376
14377 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14378 {
14379 cx.propagate();
14380 }
14381 }
14382
14383 pub fn move_up_by_lines(
14384 &mut self,
14385 action: &MoveUpByLines,
14386 window: &mut Window,
14387 cx: &mut Context<Self>,
14388 ) {
14389 if self.take_rename(true, window, cx).is_some() {
14390 return;
14391 }
14392
14393 if self.mode.is_single_line() {
14394 cx.propagate();
14395 return;
14396 }
14397
14398 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14399
14400 let text_layout_details = &self.text_layout_details(window, cx);
14401
14402 self.change_selections(Default::default(), window, cx, |s| {
14403 s.move_with(&mut |map, selection| {
14404 if !selection.is_empty() {
14405 selection.goal = SelectionGoal::None;
14406 }
14407 let (cursor, goal) = movement::up_by_rows(
14408 map,
14409 selection.start,
14410 action.lines,
14411 selection.goal,
14412 false,
14413 text_layout_details,
14414 );
14415 selection.collapse_to(cursor, goal);
14416 });
14417 })
14418 }
14419
14420 pub fn move_down_by_lines(
14421 &mut self,
14422 action: &MoveDownByLines,
14423 window: &mut Window,
14424 cx: &mut Context<Self>,
14425 ) {
14426 if self.take_rename(true, window, cx).is_some() {
14427 return;
14428 }
14429
14430 if self.mode.is_single_line() {
14431 cx.propagate();
14432 return;
14433 }
14434
14435 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14436
14437 let text_layout_details = &self.text_layout_details(window, cx);
14438
14439 self.change_selections(Default::default(), window, cx, |s| {
14440 s.move_with(&mut |map, selection| {
14441 if !selection.is_empty() {
14442 selection.goal = SelectionGoal::None;
14443 }
14444 let (cursor, goal) = movement::down_by_rows(
14445 map,
14446 selection.start,
14447 action.lines,
14448 selection.goal,
14449 false,
14450 text_layout_details,
14451 );
14452 selection.collapse_to(cursor, goal);
14453 });
14454 })
14455 }
14456
14457 pub fn select_down_by_lines(
14458 &mut self,
14459 action: &SelectDownByLines,
14460 window: &mut Window,
14461 cx: &mut Context<Self>,
14462 ) {
14463 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14464 let text_layout_details = &self.text_layout_details(window, cx);
14465 self.change_selections(Default::default(), window, cx, |s| {
14466 s.move_heads_with(&mut |map, head, goal| {
14467 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
14468 })
14469 })
14470 }
14471
14472 pub fn select_up_by_lines(
14473 &mut self,
14474 action: &SelectUpByLines,
14475 window: &mut Window,
14476 cx: &mut Context<Self>,
14477 ) {
14478 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14479 let text_layout_details = &self.text_layout_details(window, cx);
14480 self.change_selections(Default::default(), window, cx, |s| {
14481 s.move_heads_with(&mut |map, head, goal| {
14482 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
14483 })
14484 })
14485 }
14486
14487 pub fn select_page_up(
14488 &mut self,
14489 _: &SelectPageUp,
14490 window: &mut Window,
14491 cx: &mut Context<Self>,
14492 ) {
14493 let Some(row_count) = self.visible_row_count() else {
14494 return;
14495 };
14496
14497 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14498
14499 let text_layout_details = &self.text_layout_details(window, cx);
14500
14501 self.change_selections(Default::default(), window, cx, |s| {
14502 s.move_heads_with(&mut |map, head, goal| {
14503 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
14504 })
14505 })
14506 }
14507
14508 pub fn move_page_up(
14509 &mut self,
14510 action: &MovePageUp,
14511 window: &mut Window,
14512 cx: &mut Context<Self>,
14513 ) {
14514 if self.take_rename(true, window, cx).is_some() {
14515 return;
14516 }
14517
14518 if self
14519 .context_menu
14520 .borrow_mut()
14521 .as_mut()
14522 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
14523 .unwrap_or(false)
14524 {
14525 return;
14526 }
14527
14528 if matches!(self.mode, EditorMode::SingleLine) {
14529 cx.propagate();
14530 return;
14531 }
14532
14533 let Some(row_count) = self.visible_row_count() else {
14534 return;
14535 };
14536
14537 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14538
14539 let effects = if action.center_cursor {
14540 SelectionEffects::scroll(Autoscroll::center())
14541 } else {
14542 SelectionEffects::default()
14543 };
14544
14545 let text_layout_details = &self.text_layout_details(window, cx);
14546
14547 self.change_selections(effects, window, cx, |s| {
14548 s.move_with(&mut |map, selection| {
14549 if !selection.is_empty() {
14550 selection.goal = SelectionGoal::None;
14551 }
14552 let (cursor, goal) = movement::up_by_rows(
14553 map,
14554 selection.end,
14555 row_count,
14556 selection.goal,
14557 false,
14558 text_layout_details,
14559 );
14560 selection.collapse_to(cursor, goal);
14561 });
14562 });
14563 }
14564
14565 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
14566 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14567 let text_layout_details = &self.text_layout_details(window, cx);
14568 self.change_selections(Default::default(), window, cx, |s| {
14569 s.move_heads_with(&mut |map, head, goal| {
14570 movement::up(map, head, goal, false, text_layout_details)
14571 })
14572 })
14573 }
14574
14575 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
14576 self.take_rename(true, window, cx);
14577
14578 if self.mode.is_single_line() {
14579 cx.propagate();
14580 return;
14581 }
14582
14583 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14584
14585 let text_layout_details = &self.text_layout_details(window, cx);
14586 let selection_count = self.selections.count();
14587 let first_selection = self.selections.first_anchor();
14588
14589 self.change_selections(Default::default(), window, cx, |s| {
14590 s.move_with(&mut |map, selection| {
14591 if !selection.is_empty() {
14592 selection.goal = SelectionGoal::None;
14593 }
14594 let (cursor, goal) = movement::down(
14595 map,
14596 selection.end,
14597 selection.goal,
14598 false,
14599 text_layout_details,
14600 );
14601 selection.collapse_to(cursor, goal);
14602 });
14603 });
14604
14605 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14606 {
14607 cx.propagate();
14608 }
14609 }
14610
14611 pub fn select_page_down(
14612 &mut self,
14613 _: &SelectPageDown,
14614 window: &mut Window,
14615 cx: &mut Context<Self>,
14616 ) {
14617 let Some(row_count) = self.visible_row_count() else {
14618 return;
14619 };
14620
14621 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14622
14623 let text_layout_details = &self.text_layout_details(window, cx);
14624
14625 self.change_selections(Default::default(), window, cx, |s| {
14626 s.move_heads_with(&mut |map, head, goal| {
14627 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
14628 })
14629 })
14630 }
14631
14632 pub fn move_page_down(
14633 &mut self,
14634 action: &MovePageDown,
14635 window: &mut Window,
14636 cx: &mut Context<Self>,
14637 ) {
14638 if self.take_rename(true, window, cx).is_some() {
14639 return;
14640 }
14641
14642 if self
14643 .context_menu
14644 .borrow_mut()
14645 .as_mut()
14646 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
14647 .unwrap_or(false)
14648 {
14649 return;
14650 }
14651
14652 if matches!(self.mode, EditorMode::SingleLine) {
14653 cx.propagate();
14654 return;
14655 }
14656
14657 let Some(row_count) = self.visible_row_count() else {
14658 return;
14659 };
14660
14661 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14662
14663 let effects = if action.center_cursor {
14664 SelectionEffects::scroll(Autoscroll::center())
14665 } else {
14666 SelectionEffects::default()
14667 };
14668
14669 let text_layout_details = &self.text_layout_details(window, cx);
14670 self.change_selections(effects, window, cx, |s| {
14671 s.move_with(&mut |map, selection| {
14672 if !selection.is_empty() {
14673 selection.goal = SelectionGoal::None;
14674 }
14675 let (cursor, goal) = movement::down_by_rows(
14676 map,
14677 selection.end,
14678 row_count,
14679 selection.goal,
14680 false,
14681 text_layout_details,
14682 );
14683 selection.collapse_to(cursor, goal);
14684 });
14685 });
14686 }
14687
14688 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
14689 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14690 let text_layout_details = &self.text_layout_details(window, cx);
14691 self.change_selections(Default::default(), window, cx, |s| {
14692 s.move_heads_with(&mut |map, head, goal| {
14693 movement::down(map, head, goal, false, text_layout_details)
14694 })
14695 });
14696 }
14697
14698 pub fn context_menu_first(
14699 &mut self,
14700 _: &ContextMenuFirst,
14701 window: &mut Window,
14702 cx: &mut Context<Self>,
14703 ) {
14704 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14705 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
14706 }
14707 }
14708
14709 pub fn context_menu_prev(
14710 &mut self,
14711 _: &ContextMenuPrevious,
14712 window: &mut Window,
14713 cx: &mut Context<Self>,
14714 ) {
14715 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14716 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
14717 }
14718 }
14719
14720 pub fn context_menu_next(
14721 &mut self,
14722 _: &ContextMenuNext,
14723 window: &mut Window,
14724 cx: &mut Context<Self>,
14725 ) {
14726 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14727 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
14728 }
14729 }
14730
14731 pub fn context_menu_last(
14732 &mut self,
14733 _: &ContextMenuLast,
14734 window: &mut Window,
14735 cx: &mut Context<Self>,
14736 ) {
14737 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14738 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
14739 }
14740 }
14741
14742 pub fn signature_help_prev(
14743 &mut self,
14744 _: &SignatureHelpPrevious,
14745 _: &mut Window,
14746 cx: &mut Context<Self>,
14747 ) {
14748 if let Some(popover) = self.signature_help_state.popover_mut() {
14749 if popover.current_signature == 0 {
14750 popover.current_signature = popover.signatures.len() - 1;
14751 } else {
14752 popover.current_signature -= 1;
14753 }
14754 cx.notify();
14755 }
14756 }
14757
14758 pub fn signature_help_next(
14759 &mut self,
14760 _: &SignatureHelpNext,
14761 _: &mut Window,
14762 cx: &mut Context<Self>,
14763 ) {
14764 if let Some(popover) = self.signature_help_state.popover_mut() {
14765 if popover.current_signature + 1 == popover.signatures.len() {
14766 popover.current_signature = 0;
14767 } else {
14768 popover.current_signature += 1;
14769 }
14770 cx.notify();
14771 }
14772 }
14773
14774 pub fn move_to_previous_word_start(
14775 &mut self,
14776 _: &MoveToPreviousWordStart,
14777 window: &mut Window,
14778 cx: &mut Context<Self>,
14779 ) {
14780 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14781 self.change_selections(Default::default(), window, cx, |s| {
14782 s.move_cursors_with(&mut |map, head, _| {
14783 (
14784 movement::previous_word_start(map, head),
14785 SelectionGoal::None,
14786 )
14787 });
14788 })
14789 }
14790
14791 pub fn move_to_previous_subword_start(
14792 &mut self,
14793 _: &MoveToPreviousSubwordStart,
14794 window: &mut Window,
14795 cx: &mut Context<Self>,
14796 ) {
14797 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14798 self.change_selections(Default::default(), window, cx, |s| {
14799 s.move_cursors_with(&mut |map, head, _| {
14800 (
14801 movement::previous_subword_start(map, head),
14802 SelectionGoal::None,
14803 )
14804 });
14805 })
14806 }
14807
14808 pub fn select_to_previous_word_start(
14809 &mut self,
14810 _: &SelectToPreviousWordStart,
14811 window: &mut Window,
14812 cx: &mut Context<Self>,
14813 ) {
14814 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14815 self.change_selections(Default::default(), window, cx, |s| {
14816 s.move_heads_with(&mut |map, head, _| {
14817 (
14818 movement::previous_word_start(map, head),
14819 SelectionGoal::None,
14820 )
14821 });
14822 })
14823 }
14824
14825 pub fn select_to_previous_subword_start(
14826 &mut self,
14827 _: &SelectToPreviousSubwordStart,
14828 window: &mut Window,
14829 cx: &mut Context<Self>,
14830 ) {
14831 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14832 self.change_selections(Default::default(), window, cx, |s| {
14833 s.move_heads_with(&mut |map, head, _| {
14834 (
14835 movement::previous_subword_start(map, head),
14836 SelectionGoal::None,
14837 )
14838 });
14839 })
14840 }
14841
14842 pub fn delete_to_previous_word_start(
14843 &mut self,
14844 action: &DeleteToPreviousWordStart,
14845 window: &mut Window,
14846 cx: &mut Context<Self>,
14847 ) {
14848 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14849 self.transact(window, cx, |this, window, cx| {
14850 this.select_autoclose_pair(window, cx);
14851 this.change_selections(Default::default(), window, cx, |s| {
14852 s.move_with(&mut |map, selection| {
14853 if selection.is_empty() {
14854 let mut cursor = if action.ignore_newlines {
14855 movement::previous_word_start(map, selection.head())
14856 } else {
14857 movement::previous_word_start_or_newline(map, selection.head())
14858 };
14859 cursor = movement::adjust_greedy_deletion(
14860 map,
14861 selection.head(),
14862 cursor,
14863 action.ignore_brackets,
14864 );
14865 selection.set_head(cursor, SelectionGoal::None);
14866 }
14867 });
14868 });
14869 this.insert("", window, cx);
14870 });
14871 }
14872
14873 pub fn delete_to_previous_subword_start(
14874 &mut self,
14875 action: &DeleteToPreviousSubwordStart,
14876 window: &mut Window,
14877 cx: &mut Context<Self>,
14878 ) {
14879 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14880 self.transact(window, cx, |this, window, cx| {
14881 this.select_autoclose_pair(window, cx);
14882 this.change_selections(Default::default(), window, cx, |s| {
14883 s.move_with(&mut |map, selection| {
14884 if selection.is_empty() {
14885 let mut cursor = if action.ignore_newlines {
14886 movement::previous_subword_start(map, selection.head())
14887 } else {
14888 movement::previous_subword_start_or_newline(map, selection.head())
14889 };
14890 cursor = movement::adjust_greedy_deletion(
14891 map,
14892 selection.head(),
14893 cursor,
14894 action.ignore_brackets,
14895 );
14896 selection.set_head(cursor, SelectionGoal::None);
14897 }
14898 });
14899 });
14900 this.insert("", window, cx);
14901 });
14902 }
14903
14904 pub fn move_to_next_word_end(
14905 &mut self,
14906 _: &MoveToNextWordEnd,
14907 window: &mut Window,
14908 cx: &mut Context<Self>,
14909 ) {
14910 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14911 self.change_selections(Default::default(), window, cx, |s| {
14912 s.move_cursors_with(&mut |map, head, _| {
14913 (movement::next_word_end(map, head), SelectionGoal::None)
14914 });
14915 })
14916 }
14917
14918 pub fn move_to_next_subword_end(
14919 &mut self,
14920 _: &MoveToNextSubwordEnd,
14921 window: &mut Window,
14922 cx: &mut Context<Self>,
14923 ) {
14924 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14925 self.change_selections(Default::default(), window, cx, |s| {
14926 s.move_cursors_with(&mut |map, head, _| {
14927 (movement::next_subword_end(map, head), SelectionGoal::None)
14928 });
14929 })
14930 }
14931
14932 pub fn select_to_next_word_end(
14933 &mut self,
14934 _: &SelectToNextWordEnd,
14935 window: &mut Window,
14936 cx: &mut Context<Self>,
14937 ) {
14938 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14939 self.change_selections(Default::default(), window, cx, |s| {
14940 s.move_heads_with(&mut |map, head, _| {
14941 (movement::next_word_end(map, head), SelectionGoal::None)
14942 });
14943 })
14944 }
14945
14946 pub fn select_to_next_subword_end(
14947 &mut self,
14948 _: &SelectToNextSubwordEnd,
14949 window: &mut Window,
14950 cx: &mut Context<Self>,
14951 ) {
14952 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14953 self.change_selections(Default::default(), window, cx, |s| {
14954 s.move_heads_with(&mut |map, head, _| {
14955 (movement::next_subword_end(map, head), SelectionGoal::None)
14956 });
14957 })
14958 }
14959
14960 pub fn delete_to_next_word_end(
14961 &mut self,
14962 action: &DeleteToNextWordEnd,
14963 window: &mut Window,
14964 cx: &mut Context<Self>,
14965 ) {
14966 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14967 self.transact(window, cx, |this, window, cx| {
14968 this.change_selections(Default::default(), window, cx, |s| {
14969 s.move_with(&mut |map, selection| {
14970 if selection.is_empty() {
14971 let mut cursor = if action.ignore_newlines {
14972 movement::next_word_end(map, selection.head())
14973 } else {
14974 movement::next_word_end_or_newline(map, selection.head())
14975 };
14976 cursor = movement::adjust_greedy_deletion(
14977 map,
14978 selection.head(),
14979 cursor,
14980 action.ignore_brackets,
14981 );
14982 selection.set_head(cursor, SelectionGoal::None);
14983 }
14984 });
14985 });
14986 this.insert("", window, cx);
14987 });
14988 }
14989
14990 pub fn delete_to_next_subword_end(
14991 &mut self,
14992 action: &DeleteToNextSubwordEnd,
14993 window: &mut Window,
14994 cx: &mut Context<Self>,
14995 ) {
14996 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14997 self.transact(window, cx, |this, window, cx| {
14998 this.change_selections(Default::default(), window, cx, |s| {
14999 s.move_with(&mut |map, selection| {
15000 if selection.is_empty() {
15001 let mut cursor = if action.ignore_newlines {
15002 movement::next_subword_end(map, selection.head())
15003 } else {
15004 movement::next_subword_end_or_newline(map, selection.head())
15005 };
15006 cursor = movement::adjust_greedy_deletion(
15007 map,
15008 selection.head(),
15009 cursor,
15010 action.ignore_brackets,
15011 );
15012 selection.set_head(cursor, SelectionGoal::None);
15013 }
15014 });
15015 });
15016 this.insert("", window, cx);
15017 });
15018 }
15019
15020 pub fn move_to_beginning_of_line(
15021 &mut self,
15022 action: &MoveToBeginningOfLine,
15023 window: &mut Window,
15024 cx: &mut Context<Self>,
15025 ) {
15026 let stop_at_indent = action.stop_at_indent && !self.mode.is_single_line();
15027 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15028 self.change_selections(Default::default(), window, cx, |s| {
15029 s.move_cursors_with(&mut |map, head, _| {
15030 (
15031 movement::indented_line_beginning(
15032 map,
15033 head,
15034 action.stop_at_soft_wraps,
15035 stop_at_indent,
15036 ),
15037 SelectionGoal::None,
15038 )
15039 });
15040 })
15041 }
15042
15043 pub fn select_to_beginning_of_line(
15044 &mut self,
15045 action: &SelectToBeginningOfLine,
15046 window: &mut Window,
15047 cx: &mut Context<Self>,
15048 ) {
15049 let stop_at_indent = action.stop_at_indent && !self.mode.is_single_line();
15050 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15051 self.change_selections(Default::default(), window, cx, |s| {
15052 s.move_heads_with(&mut |map, head, _| {
15053 (
15054 movement::indented_line_beginning(
15055 map,
15056 head,
15057 action.stop_at_soft_wraps,
15058 stop_at_indent,
15059 ),
15060 SelectionGoal::None,
15061 )
15062 });
15063 });
15064 }
15065
15066 pub fn delete_to_beginning_of_line(
15067 &mut self,
15068 action: &DeleteToBeginningOfLine,
15069 window: &mut Window,
15070 cx: &mut Context<Self>,
15071 ) {
15072 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15073 self.transact(window, cx, |this, window, cx| {
15074 this.change_selections(Default::default(), window, cx, |s| {
15075 s.move_with(&mut |_, selection| {
15076 selection.reversed = true;
15077 });
15078 });
15079
15080 this.select_to_beginning_of_line(
15081 &SelectToBeginningOfLine {
15082 stop_at_soft_wraps: false,
15083 stop_at_indent: action.stop_at_indent,
15084 },
15085 window,
15086 cx,
15087 );
15088 this.backspace(&Backspace, window, cx);
15089 });
15090 }
15091
15092 pub fn move_to_end_of_line(
15093 &mut self,
15094 action: &MoveToEndOfLine,
15095 window: &mut Window,
15096 cx: &mut Context<Self>,
15097 ) {
15098 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15099 self.change_selections(Default::default(), window, cx, |s| {
15100 s.move_cursors_with(&mut |map, head, _| {
15101 (
15102 movement::line_end(map, head, action.stop_at_soft_wraps),
15103 SelectionGoal::None,
15104 )
15105 });
15106 })
15107 }
15108
15109 pub fn select_to_end_of_line(
15110 &mut self,
15111 action: &SelectToEndOfLine,
15112 window: &mut Window,
15113 cx: &mut Context<Self>,
15114 ) {
15115 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15116 self.change_selections(Default::default(), window, cx, |s| {
15117 s.move_heads_with(&mut |map, head, _| {
15118 (
15119 movement::line_end(map, head, action.stop_at_soft_wraps),
15120 SelectionGoal::None,
15121 )
15122 });
15123 })
15124 }
15125
15126 pub fn delete_to_end_of_line(
15127 &mut self,
15128 _: &DeleteToEndOfLine,
15129 window: &mut Window,
15130 cx: &mut Context<Self>,
15131 ) {
15132 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15133 self.transact(window, cx, |this, window, cx| {
15134 this.select_to_end_of_line(
15135 &SelectToEndOfLine {
15136 stop_at_soft_wraps: false,
15137 },
15138 window,
15139 cx,
15140 );
15141 this.delete(&Delete, window, cx);
15142 });
15143 }
15144
15145 pub fn cut_to_end_of_line(
15146 &mut self,
15147 action: &CutToEndOfLine,
15148 window: &mut Window,
15149 cx: &mut Context<Self>,
15150 ) {
15151 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15152 self.transact(window, cx, |this, window, cx| {
15153 this.select_to_end_of_line(
15154 &SelectToEndOfLine {
15155 stop_at_soft_wraps: false,
15156 },
15157 window,
15158 cx,
15159 );
15160 if !action.stop_at_newlines {
15161 this.change_selections(Default::default(), window, cx, |s| {
15162 s.move_with(&mut |_, sel| {
15163 if sel.is_empty() {
15164 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
15165 }
15166 });
15167 });
15168 }
15169 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15170 let item = this.cut_common(false, window, cx);
15171 cx.write_to_clipboard(item);
15172 });
15173 }
15174
15175 pub fn move_to_start_of_paragraph(
15176 &mut self,
15177 _: &MoveToStartOfParagraph,
15178 window: &mut Window,
15179 cx: &mut Context<Self>,
15180 ) {
15181 if matches!(self.mode, EditorMode::SingleLine) {
15182 cx.propagate();
15183 return;
15184 }
15185 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15186 self.change_selections(Default::default(), window, cx, |s| {
15187 s.move_with(&mut |map, selection| {
15188 selection.collapse_to(
15189 movement::start_of_paragraph(map, selection.head(), 1),
15190 SelectionGoal::None,
15191 )
15192 });
15193 })
15194 }
15195
15196 pub fn move_to_end_of_paragraph(
15197 &mut self,
15198 _: &MoveToEndOfParagraph,
15199 window: &mut Window,
15200 cx: &mut Context<Self>,
15201 ) {
15202 if matches!(self.mode, EditorMode::SingleLine) {
15203 cx.propagate();
15204 return;
15205 }
15206 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15207 self.change_selections(Default::default(), window, cx, |s| {
15208 s.move_with(&mut |map, selection| {
15209 selection.collapse_to(
15210 movement::end_of_paragraph(map, selection.head(), 1),
15211 SelectionGoal::None,
15212 )
15213 });
15214 })
15215 }
15216
15217 pub fn select_to_start_of_paragraph(
15218 &mut self,
15219 _: &SelectToStartOfParagraph,
15220 window: &mut Window,
15221 cx: &mut Context<Self>,
15222 ) {
15223 if matches!(self.mode, EditorMode::SingleLine) {
15224 cx.propagate();
15225 return;
15226 }
15227 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15228 self.change_selections(Default::default(), window, cx, |s| {
15229 s.move_heads_with(&mut |map, head, _| {
15230 (
15231 movement::start_of_paragraph(map, head, 1),
15232 SelectionGoal::None,
15233 )
15234 });
15235 })
15236 }
15237
15238 pub fn select_to_end_of_paragraph(
15239 &mut self,
15240 _: &SelectToEndOfParagraph,
15241 window: &mut Window,
15242 cx: &mut Context<Self>,
15243 ) {
15244 if matches!(self.mode, EditorMode::SingleLine) {
15245 cx.propagate();
15246 return;
15247 }
15248 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15249 self.change_selections(Default::default(), window, cx, |s| {
15250 s.move_heads_with(&mut |map, head, _| {
15251 (
15252 movement::end_of_paragraph(map, head, 1),
15253 SelectionGoal::None,
15254 )
15255 });
15256 })
15257 }
15258
15259 pub fn move_to_start_of_excerpt(
15260 &mut self,
15261 _: &MoveToStartOfExcerpt,
15262 window: &mut Window,
15263 cx: &mut Context<Self>,
15264 ) {
15265 if matches!(self.mode, EditorMode::SingleLine) {
15266 cx.propagate();
15267 return;
15268 }
15269 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15270 self.change_selections(Default::default(), window, cx, |s| {
15271 s.move_with(&mut |map, selection| {
15272 selection.collapse_to(
15273 movement::start_of_excerpt(
15274 map,
15275 selection.head(),
15276 workspace::searchable::Direction::Prev,
15277 ),
15278 SelectionGoal::None,
15279 )
15280 });
15281 })
15282 }
15283
15284 pub fn move_to_start_of_next_excerpt(
15285 &mut self,
15286 _: &MoveToStartOfNextExcerpt,
15287 window: &mut Window,
15288 cx: &mut Context<Self>,
15289 ) {
15290 if matches!(self.mode, EditorMode::SingleLine) {
15291 cx.propagate();
15292 return;
15293 }
15294
15295 self.change_selections(Default::default(), window, cx, |s| {
15296 s.move_with(&mut |map, selection| {
15297 selection.collapse_to(
15298 movement::start_of_excerpt(
15299 map,
15300 selection.head(),
15301 workspace::searchable::Direction::Next,
15302 ),
15303 SelectionGoal::None,
15304 )
15305 });
15306 })
15307 }
15308
15309 pub fn move_to_end_of_excerpt(
15310 &mut self,
15311 _: &MoveToEndOfExcerpt,
15312 window: &mut Window,
15313 cx: &mut Context<Self>,
15314 ) {
15315 if matches!(self.mode, EditorMode::SingleLine) {
15316 cx.propagate();
15317 return;
15318 }
15319 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15320 self.change_selections(Default::default(), window, cx, |s| {
15321 s.move_with(&mut |map, selection| {
15322 selection.collapse_to(
15323 movement::end_of_excerpt(
15324 map,
15325 selection.head(),
15326 workspace::searchable::Direction::Next,
15327 ),
15328 SelectionGoal::None,
15329 )
15330 });
15331 })
15332 }
15333
15334 pub fn move_to_end_of_previous_excerpt(
15335 &mut self,
15336 _: &MoveToEndOfPreviousExcerpt,
15337 window: &mut Window,
15338 cx: &mut Context<Self>,
15339 ) {
15340 if matches!(self.mode, EditorMode::SingleLine) {
15341 cx.propagate();
15342 return;
15343 }
15344 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15345 self.change_selections(Default::default(), window, cx, |s| {
15346 s.move_with(&mut |map, selection| {
15347 selection.collapse_to(
15348 movement::end_of_excerpt(
15349 map,
15350 selection.head(),
15351 workspace::searchable::Direction::Prev,
15352 ),
15353 SelectionGoal::None,
15354 )
15355 });
15356 })
15357 }
15358
15359 pub fn select_to_start_of_excerpt(
15360 &mut self,
15361 _: &SelectToStartOfExcerpt,
15362 window: &mut Window,
15363 cx: &mut Context<Self>,
15364 ) {
15365 if matches!(self.mode, EditorMode::SingleLine) {
15366 cx.propagate();
15367 return;
15368 }
15369 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15370 self.change_selections(Default::default(), window, cx, |s| {
15371 s.move_heads_with(&mut |map, head, _| {
15372 (
15373 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15374 SelectionGoal::None,
15375 )
15376 });
15377 })
15378 }
15379
15380 pub fn select_to_start_of_next_excerpt(
15381 &mut self,
15382 _: &SelectToStartOfNextExcerpt,
15383 window: &mut Window,
15384 cx: &mut Context<Self>,
15385 ) {
15386 if matches!(self.mode, EditorMode::SingleLine) {
15387 cx.propagate();
15388 return;
15389 }
15390 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15391 self.change_selections(Default::default(), window, cx, |s| {
15392 s.move_heads_with(&mut |map, head, _| {
15393 (
15394 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
15395 SelectionGoal::None,
15396 )
15397 });
15398 })
15399 }
15400
15401 pub fn select_to_end_of_excerpt(
15402 &mut self,
15403 _: &SelectToEndOfExcerpt,
15404 window: &mut Window,
15405 cx: &mut Context<Self>,
15406 ) {
15407 if matches!(self.mode, EditorMode::SingleLine) {
15408 cx.propagate();
15409 return;
15410 }
15411 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15412 self.change_selections(Default::default(), window, cx, |s| {
15413 s.move_heads_with(&mut |map, head, _| {
15414 (
15415 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
15416 SelectionGoal::None,
15417 )
15418 });
15419 })
15420 }
15421
15422 pub fn select_to_end_of_previous_excerpt(
15423 &mut self,
15424 _: &SelectToEndOfPreviousExcerpt,
15425 window: &mut Window,
15426 cx: &mut Context<Self>,
15427 ) {
15428 if matches!(self.mode, EditorMode::SingleLine) {
15429 cx.propagate();
15430 return;
15431 }
15432 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15433 self.change_selections(Default::default(), window, cx, |s| {
15434 s.move_heads_with(&mut |map, head, _| {
15435 (
15436 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15437 SelectionGoal::None,
15438 )
15439 });
15440 })
15441 }
15442
15443 pub fn move_to_beginning(
15444 &mut self,
15445 _: &MoveToBeginning,
15446 window: &mut Window,
15447 cx: &mut Context<Self>,
15448 ) {
15449 if matches!(self.mode, EditorMode::SingleLine) {
15450 cx.propagate();
15451 return;
15452 }
15453 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15454 self.change_selections(Default::default(), window, cx, |s| {
15455 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
15456 });
15457 }
15458
15459 pub fn select_to_beginning(
15460 &mut self,
15461 _: &SelectToBeginning,
15462 window: &mut Window,
15463 cx: &mut Context<Self>,
15464 ) {
15465 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
15466 selection.set_head(Point::zero(), SelectionGoal::None);
15467 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15468 self.change_selections(Default::default(), window, cx, |s| {
15469 s.select(vec![selection]);
15470 });
15471 }
15472
15473 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
15474 if matches!(self.mode, EditorMode::SingleLine) {
15475 cx.propagate();
15476 return;
15477 }
15478 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15479 let cursor = self.buffer.read(cx).read(cx).len();
15480 self.change_selections(Default::default(), window, cx, |s| {
15481 s.select_ranges(vec![cursor..cursor])
15482 });
15483 }
15484
15485 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
15486 self.nav_history = nav_history;
15487 }
15488
15489 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
15490 self.nav_history.as_ref()
15491 }
15492
15493 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
15494 self.push_to_nav_history(
15495 self.selections.newest_anchor().head(),
15496 None,
15497 false,
15498 true,
15499 cx,
15500 );
15501 }
15502
15503 fn navigation_data(&self, cursor_anchor: Anchor, cx: &mut Context<Self>) -> NavigationData {
15504 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15505 let buffer = self.buffer.read(cx).read(cx);
15506 let cursor_position = cursor_anchor.to_point(&buffer);
15507 let scroll_anchor = self.scroll_manager.native_anchor(&display_snapshot, cx);
15508 let scroll_top_row = scroll_anchor.top_row(&buffer);
15509 drop(buffer);
15510
15511 NavigationData {
15512 cursor_anchor,
15513 cursor_position,
15514 scroll_anchor,
15515 scroll_top_row,
15516 }
15517 }
15518
15519 fn navigation_entry(
15520 &self,
15521 cursor_anchor: Anchor,
15522 cx: &mut Context<Self>,
15523 ) -> Option<NavigationEntry> {
15524 let Some(history) = self.nav_history.clone() else {
15525 return None;
15526 };
15527 let data = self.navigation_data(cursor_anchor, cx);
15528 Some(history.navigation_entry(Some(Arc::new(data) as Arc<dyn Any + Send + Sync>)))
15529 }
15530
15531 fn push_to_nav_history(
15532 &mut self,
15533 cursor_anchor: Anchor,
15534 new_position: Option<Point>,
15535 is_deactivate: bool,
15536 always: bool,
15537 cx: &mut Context<Self>,
15538 ) {
15539 let data = self.navigation_data(cursor_anchor, cx);
15540 if let Some(nav_history) = self.nav_history.as_mut() {
15541 if let Some(new_position) = new_position {
15542 let row_delta = (new_position.row as i64 - data.cursor_position.row as i64).abs();
15543 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
15544 return;
15545 }
15546 }
15547
15548 nav_history.push(Some(data), cx);
15549 cx.emit(EditorEvent::PushedToNavHistory {
15550 anchor: cursor_anchor,
15551 is_deactivate,
15552 })
15553 }
15554 }
15555
15556 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
15557 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15558 let buffer = self.buffer.read(cx).snapshot(cx);
15559 let mut selection = self
15560 .selections
15561 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
15562 selection.set_head(buffer.len(), SelectionGoal::None);
15563 self.change_selections(Default::default(), window, cx, |s| {
15564 s.select(vec![selection]);
15565 });
15566 }
15567
15568 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
15569 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15570 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15571 s.select_ranges([Anchor::min()..Anchor::max()]);
15572 });
15573 }
15574
15575 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
15576 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15577 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15578 let mut selections = self.selections.all::<Point>(&display_map);
15579 let max_point = display_map.buffer_snapshot().max_point();
15580 for selection in &mut selections {
15581 let rows = selection.spanned_rows(true, &display_map);
15582 selection.start = Point::new(rows.start.0, 0);
15583 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
15584 selection.reversed = false;
15585 }
15586 self.change_selections(Default::default(), window, cx, |s| {
15587 s.select(selections);
15588 });
15589 }
15590
15591 pub fn split_selection_into_lines(
15592 &mut self,
15593 action: &SplitSelectionIntoLines,
15594 window: &mut Window,
15595 cx: &mut Context<Self>,
15596 ) {
15597 let selections = self
15598 .selections
15599 .all::<Point>(&self.display_snapshot(cx))
15600 .into_iter()
15601 .map(|selection| selection.start..selection.end)
15602 .collect::<Vec<_>>();
15603 self.unfold_ranges(&selections, true, false, cx);
15604
15605 let mut new_selection_ranges = Vec::new();
15606 {
15607 let buffer = self.buffer.read(cx).read(cx);
15608 for selection in selections {
15609 for row in selection.start.row..selection.end.row {
15610 let line_start = Point::new(row, 0);
15611 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
15612
15613 if action.keep_selections {
15614 // Keep the selection range for each line
15615 let selection_start = if row == selection.start.row {
15616 selection.start
15617 } else {
15618 line_start
15619 };
15620 new_selection_ranges.push(selection_start..line_end);
15621 } else {
15622 // Collapse to cursor at end of line
15623 new_selection_ranges.push(line_end..line_end);
15624 }
15625 }
15626
15627 let is_multiline_selection = selection.start.row != selection.end.row;
15628 // Don't insert last one if it's a multi-line selection ending at the start of a line,
15629 // so this action feels more ergonomic when paired with other selection operations
15630 let should_skip_last = is_multiline_selection && selection.end.column == 0;
15631 if !should_skip_last {
15632 if action.keep_selections {
15633 if is_multiline_selection {
15634 let line_start = Point::new(selection.end.row, 0);
15635 new_selection_ranges.push(line_start..selection.end);
15636 } else {
15637 new_selection_ranges.push(selection.start..selection.end);
15638 }
15639 } else {
15640 new_selection_ranges.push(selection.end..selection.end);
15641 }
15642 }
15643 }
15644 }
15645 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15646 s.select_ranges(new_selection_ranges);
15647 });
15648 }
15649
15650 pub fn add_selection_above(
15651 &mut self,
15652 action: &AddSelectionAbove,
15653 window: &mut Window,
15654 cx: &mut Context<Self>,
15655 ) {
15656 self.add_selection(true, action.skip_soft_wrap, window, cx);
15657 }
15658
15659 pub fn add_selection_below(
15660 &mut self,
15661 action: &AddSelectionBelow,
15662 window: &mut Window,
15663 cx: &mut Context<Self>,
15664 ) {
15665 self.add_selection(false, action.skip_soft_wrap, window, cx);
15666 }
15667
15668 fn add_selection(
15669 &mut self,
15670 above: bool,
15671 skip_soft_wrap: bool,
15672 window: &mut Window,
15673 cx: &mut Context<Self>,
15674 ) {
15675 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15676
15677 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15678 let all_selections = self.selections.all::<Point>(&display_map);
15679 let text_layout_details = self.text_layout_details(window, cx);
15680
15681 let (mut columnar_selections, new_selections_to_columnarize) = {
15682 if let Some(state) = self.add_selections_state.as_ref() {
15683 let columnar_selection_ids: HashSet<_> = state
15684 .groups
15685 .iter()
15686 .flat_map(|group| group.stack.iter())
15687 .copied()
15688 .collect();
15689
15690 all_selections
15691 .into_iter()
15692 .partition(|s| columnar_selection_ids.contains(&s.id))
15693 } else {
15694 (Vec::new(), all_selections)
15695 }
15696 };
15697
15698 let mut state = self
15699 .add_selections_state
15700 .take()
15701 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
15702
15703 for selection in new_selections_to_columnarize {
15704 let range = selection.display_range(&display_map).sorted();
15705 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
15706 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
15707 let positions = start_x.min(end_x)..start_x.max(end_x);
15708 let mut stack = Vec::new();
15709 for row in range.start.row().0..=range.end.row().0 {
15710 if let Some(selection) = self.selections.build_columnar_selection(
15711 &display_map,
15712 DisplayRow(row),
15713 &positions,
15714 selection.reversed,
15715 &text_layout_details,
15716 ) {
15717 stack.push(selection.id);
15718 columnar_selections.push(selection);
15719 }
15720 }
15721 if !stack.is_empty() {
15722 if above {
15723 stack.reverse();
15724 }
15725 state.groups.push(AddSelectionsGroup { above, stack });
15726 }
15727 }
15728
15729 let mut final_selections = Vec::new();
15730 let end_row = if above {
15731 DisplayRow(0)
15732 } else {
15733 display_map.max_point().row()
15734 };
15735
15736 // When `skip_soft_wrap` is true, we use UTF-16 columns instead of pixel
15737 // positions to place new selections, so we need to keep track of the
15738 // column range of the oldest selection in each group, because
15739 // intermediate selections may have been clamped to shorter lines.
15740 let mut goal_columns_by_selection_id = if skip_soft_wrap {
15741 let mut map = HashMap::default();
15742 for group in state.groups.iter() {
15743 if let Some(oldest_id) = group.stack.first() {
15744 if let Some(oldest_selection) =
15745 columnar_selections.iter().find(|s| s.id == *oldest_id)
15746 {
15747 let snapshot = display_map.buffer_snapshot();
15748 let start_col =
15749 snapshot.point_to_point_utf16(oldest_selection.start).column;
15750 let end_col = snapshot.point_to_point_utf16(oldest_selection.end).column;
15751 let goal_columns = start_col.min(end_col)..start_col.max(end_col);
15752 for id in &group.stack {
15753 map.insert(*id, goal_columns.clone());
15754 }
15755 }
15756 }
15757 }
15758 map
15759 } else {
15760 HashMap::default()
15761 };
15762
15763 let mut last_added_item_per_group = HashMap::default();
15764 for group in state.groups.iter_mut() {
15765 if let Some(last_id) = group.stack.last() {
15766 last_added_item_per_group.insert(*last_id, group);
15767 }
15768 }
15769
15770 for selection in columnar_selections {
15771 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
15772 if above == group.above {
15773 let range = selection.display_range(&display_map).sorted();
15774 debug_assert_eq!(range.start.row(), range.end.row());
15775 let row = range.start.row();
15776 let positions =
15777 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
15778 Pixels::from(start)..Pixels::from(end)
15779 } else {
15780 let start_x =
15781 display_map.x_for_display_point(range.start, &text_layout_details);
15782 let end_x =
15783 display_map.x_for_display_point(range.end, &text_layout_details);
15784 start_x.min(end_x)..start_x.max(end_x)
15785 };
15786
15787 let maybe_new_selection = if skip_soft_wrap {
15788 let goal_columns = goal_columns_by_selection_id
15789 .remove(&selection.id)
15790 .unwrap_or_else(|| {
15791 let snapshot = display_map.buffer_snapshot();
15792 let start_col =
15793 snapshot.point_to_point_utf16(selection.start).column;
15794 let end_col = snapshot.point_to_point_utf16(selection.end).column;
15795 start_col.min(end_col)..start_col.max(end_col)
15796 });
15797 self.selections.find_next_columnar_selection_by_buffer_row(
15798 &display_map,
15799 row,
15800 end_row,
15801 above,
15802 &goal_columns,
15803 selection.reversed,
15804 &text_layout_details,
15805 )
15806 } else {
15807 self.selections.find_next_columnar_selection_by_display_row(
15808 &display_map,
15809 row,
15810 end_row,
15811 above,
15812 &positions,
15813 selection.reversed,
15814 &text_layout_details,
15815 )
15816 };
15817
15818 if let Some(new_selection) = maybe_new_selection {
15819 group.stack.push(new_selection.id);
15820 if above {
15821 final_selections.push(new_selection);
15822 final_selections.push(selection);
15823 } else {
15824 final_selections.push(selection);
15825 final_selections.push(new_selection);
15826 }
15827 } else {
15828 final_selections.push(selection);
15829 }
15830 } else {
15831 group.stack.pop();
15832 }
15833 } else {
15834 final_selections.push(selection);
15835 }
15836 }
15837
15838 self.change_selections(Default::default(), window, cx, |s| {
15839 s.select(final_selections);
15840 });
15841
15842 let final_selection_ids: HashSet<_> = self
15843 .selections
15844 .all::<Point>(&display_map)
15845 .iter()
15846 .map(|s| s.id)
15847 .collect();
15848 state.groups.retain_mut(|group| {
15849 // selections might get merged above so we remove invalid items from stacks
15850 group.stack.retain(|id| final_selection_ids.contains(id));
15851
15852 // single selection in stack can be treated as initial state
15853 group.stack.len() > 1
15854 });
15855
15856 if !state.groups.is_empty() {
15857 self.add_selections_state = Some(state);
15858 }
15859 }
15860
15861 pub fn insert_snippet_at_selections(
15862 &mut self,
15863 action: &InsertSnippet,
15864 window: &mut Window,
15865 cx: &mut Context<Self>,
15866 ) {
15867 self.try_insert_snippet_at_selections(action, window, cx)
15868 .log_err();
15869 }
15870
15871 fn try_insert_snippet_at_selections(
15872 &mut self,
15873 action: &InsertSnippet,
15874 window: &mut Window,
15875 cx: &mut Context<Self>,
15876 ) -> Result<()> {
15877 let insertion_ranges = self
15878 .selections
15879 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15880 .into_iter()
15881 .map(|selection| selection.range())
15882 .collect_vec();
15883
15884 let snippet = if let Some(snippet_body) = &action.snippet {
15885 if action.language.is_none() && action.name.is_none() {
15886 Snippet::parse(snippet_body)?
15887 } else {
15888 bail!("`snippet` is mutually exclusive with `language` and `name`")
15889 }
15890 } else if let Some(name) = &action.name {
15891 let project = self.project().context("no project")?;
15892 let snippet_store = project.read(cx).snippets().read(cx);
15893 let snippet = snippet_store
15894 .snippets_for(action.language.clone(), cx)
15895 .into_iter()
15896 .find(|snippet| snippet.name == *name)
15897 .context("snippet not found")?;
15898 Snippet::parse(&snippet.body)?
15899 } else {
15900 // todo(andrew): open modal to select snippet
15901 bail!("`name` or `snippet` is required")
15902 };
15903
15904 self.insert_snippet(&insertion_ranges, snippet, window, cx)
15905 }
15906
15907 fn select_match_ranges(
15908 &mut self,
15909 range: Range<MultiBufferOffset>,
15910 reversed: bool,
15911 replace_newest: bool,
15912 auto_scroll: Option<Autoscroll>,
15913 window: &mut Window,
15914 cx: &mut Context<Editor>,
15915 ) {
15916 self.unfold_ranges(
15917 std::slice::from_ref(&range),
15918 false,
15919 auto_scroll.is_some(),
15920 cx,
15921 );
15922 let effects = if let Some(scroll) = auto_scroll {
15923 SelectionEffects::scroll(scroll)
15924 } else {
15925 SelectionEffects::no_scroll()
15926 };
15927 self.change_selections(effects, window, cx, |s| {
15928 if replace_newest {
15929 s.delete(s.newest_anchor().id);
15930 }
15931 if reversed {
15932 s.insert_range(range.end..range.start);
15933 } else {
15934 s.insert_range(range);
15935 }
15936 });
15937 }
15938
15939 pub fn select_next_match_internal(
15940 &mut self,
15941 display_map: &DisplaySnapshot,
15942 replace_newest: bool,
15943 autoscroll: Option<Autoscroll>,
15944 window: &mut Window,
15945 cx: &mut Context<Self>,
15946 ) -> Result<()> {
15947 let buffer = display_map.buffer_snapshot();
15948 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15949 if let Some(mut select_next_state) = self.select_next_state.take() {
15950 let query = &select_next_state.query;
15951 if !select_next_state.done {
15952 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15953 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15954 let mut next_selected_range = None;
15955
15956 let bytes_after_last_selection =
15957 buffer.bytes_in_range(last_selection.end..buffer.len());
15958 let bytes_before_first_selection =
15959 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
15960 let query_matches = query
15961 .stream_find_iter(bytes_after_last_selection)
15962 .map(|result| (last_selection.end, result))
15963 .chain(
15964 query
15965 .stream_find_iter(bytes_before_first_selection)
15966 .map(|result| (MultiBufferOffset(0), result)),
15967 );
15968
15969 for (start_offset, query_match) in query_matches {
15970 let query_match = query_match.unwrap(); // can only fail due to I/O
15971 let offset_range =
15972 start_offset + query_match.start()..start_offset + query_match.end();
15973
15974 if !select_next_state.wordwise
15975 || (!buffer.is_inside_word(offset_range.start, None)
15976 && !buffer.is_inside_word(offset_range.end, None))
15977 {
15978 let idx = selections
15979 .partition_point(|selection| selection.end <= offset_range.start);
15980 let overlaps = selections
15981 .get(idx)
15982 .map_or(false, |selection| selection.start < offset_range.end);
15983
15984 if !overlaps {
15985 next_selected_range = Some(offset_range);
15986 break;
15987 }
15988 }
15989 }
15990
15991 if let Some(next_selected_range) = next_selected_range {
15992 self.select_match_ranges(
15993 next_selected_range,
15994 last_selection.reversed,
15995 replace_newest,
15996 autoscroll,
15997 window,
15998 cx,
15999 );
16000 } else {
16001 select_next_state.done = true;
16002 }
16003 }
16004
16005 self.select_next_state = Some(select_next_state);
16006 } else {
16007 let mut only_carets = true;
16008 let mut same_text_selected = true;
16009 let mut selected_text = None;
16010
16011 let mut selections_iter = selections.iter().peekable();
16012 while let Some(selection) = selections_iter.next() {
16013 if selection.start != selection.end {
16014 only_carets = false;
16015 }
16016
16017 if same_text_selected {
16018 if selected_text.is_none() {
16019 selected_text =
16020 Some(buffer.text_for_range(selection.range()).collect::<String>());
16021 }
16022
16023 if let Some(next_selection) = selections_iter.peek() {
16024 if next_selection.len() == selection.len() {
16025 let next_selected_text = buffer
16026 .text_for_range(next_selection.range())
16027 .collect::<String>();
16028 if Some(next_selected_text) != selected_text {
16029 same_text_selected = false;
16030 selected_text = None;
16031 }
16032 } else {
16033 same_text_selected = false;
16034 selected_text = None;
16035 }
16036 }
16037 }
16038 }
16039
16040 if only_carets {
16041 for selection in &mut selections {
16042 let (word_range, _) = buffer.surrounding_word(selection.start, None);
16043 selection.start = word_range.start;
16044 selection.end = word_range.end;
16045 selection.goal = SelectionGoal::None;
16046 selection.reversed = false;
16047 self.select_match_ranges(
16048 selection.start..selection.end,
16049 selection.reversed,
16050 replace_newest,
16051 autoscroll,
16052 window,
16053 cx,
16054 );
16055 }
16056
16057 if selections.len() == 1 {
16058 let selection = selections
16059 .last()
16060 .expect("ensured that there's only one selection");
16061 let query = buffer
16062 .text_for_range(selection.start..selection.end)
16063 .collect::<String>();
16064 let is_empty = query.is_empty();
16065 let select_state = SelectNextState {
16066 query: self.build_query(&[query], cx)?,
16067 wordwise: true,
16068 done: is_empty,
16069 };
16070 self.select_next_state = Some(select_state);
16071 } else {
16072 self.select_next_state = None;
16073 }
16074 } else if let Some(selected_text) = selected_text {
16075 self.select_next_state = Some(SelectNextState {
16076 query: self.build_query(&[selected_text], cx)?,
16077 wordwise: false,
16078 done: false,
16079 });
16080 self.select_next_match_internal(
16081 display_map,
16082 replace_newest,
16083 autoscroll,
16084 window,
16085 cx,
16086 )?;
16087 }
16088 }
16089 Ok(())
16090 }
16091
16092 pub fn select_all_matches(
16093 &mut self,
16094 _action: &SelectAllMatches,
16095 window: &mut Window,
16096 cx: &mut Context<Self>,
16097 ) -> Result<()> {
16098 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16099
16100 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16101
16102 self.select_next_match_internal(&display_map, false, None, window, cx)?;
16103 let Some(select_next_state) = self.select_next_state.as_mut().filter(|state| !state.done)
16104 else {
16105 return Ok(());
16106 };
16107
16108 let mut new_selections = Vec::new();
16109
16110 let reversed = self
16111 .selections
16112 .oldest::<MultiBufferOffset>(&display_map)
16113 .reversed;
16114 let buffer = display_map.buffer_snapshot();
16115 let query_matches = select_next_state
16116 .query
16117 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
16118
16119 for query_match in query_matches.into_iter() {
16120 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
16121 let offset_range = if reversed {
16122 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
16123 } else {
16124 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
16125 };
16126
16127 if !select_next_state.wordwise
16128 || (!buffer.is_inside_word(offset_range.start, None)
16129 && !buffer.is_inside_word(offset_range.end, None))
16130 {
16131 new_selections.push(offset_range.start..offset_range.end);
16132 }
16133 }
16134
16135 select_next_state.done = true;
16136
16137 if new_selections.is_empty() {
16138 log::error!("bug: new_selections is empty in select_all_matches");
16139 return Ok(());
16140 }
16141
16142 self.unfold_ranges(&new_selections, false, false, cx);
16143 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
16144 selections.select_ranges(new_selections)
16145 });
16146
16147 Ok(())
16148 }
16149
16150 pub fn select_next(
16151 &mut self,
16152 action: &SelectNext,
16153 window: &mut Window,
16154 cx: &mut Context<Self>,
16155 ) -> Result<()> {
16156 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16157 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16158 self.select_next_match_internal(
16159 &display_map,
16160 action.replace_newest,
16161 Some(Autoscroll::newest()),
16162 window,
16163 cx,
16164 )
16165 }
16166
16167 pub fn select_previous(
16168 &mut self,
16169 action: &SelectPrevious,
16170 window: &mut Window,
16171 cx: &mut Context<Self>,
16172 ) -> Result<()> {
16173 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16174 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16175 let buffer = display_map.buffer_snapshot();
16176 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
16177 if let Some(mut select_prev_state) = self.select_prev_state.take() {
16178 let query = &select_prev_state.query;
16179 if !select_prev_state.done {
16180 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
16181 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
16182 let mut next_selected_range = None;
16183 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
16184 let bytes_before_last_selection =
16185 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
16186 let bytes_after_first_selection =
16187 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
16188 let query_matches = query
16189 .stream_find_iter(bytes_before_last_selection)
16190 .map(|result| (last_selection.start, result))
16191 .chain(
16192 query
16193 .stream_find_iter(bytes_after_first_selection)
16194 .map(|result| (buffer.len(), result)),
16195 );
16196 for (end_offset, query_match) in query_matches {
16197 let query_match = query_match.unwrap(); // can only fail due to I/O
16198 let offset_range =
16199 end_offset - query_match.end()..end_offset - query_match.start();
16200
16201 if !select_prev_state.wordwise
16202 || (!buffer.is_inside_word(offset_range.start, None)
16203 && !buffer.is_inside_word(offset_range.end, None))
16204 {
16205 next_selected_range = Some(offset_range);
16206 break;
16207 }
16208 }
16209
16210 if let Some(next_selected_range) = next_selected_range {
16211 self.select_match_ranges(
16212 next_selected_range,
16213 last_selection.reversed,
16214 action.replace_newest,
16215 Some(Autoscroll::newest()),
16216 window,
16217 cx,
16218 );
16219 } else {
16220 select_prev_state.done = true;
16221 }
16222 }
16223
16224 self.select_prev_state = Some(select_prev_state);
16225 } else {
16226 let mut only_carets = true;
16227 let mut same_text_selected = true;
16228 let mut selected_text = None;
16229
16230 let mut selections_iter = selections.iter().peekable();
16231 while let Some(selection) = selections_iter.next() {
16232 if selection.start != selection.end {
16233 only_carets = false;
16234 }
16235
16236 if same_text_selected {
16237 if selected_text.is_none() {
16238 selected_text =
16239 Some(buffer.text_for_range(selection.range()).collect::<String>());
16240 }
16241
16242 if let Some(next_selection) = selections_iter.peek() {
16243 if next_selection.len() == selection.len() {
16244 let next_selected_text = buffer
16245 .text_for_range(next_selection.range())
16246 .collect::<String>();
16247 if Some(next_selected_text) != selected_text {
16248 same_text_selected = false;
16249 selected_text = None;
16250 }
16251 } else {
16252 same_text_selected = false;
16253 selected_text = None;
16254 }
16255 }
16256 }
16257 }
16258
16259 if only_carets {
16260 for selection in &mut selections {
16261 let (word_range, _) = buffer.surrounding_word(selection.start, None);
16262 selection.start = word_range.start;
16263 selection.end = word_range.end;
16264 selection.goal = SelectionGoal::None;
16265 selection.reversed = false;
16266 self.select_match_ranges(
16267 selection.start..selection.end,
16268 selection.reversed,
16269 action.replace_newest,
16270 Some(Autoscroll::newest()),
16271 window,
16272 cx,
16273 );
16274 }
16275 if selections.len() == 1 {
16276 let selection = selections
16277 .last()
16278 .expect("ensured that there's only one selection");
16279 let query = buffer
16280 .text_for_range(selection.start..selection.end)
16281 .collect::<String>();
16282 let is_empty = query.is_empty();
16283 let select_state = SelectNextState {
16284 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
16285 wordwise: true,
16286 done: is_empty,
16287 };
16288 self.select_prev_state = Some(select_state);
16289 } else {
16290 self.select_prev_state = None;
16291 }
16292 } else if let Some(selected_text) = selected_text {
16293 self.select_prev_state = Some(SelectNextState {
16294 query: self
16295 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
16296 wordwise: false,
16297 done: false,
16298 });
16299 self.select_previous(action, window, cx)?;
16300 }
16301 }
16302 Ok(())
16303 }
16304
16305 /// Builds an `AhoCorasick` automaton from the provided patterns, while
16306 /// setting the case sensitivity based on the global
16307 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
16308 /// editor's settings.
16309 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
16310 where
16311 I: IntoIterator<Item = P>,
16312 P: AsRef<[u8]>,
16313 {
16314 let case_sensitive = self
16315 .select_next_is_case_sensitive
16316 .unwrap_or_else(|| EditorSettings::get_global(cx).search.case_sensitive);
16317
16318 let mut builder = AhoCorasickBuilder::new();
16319 builder.ascii_case_insensitive(!case_sensitive);
16320 builder.build(patterns)
16321 }
16322
16323 pub fn find_next_match(
16324 &mut self,
16325 _: &FindNextMatch,
16326 window: &mut Window,
16327 cx: &mut Context<Self>,
16328 ) -> Result<()> {
16329 let selections = self.selections.disjoint_anchors_arc();
16330 match selections.first() {
16331 Some(first) if selections.len() >= 2 => {
16332 self.change_selections(Default::default(), window, cx, |s| {
16333 s.select_ranges([first.range()]);
16334 });
16335 }
16336 _ => self.select_next(
16337 &SelectNext {
16338 replace_newest: true,
16339 },
16340 window,
16341 cx,
16342 )?,
16343 }
16344 Ok(())
16345 }
16346
16347 pub fn find_previous_match(
16348 &mut self,
16349 _: &FindPreviousMatch,
16350 window: &mut Window,
16351 cx: &mut Context<Self>,
16352 ) -> Result<()> {
16353 let selections = self.selections.disjoint_anchors_arc();
16354 match selections.last() {
16355 Some(last) if selections.len() >= 2 => {
16356 self.change_selections(Default::default(), window, cx, |s| {
16357 s.select_ranges([last.range()]);
16358 });
16359 }
16360 _ => self.select_previous(
16361 &SelectPrevious {
16362 replace_newest: true,
16363 },
16364 window,
16365 cx,
16366 )?,
16367 }
16368 Ok(())
16369 }
16370
16371 pub fn toggle_comments(
16372 &mut self,
16373 action: &ToggleComments,
16374 window: &mut Window,
16375 cx: &mut Context<Self>,
16376 ) {
16377 if self.read_only(cx) {
16378 return;
16379 }
16380 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16381 let text_layout_details = &self.text_layout_details(window, cx);
16382 self.transact(window, cx, |this, window, cx| {
16383 let mut selections = this
16384 .selections
16385 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
16386 let mut edits = Vec::new();
16387 let mut selection_edit_ranges = Vec::new();
16388 let mut last_toggled_row = None;
16389 let snapshot = this.buffer.read(cx).read(cx);
16390 let empty_str: Arc<str> = Arc::default();
16391 let mut suffixes_inserted = Vec::new();
16392 let ignore_indent = action.ignore_indent;
16393
16394 fn comment_prefix_range(
16395 snapshot: &MultiBufferSnapshot,
16396 row: MultiBufferRow,
16397 comment_prefix: &str,
16398 comment_prefix_whitespace: &str,
16399 ignore_indent: bool,
16400 ) -> Range<Point> {
16401 let indent_size = if ignore_indent {
16402 0
16403 } else {
16404 snapshot.indent_size_for_line(row).len
16405 };
16406
16407 let start = Point::new(row.0, indent_size);
16408
16409 let mut line_bytes = snapshot
16410 .bytes_in_range(start..snapshot.max_point())
16411 .flatten()
16412 .copied();
16413
16414 // If this line currently begins with the line comment prefix, then record
16415 // the range containing the prefix.
16416 if line_bytes
16417 .by_ref()
16418 .take(comment_prefix.len())
16419 .eq(comment_prefix.bytes())
16420 {
16421 // Include any whitespace that matches the comment prefix.
16422 let matching_whitespace_len = line_bytes
16423 .zip(comment_prefix_whitespace.bytes())
16424 .take_while(|(a, b)| a == b)
16425 .count() as u32;
16426 let end = Point::new(
16427 start.row,
16428 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
16429 );
16430 start..end
16431 } else {
16432 start..start
16433 }
16434 }
16435
16436 fn comment_suffix_range(
16437 snapshot: &MultiBufferSnapshot,
16438 row: MultiBufferRow,
16439 comment_suffix: &str,
16440 comment_suffix_has_leading_space: bool,
16441 ) -> Range<Point> {
16442 let end = Point::new(row.0, snapshot.line_len(row));
16443 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
16444
16445 let mut line_end_bytes = snapshot
16446 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
16447 .flatten()
16448 .copied();
16449
16450 let leading_space_len = if suffix_start_column > 0
16451 && line_end_bytes.next() == Some(b' ')
16452 && comment_suffix_has_leading_space
16453 {
16454 1
16455 } else {
16456 0
16457 };
16458
16459 // If this line currently begins with the line comment prefix, then record
16460 // the range containing the prefix.
16461 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
16462 let start = Point::new(end.row, suffix_start_column - leading_space_len);
16463 start..end
16464 } else {
16465 end..end
16466 }
16467 }
16468
16469 // TODO: Handle selections that cross excerpts
16470 for selection in &mut selections {
16471 let start_column = snapshot
16472 .indent_size_for_line(MultiBufferRow(selection.start.row))
16473 .len;
16474 let language = if let Some(language) =
16475 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
16476 {
16477 language
16478 } else {
16479 continue;
16480 };
16481
16482 selection_edit_ranges.clear();
16483
16484 // If multiple selections contain a given row, avoid processing that
16485 // row more than once.
16486 let mut start_row = MultiBufferRow(selection.start.row);
16487 if last_toggled_row == Some(start_row) {
16488 start_row = start_row.next_row();
16489 }
16490 let end_row =
16491 if selection.end.row > selection.start.row && selection.end.column == 0 {
16492 MultiBufferRow(selection.end.row - 1)
16493 } else {
16494 MultiBufferRow(selection.end.row)
16495 };
16496 last_toggled_row = Some(end_row);
16497
16498 if start_row > end_row {
16499 continue;
16500 }
16501
16502 // If the language has line comments, toggle those.
16503 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
16504
16505 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
16506 if ignore_indent {
16507 full_comment_prefixes = full_comment_prefixes
16508 .into_iter()
16509 .map(|s| Arc::from(s.trim_end()))
16510 .collect();
16511 }
16512
16513 if !full_comment_prefixes.is_empty() {
16514 let first_prefix = full_comment_prefixes
16515 .first()
16516 .expect("prefixes is non-empty");
16517 let prefix_trimmed_lengths = full_comment_prefixes
16518 .iter()
16519 .map(|p| p.trim_end_matches(' ').len())
16520 .collect::<SmallVec<[usize; 4]>>();
16521
16522 let mut all_selection_lines_are_comments = true;
16523
16524 for row in start_row.0..=end_row.0 {
16525 let row = MultiBufferRow(row);
16526 if start_row < end_row && snapshot.is_line_blank(row) {
16527 continue;
16528 }
16529
16530 let prefix_range = full_comment_prefixes
16531 .iter()
16532 .zip(prefix_trimmed_lengths.iter().copied())
16533 .map(|(prefix, trimmed_prefix_len)| {
16534 comment_prefix_range(
16535 snapshot.deref(),
16536 row,
16537 &prefix[..trimmed_prefix_len],
16538 &prefix[trimmed_prefix_len..],
16539 ignore_indent,
16540 )
16541 })
16542 .max_by_key(|range| range.end.column - range.start.column)
16543 .expect("prefixes is non-empty");
16544
16545 if prefix_range.is_empty() {
16546 all_selection_lines_are_comments = false;
16547 }
16548
16549 selection_edit_ranges.push(prefix_range);
16550 }
16551
16552 if all_selection_lines_are_comments {
16553 edits.extend(
16554 selection_edit_ranges
16555 .iter()
16556 .cloned()
16557 .map(|range| (range, empty_str.clone())),
16558 );
16559 } else {
16560 let min_column = selection_edit_ranges
16561 .iter()
16562 .map(|range| range.start.column)
16563 .min()
16564 .unwrap_or(0);
16565 edits.extend(selection_edit_ranges.iter().map(|range| {
16566 let position = Point::new(range.start.row, min_column);
16567 (position..position, first_prefix.clone())
16568 }));
16569 }
16570 } else if let Some(BlockCommentConfig {
16571 start: full_comment_prefix,
16572 end: comment_suffix,
16573 ..
16574 }) = language.block_comment()
16575 {
16576 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
16577 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
16578 let prefix_range = comment_prefix_range(
16579 snapshot.deref(),
16580 start_row,
16581 comment_prefix,
16582 comment_prefix_whitespace,
16583 ignore_indent,
16584 );
16585 let suffix_range = comment_suffix_range(
16586 snapshot.deref(),
16587 end_row,
16588 comment_suffix.trim_start_matches(' '),
16589 comment_suffix.starts_with(' '),
16590 );
16591
16592 if prefix_range.is_empty() || suffix_range.is_empty() {
16593 edits.push((
16594 prefix_range.start..prefix_range.start,
16595 full_comment_prefix.clone(),
16596 ));
16597 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
16598 suffixes_inserted.push((end_row, comment_suffix.len()));
16599 } else {
16600 edits.push((prefix_range, empty_str.clone()));
16601 edits.push((suffix_range, empty_str.clone()));
16602 }
16603 } else {
16604 continue;
16605 }
16606 }
16607
16608 drop(snapshot);
16609 this.buffer.update(cx, |buffer, cx| {
16610 buffer.edit(edits, None, cx);
16611 });
16612
16613 // Adjust selections so that they end before any comment suffixes that
16614 // were inserted.
16615 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
16616 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16617 let snapshot = this.buffer.read(cx).read(cx);
16618 for selection in &mut selections {
16619 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
16620 match row.cmp(&MultiBufferRow(selection.end.row)) {
16621 Ordering::Less => {
16622 suffixes_inserted.next();
16623 continue;
16624 }
16625 Ordering::Greater => break,
16626 Ordering::Equal => {
16627 if selection.end.column == snapshot.line_len(row) {
16628 if selection.is_empty() {
16629 selection.start.column -= suffix_len as u32;
16630 }
16631 selection.end.column -= suffix_len as u32;
16632 }
16633 break;
16634 }
16635 }
16636 }
16637 }
16638
16639 drop(snapshot);
16640 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
16641
16642 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16643 let selections_on_single_row = selections.windows(2).all(|selections| {
16644 selections[0].start.row == selections[1].start.row
16645 && selections[0].end.row == selections[1].end.row
16646 && selections[0].start.row == selections[0].end.row
16647 });
16648 let selections_selecting = selections
16649 .iter()
16650 .any(|selection| selection.start != selection.end);
16651 let advance_downwards = action.advance_downwards
16652 && selections_on_single_row
16653 && !selections_selecting
16654 && !matches!(this.mode, EditorMode::SingleLine);
16655
16656 if advance_downwards {
16657 let snapshot = this.buffer.read(cx).snapshot(cx);
16658
16659 this.change_selections(Default::default(), window, cx, |s| {
16660 s.move_cursors_with(&mut |display_snapshot, display_point, _| {
16661 let mut point = display_point.to_point(display_snapshot);
16662 point.row += 1;
16663 point = snapshot.clip_point(point, Bias::Left);
16664 let display_point = point.to_display_point(display_snapshot);
16665 let goal = SelectionGoal::HorizontalPosition(
16666 display_snapshot
16667 .x_for_display_point(display_point, text_layout_details)
16668 .into(),
16669 );
16670 (display_point, goal)
16671 })
16672 });
16673 }
16674 });
16675 }
16676
16677 pub fn select_enclosing_symbol(
16678 &mut self,
16679 _: &SelectEnclosingSymbol,
16680 window: &mut Window,
16681 cx: &mut Context<Self>,
16682 ) {
16683 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16684
16685 let buffer = self.buffer.read(cx).snapshot(cx);
16686 let old_selections = self
16687 .selections
16688 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16689 .into_boxed_slice();
16690
16691 fn update_selection(
16692 selection: &Selection<MultiBufferOffset>,
16693 buffer_snap: &MultiBufferSnapshot,
16694 ) -> Option<Selection<MultiBufferOffset>> {
16695 let cursor = selection.head();
16696 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
16697 for symbol in symbols.iter().rev() {
16698 let start = symbol.range.start.to_offset(buffer_snap);
16699 let end = symbol.range.end.to_offset(buffer_snap);
16700 let new_range = start..end;
16701 if start < selection.start || end > selection.end {
16702 return Some(Selection {
16703 id: selection.id,
16704 start: new_range.start,
16705 end: new_range.end,
16706 goal: SelectionGoal::None,
16707 reversed: selection.reversed,
16708 });
16709 }
16710 }
16711 None
16712 }
16713
16714 let mut selected_larger_symbol = false;
16715 let new_selections = old_selections
16716 .iter()
16717 .map(|selection| match update_selection(selection, &buffer) {
16718 Some(new_selection) => {
16719 if new_selection.range() != selection.range() {
16720 selected_larger_symbol = true;
16721 }
16722 new_selection
16723 }
16724 None => selection.clone(),
16725 })
16726 .collect::<Vec<_>>();
16727
16728 if selected_larger_symbol {
16729 self.change_selections(Default::default(), window, cx, |s| {
16730 s.select(new_selections);
16731 });
16732 }
16733 }
16734
16735 pub fn select_larger_syntax_node(
16736 &mut self,
16737 _: &SelectLargerSyntaxNode,
16738 window: &mut Window,
16739 cx: &mut Context<Self>,
16740 ) {
16741 let Some(visible_row_count) = self.visible_row_count() else {
16742 return;
16743 };
16744 let old_selections: Box<[_]> = self
16745 .selections
16746 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16747 .into();
16748 if old_selections.is_empty() {
16749 return;
16750 }
16751
16752 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16753
16754 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16755 let buffer = self.buffer.read(cx).snapshot(cx);
16756
16757 let mut selected_larger_node = false;
16758 let mut new_selections = old_selections
16759 .iter()
16760 .map(|selection| {
16761 let old_range = selection.start..selection.end;
16762
16763 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
16764 // manually select word at selection
16765 if ["string_content", "inline"].contains(&node.kind()) {
16766 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
16767 // ignore if word is already selected
16768 if !word_range.is_empty() && old_range != word_range {
16769 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
16770 // only select word if start and end point belongs to same word
16771 if word_range == last_word_range {
16772 selected_larger_node = true;
16773 return Selection {
16774 id: selection.id,
16775 start: word_range.start,
16776 end: word_range.end,
16777 goal: SelectionGoal::None,
16778 reversed: selection.reversed,
16779 };
16780 }
16781 }
16782 }
16783 }
16784
16785 let mut new_range = old_range.clone();
16786 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
16787 new_range = range;
16788 if !node.is_named() {
16789 continue;
16790 }
16791 if !display_map.intersects_fold(new_range.start)
16792 && !display_map.intersects_fold(new_range.end)
16793 {
16794 break;
16795 }
16796 }
16797
16798 selected_larger_node |= new_range != old_range;
16799 Selection {
16800 id: selection.id,
16801 start: new_range.start,
16802 end: new_range.end,
16803 goal: SelectionGoal::None,
16804 reversed: selection.reversed,
16805 }
16806 })
16807 .collect::<Vec<_>>();
16808
16809 if !selected_larger_node {
16810 return; // don't put this call in the history
16811 }
16812
16813 // scroll based on transformation done to the last selection created by the user
16814 let (last_old, last_new) = old_selections
16815 .last()
16816 .zip(new_selections.last().cloned())
16817 .expect("old_selections isn't empty");
16818
16819 let is_selection_reversed = if new_selections.len() == 1 {
16820 let should_be_reversed = last_old.start != last_new.start;
16821 new_selections.last_mut().expect("checked above").reversed = should_be_reversed;
16822 should_be_reversed
16823 } else {
16824 last_new.reversed
16825 };
16826
16827 if selected_larger_node {
16828 self.select_syntax_node_history.disable_clearing = true;
16829 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16830 s.select(new_selections.clone());
16831 });
16832 self.select_syntax_node_history.disable_clearing = false;
16833 }
16834
16835 let start_row = last_new.start.to_display_point(&display_map).row().0;
16836 let end_row = last_new.end.to_display_point(&display_map).row().0;
16837 let selection_height = end_row - start_row + 1;
16838 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
16839
16840 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
16841 let scroll_behavior = if fits_on_the_screen {
16842 self.request_autoscroll(Autoscroll::fit(), cx);
16843 SelectSyntaxNodeScrollBehavior::FitSelection
16844 } else if is_selection_reversed {
16845 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16846 SelectSyntaxNodeScrollBehavior::CursorTop
16847 } else {
16848 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16849 SelectSyntaxNodeScrollBehavior::CursorBottom
16850 };
16851
16852 let old_selections: Box<[Selection<Anchor>]> = old_selections
16853 .iter()
16854 .map(|s| s.map(|offset| buffer.anchor_before(offset)))
16855 .collect();
16856 self.select_syntax_node_history.push((
16857 old_selections,
16858 scroll_behavior,
16859 is_selection_reversed,
16860 ));
16861 }
16862
16863 pub fn select_smaller_syntax_node(
16864 &mut self,
16865 _: &SelectSmallerSyntaxNode,
16866 window: &mut Window,
16867 cx: &mut Context<Self>,
16868 ) {
16869 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16870
16871 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
16872 self.select_syntax_node_history.pop()
16873 {
16874 if let Some(selection) = selections.last_mut() {
16875 selection.reversed = is_selection_reversed;
16876 }
16877
16878 let snapshot = self.buffer.read(cx).snapshot(cx);
16879 let selections: Vec<Selection<MultiBufferOffset>> = selections
16880 .iter()
16881 .map(|s| s.map(|anchor| anchor.to_offset(&snapshot)))
16882 .collect();
16883
16884 self.select_syntax_node_history.disable_clearing = true;
16885 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16886 s.select(selections);
16887 });
16888 self.select_syntax_node_history.disable_clearing = false;
16889
16890 match scroll_behavior {
16891 SelectSyntaxNodeScrollBehavior::CursorTop => {
16892 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16893 }
16894 SelectSyntaxNodeScrollBehavior::FitSelection => {
16895 self.request_autoscroll(Autoscroll::fit(), cx);
16896 }
16897 SelectSyntaxNodeScrollBehavior::CursorBottom => {
16898 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16899 }
16900 }
16901 }
16902 }
16903
16904 pub fn unwrap_syntax_node(
16905 &mut self,
16906 _: &UnwrapSyntaxNode,
16907 window: &mut Window,
16908 cx: &mut Context<Self>,
16909 ) {
16910 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16911
16912 let buffer = self.buffer.read(cx).snapshot(cx);
16913 let selections = self
16914 .selections
16915 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16916 .into_iter()
16917 // subtracting the offset requires sorting
16918 .sorted_by_key(|i| i.start);
16919
16920 let full_edits = selections
16921 .into_iter()
16922 .filter_map(|selection| {
16923 let child = if selection.is_empty()
16924 && let Some((_, ancestor_range)) =
16925 buffer.syntax_ancestor(selection.start..selection.end)
16926 {
16927 ancestor_range
16928 } else {
16929 selection.range()
16930 };
16931
16932 let mut parent = child.clone();
16933 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
16934 parent = ancestor_range;
16935 if parent.start < child.start || parent.end > child.end {
16936 break;
16937 }
16938 }
16939
16940 if parent == child {
16941 return None;
16942 }
16943 let text = buffer.text_for_range(child).collect::<String>();
16944 Some((selection.id, parent, text))
16945 })
16946 .collect::<Vec<_>>();
16947 if full_edits.is_empty() {
16948 return;
16949 }
16950
16951 self.transact(window, cx, |this, window, cx| {
16952 this.buffer.update(cx, |buffer, cx| {
16953 buffer.edit(
16954 full_edits
16955 .iter()
16956 .map(|(_, p, t)| (p.clone(), t.clone()))
16957 .collect::<Vec<_>>(),
16958 None,
16959 cx,
16960 );
16961 });
16962 this.change_selections(Default::default(), window, cx, |s| {
16963 let mut offset = 0;
16964 let mut selections = vec![];
16965 for (id, parent, text) in full_edits {
16966 let start = parent.start - offset;
16967 offset += (parent.end - parent.start) - text.len();
16968 selections.push(Selection {
16969 id,
16970 start,
16971 end: start + text.len(),
16972 reversed: false,
16973 goal: Default::default(),
16974 });
16975 }
16976 s.select(selections);
16977 });
16978 });
16979 }
16980
16981 pub fn select_next_syntax_node(
16982 &mut self,
16983 _: &SelectNextSyntaxNode,
16984 window: &mut Window,
16985 cx: &mut Context<Self>,
16986 ) {
16987 let old_selections: Box<[_]> = self
16988 .selections
16989 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16990 .into();
16991 if old_selections.is_empty() {
16992 return;
16993 }
16994
16995 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16996
16997 let buffer = self.buffer.read(cx).snapshot(cx);
16998 let mut selected_sibling = false;
16999
17000 let new_selections = old_selections
17001 .iter()
17002 .map(|selection| {
17003 let old_range = selection.start..selection.end;
17004
17005 let old_range =
17006 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
17007 let excerpt = buffer.excerpt_containing(old_range.clone());
17008
17009 if let Some(mut excerpt) = excerpt
17010 && let Some(node) = excerpt
17011 .buffer()
17012 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
17013 {
17014 let new_range = excerpt.map_range_from_buffer(
17015 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
17016 );
17017 selected_sibling = true;
17018 Selection {
17019 id: selection.id,
17020 start: new_range.start,
17021 end: new_range.end,
17022 goal: SelectionGoal::None,
17023 reversed: selection.reversed,
17024 }
17025 } else {
17026 selection.clone()
17027 }
17028 })
17029 .collect::<Vec<_>>();
17030
17031 if selected_sibling {
17032 self.change_selections(
17033 SelectionEffects::scroll(Autoscroll::fit()),
17034 window,
17035 cx,
17036 |s| {
17037 s.select(new_selections);
17038 },
17039 );
17040 }
17041 }
17042
17043 pub fn select_prev_syntax_node(
17044 &mut self,
17045 _: &SelectPreviousSyntaxNode,
17046 window: &mut Window,
17047 cx: &mut Context<Self>,
17048 ) {
17049 let old_selections: Box<[_]> = self
17050 .selections
17051 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
17052 .into();
17053 if old_selections.is_empty() {
17054 return;
17055 }
17056
17057 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17058
17059 let buffer = self.buffer.read(cx).snapshot(cx);
17060 let mut selected_sibling = false;
17061
17062 let new_selections = old_selections
17063 .iter()
17064 .map(|selection| {
17065 let old_range = selection.start..selection.end;
17066 let old_range =
17067 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
17068 let excerpt = buffer.excerpt_containing(old_range.clone());
17069
17070 if let Some(mut excerpt) = excerpt
17071 && let Some(node) = excerpt
17072 .buffer()
17073 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
17074 {
17075 let new_range = excerpt.map_range_from_buffer(
17076 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
17077 );
17078 selected_sibling = true;
17079 Selection {
17080 id: selection.id,
17081 start: new_range.start,
17082 end: new_range.end,
17083 goal: SelectionGoal::None,
17084 reversed: selection.reversed,
17085 }
17086 } else {
17087 selection.clone()
17088 }
17089 })
17090 .collect::<Vec<_>>();
17091
17092 if selected_sibling {
17093 self.change_selections(
17094 SelectionEffects::scroll(Autoscroll::fit()),
17095 window,
17096 cx,
17097 |s| {
17098 s.select(new_selections);
17099 },
17100 );
17101 }
17102 }
17103
17104 pub fn move_to_start_of_larger_syntax_node(
17105 &mut self,
17106 _: &MoveToStartOfLargerSyntaxNode,
17107 window: &mut Window,
17108 cx: &mut Context<Self>,
17109 ) {
17110 self.move_cursors_to_syntax_nodes(window, cx, false);
17111 }
17112
17113 pub fn move_to_end_of_larger_syntax_node(
17114 &mut self,
17115 _: &MoveToEndOfLargerSyntaxNode,
17116 window: &mut Window,
17117 cx: &mut Context<Self>,
17118 ) {
17119 self.move_cursors_to_syntax_nodes(window, cx, true);
17120 }
17121
17122 fn find_syntax_node_boundary(
17123 &self,
17124 selection_pos: MultiBufferOffset,
17125 move_to_end: bool,
17126 display_map: &DisplaySnapshot,
17127 buffer: &MultiBufferSnapshot,
17128 ) -> MultiBufferOffset {
17129 let old_range = selection_pos..selection_pos;
17130 let mut new_pos = selection_pos;
17131 let mut search_range = old_range;
17132 while let Some((node, range)) = buffer.syntax_ancestor(search_range.clone()) {
17133 search_range = range.clone();
17134 if !node.is_named()
17135 || display_map.intersects_fold(range.start)
17136 || display_map.intersects_fold(range.end)
17137 // If cursor is already at the end of the syntax node, continue searching
17138 || (move_to_end && range.end == selection_pos)
17139 // If cursor is already at the start of the syntax node, continue searching
17140 || (!move_to_end && range.start == selection_pos)
17141 {
17142 continue;
17143 }
17144
17145 // If we found a string_content node, find the largest parent that is still string_content
17146 // Enables us to skip to the end of strings without taking multiple steps inside the string
17147 let (_, final_range) = if node.kind() == "string_content" {
17148 let mut current_node = node;
17149 let mut current_range = range;
17150 while let Some((parent, parent_range)) =
17151 buffer.syntax_ancestor(current_range.clone())
17152 {
17153 if parent.kind() == "string_content" {
17154 current_node = parent;
17155 current_range = parent_range;
17156 } else {
17157 break;
17158 }
17159 }
17160
17161 (current_node, current_range)
17162 } else {
17163 (node, range)
17164 };
17165
17166 new_pos = if move_to_end {
17167 final_range.end
17168 } else {
17169 final_range.start
17170 };
17171
17172 break;
17173 }
17174
17175 new_pos
17176 }
17177
17178 fn move_cursors_to_syntax_nodes(
17179 &mut self,
17180 window: &mut Window,
17181 cx: &mut Context<Self>,
17182 move_to_end: bool,
17183 ) -> bool {
17184 let old_selections: Box<[_]> = self
17185 .selections
17186 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
17187 .into();
17188 if old_selections.is_empty() {
17189 return false;
17190 }
17191
17192 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17193
17194 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17195 let buffer = self.buffer.read(cx).snapshot(cx);
17196
17197 let mut any_cursor_moved = false;
17198 let new_selections = old_selections
17199 .iter()
17200 .map(|selection| {
17201 if !selection.is_empty() {
17202 return selection.clone();
17203 }
17204
17205 let selection_pos = selection.head();
17206 let new_pos = self.find_syntax_node_boundary(
17207 selection_pos,
17208 move_to_end,
17209 &display_map,
17210 &buffer,
17211 );
17212
17213 any_cursor_moved |= new_pos != selection_pos;
17214
17215 Selection {
17216 id: selection.id,
17217 start: new_pos,
17218 end: new_pos,
17219 goal: SelectionGoal::None,
17220 reversed: false,
17221 }
17222 })
17223 .collect::<Vec<_>>();
17224
17225 self.change_selections(Default::default(), window, cx, |s| {
17226 s.select(new_selections);
17227 });
17228 self.request_autoscroll(Autoscroll::newest(), cx);
17229
17230 any_cursor_moved
17231 }
17232
17233 pub fn select_to_start_of_larger_syntax_node(
17234 &mut self,
17235 _: &SelectToStartOfLargerSyntaxNode,
17236 window: &mut Window,
17237 cx: &mut Context<Self>,
17238 ) {
17239 self.select_to_syntax_nodes(window, cx, false);
17240 }
17241
17242 pub fn select_to_end_of_larger_syntax_node(
17243 &mut self,
17244 _: &SelectToEndOfLargerSyntaxNode,
17245 window: &mut Window,
17246 cx: &mut Context<Self>,
17247 ) {
17248 self.select_to_syntax_nodes(window, cx, true);
17249 }
17250
17251 fn select_to_syntax_nodes(
17252 &mut self,
17253 window: &mut Window,
17254 cx: &mut Context<Self>,
17255 move_to_end: bool,
17256 ) {
17257 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17258
17259 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17260 let buffer = self.buffer.read(cx).snapshot(cx);
17261 let old_selections = self.selections.all::<MultiBufferOffset>(&display_map);
17262
17263 let new_selections = old_selections
17264 .iter()
17265 .map(|selection| {
17266 let new_pos = self.find_syntax_node_boundary(
17267 selection.head(),
17268 move_to_end,
17269 &display_map,
17270 &buffer,
17271 );
17272
17273 let mut new_selection = selection.clone();
17274 new_selection.set_head(new_pos, SelectionGoal::None);
17275 new_selection
17276 })
17277 .collect::<Vec<_>>();
17278
17279 self.change_selections(Default::default(), window, cx, |s| {
17280 s.select(new_selections);
17281 });
17282 }
17283
17284 pub fn move_to_enclosing_bracket(
17285 &mut self,
17286 _: &MoveToEnclosingBracket,
17287 window: &mut Window,
17288 cx: &mut Context<Self>,
17289 ) {
17290 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17291 self.change_selections(Default::default(), window, cx, |s| {
17292 s.move_offsets_with(&mut |snapshot, selection| {
17293 let Some(enclosing_bracket_ranges) =
17294 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
17295 else {
17296 return;
17297 };
17298
17299 let mut best_length = usize::MAX;
17300 let mut best_inside = false;
17301 let mut best_in_bracket_range = false;
17302 let mut best_destination = None;
17303 for (open, close) in enclosing_bracket_ranges {
17304 let close = close.to_inclusive();
17305 let length = *close.end() - open.start;
17306 let inside = selection.start >= open.end && selection.end <= *close.start();
17307 let in_bracket_range = open.to_inclusive().contains(&selection.head())
17308 || close.contains(&selection.head());
17309
17310 // If best is next to a bracket and current isn't, skip
17311 if !in_bracket_range && best_in_bracket_range {
17312 continue;
17313 }
17314
17315 // Prefer smaller lengths unless best is inside and current isn't
17316 if length > best_length && (best_inside || !inside) {
17317 continue;
17318 }
17319
17320 best_length = length;
17321 best_inside = inside;
17322 best_in_bracket_range = in_bracket_range;
17323 best_destination = Some(
17324 if close.contains(&selection.start) && close.contains(&selection.end) {
17325 if inside { open.end } else { open.start }
17326 } else if inside {
17327 *close.start()
17328 } else {
17329 *close.end()
17330 },
17331 );
17332 }
17333
17334 if let Some(destination) = best_destination {
17335 selection.collapse_to(destination, SelectionGoal::None);
17336 }
17337 })
17338 });
17339 }
17340
17341 pub fn undo_selection(
17342 &mut self,
17343 _: &UndoSelection,
17344 window: &mut Window,
17345 cx: &mut Context<Self>,
17346 ) {
17347 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17348 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
17349 self.selection_history.mode = SelectionHistoryMode::Undoing;
17350 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17351 this.end_selection(window, cx);
17352 this.change_selections(
17353 SelectionEffects::scroll(Autoscroll::newest()),
17354 window,
17355 cx,
17356 |s| s.select_anchors(entry.selections.to_vec()),
17357 );
17358 });
17359 self.selection_history.mode = SelectionHistoryMode::Normal;
17360
17361 self.select_next_state = entry.select_next_state;
17362 self.select_prev_state = entry.select_prev_state;
17363 self.add_selections_state = entry.add_selections_state;
17364 }
17365 }
17366
17367 pub fn redo_selection(
17368 &mut self,
17369 _: &RedoSelection,
17370 window: &mut Window,
17371 cx: &mut Context<Self>,
17372 ) {
17373 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17374 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
17375 self.selection_history.mode = SelectionHistoryMode::Redoing;
17376 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17377 this.end_selection(window, cx);
17378 this.change_selections(
17379 SelectionEffects::scroll(Autoscroll::newest()),
17380 window,
17381 cx,
17382 |s| s.select_anchors(entry.selections.to_vec()),
17383 );
17384 });
17385 self.selection_history.mode = SelectionHistoryMode::Normal;
17386
17387 self.select_next_state = entry.select_next_state;
17388 self.select_prev_state = entry.select_prev_state;
17389 self.add_selections_state = entry.add_selections_state;
17390 }
17391 }
17392
17393 pub fn expand_excerpts(
17394 &mut self,
17395 action: &ExpandExcerpts,
17396 _: &mut Window,
17397 cx: &mut Context<Self>,
17398 ) {
17399 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
17400 }
17401
17402 pub fn expand_excerpts_down(
17403 &mut self,
17404 action: &ExpandExcerptsDown,
17405 _: &mut Window,
17406 cx: &mut Context<Self>,
17407 ) {
17408 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
17409 }
17410
17411 pub fn expand_excerpts_up(
17412 &mut self,
17413 action: &ExpandExcerptsUp,
17414 _: &mut Window,
17415 cx: &mut Context<Self>,
17416 ) {
17417 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
17418 }
17419
17420 pub fn expand_excerpts_for_direction(
17421 &mut self,
17422 lines: u32,
17423 direction: ExpandExcerptDirection,
17424 cx: &mut Context<Self>,
17425 ) {
17426 let selections = self.selections.disjoint_anchors_arc();
17427
17428 let lines = if lines == 0 {
17429 EditorSettings::get_global(cx).expand_excerpt_lines
17430 } else {
17431 lines
17432 };
17433
17434 let snapshot = self.buffer.read(cx).snapshot(cx);
17435 let excerpt_ids = selections
17436 .iter()
17437 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
17438 .unique()
17439 .sorted()
17440 .collect::<Vec<_>>();
17441
17442 if self.delegate_expand_excerpts {
17443 cx.emit(EditorEvent::ExpandExcerptsRequested {
17444 excerpt_ids,
17445 lines,
17446 direction,
17447 });
17448 return;
17449 }
17450
17451 self.buffer.update(cx, |buffer, cx| {
17452 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
17453 })
17454 }
17455
17456 pub fn expand_excerpt(
17457 &mut self,
17458 excerpt: ExcerptId,
17459 direction: ExpandExcerptDirection,
17460 window: &mut Window,
17461 cx: &mut Context<Self>,
17462 ) {
17463 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
17464
17465 if self.delegate_expand_excerpts {
17466 cx.emit(EditorEvent::ExpandExcerptsRequested {
17467 excerpt_ids: vec![excerpt],
17468 lines: lines_to_expand,
17469 direction,
17470 });
17471 return;
17472 }
17473
17474 let current_scroll_position = self.scroll_position(cx);
17475 let mut scroll = None;
17476
17477 if direction == ExpandExcerptDirection::Down {
17478 let multi_buffer = self.buffer.read(cx);
17479 let snapshot = multi_buffer.snapshot(cx);
17480 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
17481 && let Some(buffer) = multi_buffer.buffer(buffer_id)
17482 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
17483 {
17484 let buffer_snapshot = buffer.read(cx).snapshot();
17485 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
17486 let last_row = buffer_snapshot.max_point().row;
17487 let lines_below = last_row.saturating_sub(excerpt_end_row);
17488 if lines_below >= lines_to_expand {
17489 scroll = Some(
17490 current_scroll_position
17491 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
17492 );
17493 }
17494 }
17495 }
17496 if direction == ExpandExcerptDirection::Up
17497 && self
17498 .buffer
17499 .read(cx)
17500 .snapshot(cx)
17501 .excerpt_before(excerpt)
17502 .is_none()
17503 {
17504 scroll = Some(current_scroll_position);
17505 }
17506
17507 self.buffer.update(cx, |buffer, cx| {
17508 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
17509 });
17510
17511 if let Some(new_scroll_position) = scroll {
17512 self.set_scroll_position(new_scroll_position, window, cx);
17513 }
17514 }
17515
17516 pub fn go_to_singleton_buffer_point(
17517 &mut self,
17518 point: Point,
17519 window: &mut Window,
17520 cx: &mut Context<Self>,
17521 ) {
17522 self.go_to_singleton_buffer_range(point..point, window, cx);
17523 }
17524
17525 pub fn go_to_singleton_buffer_range(
17526 &mut self,
17527 range: Range<Point>,
17528 window: &mut Window,
17529 cx: &mut Context<Self>,
17530 ) {
17531 let multibuffer = self.buffer().read(cx);
17532 let Some(buffer) = multibuffer.as_singleton() else {
17533 return;
17534 };
17535 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
17536 return;
17537 };
17538 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
17539 return;
17540 };
17541 self.change_selections(
17542 SelectionEffects::default().nav_history(true),
17543 window,
17544 cx,
17545 |s| s.select_anchor_ranges([start..end]),
17546 );
17547 }
17548
17549 pub fn go_to_diagnostic(
17550 &mut self,
17551 action: &GoToDiagnostic,
17552 window: &mut Window,
17553 cx: &mut Context<Self>,
17554 ) {
17555 if !self.diagnostics_enabled() {
17556 return;
17557 }
17558 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17559 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
17560 }
17561
17562 pub fn go_to_prev_diagnostic(
17563 &mut self,
17564 action: &GoToPreviousDiagnostic,
17565 window: &mut Window,
17566 cx: &mut Context<Self>,
17567 ) {
17568 if !self.diagnostics_enabled() {
17569 return;
17570 }
17571 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17572 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
17573 }
17574
17575 pub fn go_to_diagnostic_impl(
17576 &mut self,
17577 direction: Direction,
17578 severity: GoToDiagnosticSeverityFilter,
17579 window: &mut Window,
17580 cx: &mut Context<Self>,
17581 ) {
17582 let buffer = self.buffer.read(cx).snapshot(cx);
17583 let selection = self
17584 .selections
17585 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
17586
17587 let mut active_group_id = None;
17588 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
17589 && active_group.active_range.start.to_offset(&buffer) == selection.start
17590 {
17591 active_group_id = Some(active_group.group_id);
17592 }
17593
17594 fn filtered<'a>(
17595 severity: GoToDiagnosticSeverityFilter,
17596 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
17597 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
17598 diagnostics
17599 .filter(move |entry| severity.matches(entry.diagnostic.severity))
17600 .filter(|entry| entry.range.start != entry.range.end)
17601 .filter(|entry| !entry.diagnostic.is_unnecessary)
17602 }
17603
17604 let before = filtered(
17605 severity,
17606 buffer
17607 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
17608 .filter(|entry| entry.range.start <= selection.start),
17609 );
17610 let after = filtered(
17611 severity,
17612 buffer
17613 .diagnostics_in_range(selection.start..buffer.len())
17614 .filter(|entry| entry.range.start >= selection.start),
17615 );
17616
17617 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
17618 if direction == Direction::Prev {
17619 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
17620 {
17621 for diagnostic in prev_diagnostics.into_iter().rev() {
17622 if diagnostic.range.start != selection.start
17623 || active_group_id
17624 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
17625 {
17626 found = Some(diagnostic);
17627 break 'outer;
17628 }
17629 }
17630 }
17631 } else {
17632 for diagnostic in after.chain(before) {
17633 if diagnostic.range.start != selection.start
17634 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
17635 {
17636 found = Some(diagnostic);
17637 break;
17638 }
17639 }
17640 }
17641 let Some(next_diagnostic) = found else {
17642 return;
17643 };
17644
17645 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
17646 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
17647 return;
17648 };
17649 let snapshot = self.snapshot(window, cx);
17650 if snapshot.intersects_fold(next_diagnostic.range.start) {
17651 self.unfold_ranges(
17652 std::slice::from_ref(&next_diagnostic.range),
17653 true,
17654 false,
17655 cx,
17656 );
17657 }
17658 self.change_selections(Default::default(), window, cx, |s| {
17659 s.select_ranges(vec![
17660 next_diagnostic.range.start..next_diagnostic.range.start,
17661 ])
17662 });
17663 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
17664 self.refresh_edit_prediction(false, true, window, cx);
17665 }
17666
17667 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
17668 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17669 let snapshot = self.snapshot(window, cx);
17670 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
17671 self.go_to_hunk_before_or_after_position(
17672 &snapshot,
17673 selection.head(),
17674 Direction::Next,
17675 true,
17676 window,
17677 cx,
17678 );
17679 }
17680
17681 pub fn go_to_hunk_before_or_after_position(
17682 &mut self,
17683 snapshot: &EditorSnapshot,
17684 position: Point,
17685 direction: Direction,
17686 wrap_around: bool,
17687 window: &mut Window,
17688 cx: &mut Context<Editor>,
17689 ) {
17690 let row = if direction == Direction::Next {
17691 self.hunk_after_position(snapshot, position, wrap_around)
17692 .map(|hunk| hunk.row_range.start)
17693 } else {
17694 self.hunk_before_position(snapshot, position, wrap_around)
17695 };
17696
17697 if let Some(row) = row {
17698 let destination = Point::new(row.0, 0);
17699 let autoscroll = Autoscroll::center();
17700
17701 self.unfold_ranges(&[destination..destination], false, false, cx);
17702 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17703 s.select_ranges([destination..destination]);
17704 });
17705 }
17706 }
17707
17708 fn hunk_after_position(
17709 &mut self,
17710 snapshot: &EditorSnapshot,
17711 position: Point,
17712 wrap_around: bool,
17713 ) -> Option<MultiBufferDiffHunk> {
17714 let result = snapshot
17715 .buffer_snapshot()
17716 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
17717 .find(|hunk| hunk.row_range.start.0 > position.row);
17718
17719 if wrap_around {
17720 result.or_else(|| {
17721 snapshot
17722 .buffer_snapshot()
17723 .diff_hunks_in_range(Point::zero()..position)
17724 .find(|hunk| hunk.row_range.end.0 < position.row)
17725 })
17726 } else {
17727 result
17728 }
17729 }
17730
17731 fn go_to_prev_hunk(
17732 &mut self,
17733 _: &GoToPreviousHunk,
17734 window: &mut Window,
17735 cx: &mut Context<Self>,
17736 ) {
17737 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17738 let snapshot = self.snapshot(window, cx);
17739 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
17740 self.go_to_hunk_before_or_after_position(
17741 &snapshot,
17742 selection.head(),
17743 Direction::Prev,
17744 true,
17745 window,
17746 cx,
17747 );
17748 }
17749
17750 fn hunk_before_position(
17751 &mut self,
17752 snapshot: &EditorSnapshot,
17753 position: Point,
17754 wrap_around: bool,
17755 ) -> Option<MultiBufferRow> {
17756 let result = snapshot.buffer_snapshot().diff_hunk_before(position);
17757
17758 if wrap_around {
17759 result.or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
17760 } else {
17761 result
17762 }
17763 }
17764
17765 fn go_to_next_change(
17766 &mut self,
17767 _: &GoToNextChange,
17768 window: &mut Window,
17769 cx: &mut Context<Self>,
17770 ) {
17771 if let Some(selections) = self
17772 .change_list
17773 .next_change(1, Direction::Next)
17774 .map(|s| s.to_vec())
17775 {
17776 self.change_selections(Default::default(), window, cx, |s| {
17777 let map = s.display_snapshot();
17778 s.select_display_ranges(selections.iter().map(|a| {
17779 let point = a.to_display_point(&map);
17780 point..point
17781 }))
17782 })
17783 }
17784 }
17785
17786 fn go_to_previous_change(
17787 &mut self,
17788 _: &GoToPreviousChange,
17789 window: &mut Window,
17790 cx: &mut Context<Self>,
17791 ) {
17792 if let Some(selections) = self
17793 .change_list
17794 .next_change(1, Direction::Prev)
17795 .map(|s| s.to_vec())
17796 {
17797 self.change_selections(Default::default(), window, cx, |s| {
17798 let map = s.display_snapshot();
17799 s.select_display_ranges(selections.iter().map(|a| {
17800 let point = a.to_display_point(&map);
17801 point..point
17802 }))
17803 })
17804 }
17805 }
17806
17807 pub fn go_to_next_document_highlight(
17808 &mut self,
17809 _: &GoToNextDocumentHighlight,
17810 window: &mut Window,
17811 cx: &mut Context<Self>,
17812 ) {
17813 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
17814 }
17815
17816 pub fn go_to_prev_document_highlight(
17817 &mut self,
17818 _: &GoToPreviousDocumentHighlight,
17819 window: &mut Window,
17820 cx: &mut Context<Self>,
17821 ) {
17822 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
17823 }
17824
17825 pub fn go_to_document_highlight_before_or_after_position(
17826 &mut self,
17827 direction: Direction,
17828 window: &mut Window,
17829 cx: &mut Context<Editor>,
17830 ) {
17831 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17832 let snapshot = self.snapshot(window, cx);
17833 let buffer = &snapshot.buffer_snapshot();
17834 let position = self
17835 .selections
17836 .newest::<Point>(&snapshot.display_snapshot)
17837 .head();
17838 let anchor_position = buffer.anchor_after(position);
17839
17840 // Get all document highlights (both read and write)
17841 let mut all_highlights = Vec::new();
17842
17843 if let Some((_, read_highlights)) = self
17844 .background_highlights
17845 .get(&HighlightKey::DocumentHighlightRead)
17846 {
17847 all_highlights.extend(read_highlights.iter());
17848 }
17849
17850 if let Some((_, write_highlights)) = self
17851 .background_highlights
17852 .get(&HighlightKey::DocumentHighlightWrite)
17853 {
17854 all_highlights.extend(write_highlights.iter());
17855 }
17856
17857 if all_highlights.is_empty() {
17858 return;
17859 }
17860
17861 // Sort highlights by position
17862 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
17863
17864 let target_highlight = match direction {
17865 Direction::Next => {
17866 // Find the first highlight after the current position
17867 all_highlights
17868 .iter()
17869 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
17870 }
17871 Direction::Prev => {
17872 // Find the last highlight before the current position
17873 all_highlights
17874 .iter()
17875 .rev()
17876 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
17877 }
17878 };
17879
17880 if let Some(highlight) = target_highlight {
17881 let destination = highlight.start.to_point(buffer);
17882 let autoscroll = Autoscroll::center();
17883
17884 self.unfold_ranges(&[destination..destination], false, false, cx);
17885 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17886 s.select_ranges([destination..destination]);
17887 });
17888 }
17889 }
17890
17891 fn go_to_line<T: 'static>(
17892 &mut self,
17893 position: Anchor,
17894 highlight_color: Option<Hsla>,
17895 window: &mut Window,
17896 cx: &mut Context<Self>,
17897 ) {
17898 let snapshot = self.snapshot(window, cx).display_snapshot;
17899 let position = position.to_point(&snapshot.buffer_snapshot());
17900 let start = snapshot
17901 .buffer_snapshot()
17902 .clip_point(Point::new(position.row, 0), Bias::Left);
17903 let end = start + Point::new(1, 0);
17904 let start = snapshot.buffer_snapshot().anchor_before(start);
17905 let end = snapshot.buffer_snapshot().anchor_before(end);
17906
17907 self.highlight_rows::<T>(
17908 start..end,
17909 highlight_color
17910 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
17911 Default::default(),
17912 cx,
17913 );
17914
17915 if self.buffer.read(cx).is_singleton() {
17916 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
17917 }
17918 }
17919
17920 pub fn go_to_definition(
17921 &mut self,
17922 _: &GoToDefinition,
17923 window: &mut Window,
17924 cx: &mut Context<Self>,
17925 ) -> Task<Result<Navigated>> {
17926 let definition =
17927 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
17928 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
17929 cx.spawn_in(window, async move |editor, cx| {
17930 if definition.await? == Navigated::Yes {
17931 return Ok(Navigated::Yes);
17932 }
17933 match fallback_strategy {
17934 GoToDefinitionFallback::None => Ok(Navigated::No),
17935 GoToDefinitionFallback::FindAllReferences => {
17936 match editor.update_in(cx, |editor, window, cx| {
17937 editor.find_all_references(&FindAllReferences::default(), window, cx)
17938 })? {
17939 Some(references) => references.await,
17940 None => Ok(Navigated::No),
17941 }
17942 }
17943 }
17944 })
17945 }
17946
17947 pub fn go_to_declaration(
17948 &mut self,
17949 _: &GoToDeclaration,
17950 window: &mut Window,
17951 cx: &mut Context<Self>,
17952 ) -> Task<Result<Navigated>> {
17953 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
17954 }
17955
17956 pub fn go_to_declaration_split(
17957 &mut self,
17958 _: &GoToDeclaration,
17959 window: &mut Window,
17960 cx: &mut Context<Self>,
17961 ) -> Task<Result<Navigated>> {
17962 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
17963 }
17964
17965 pub fn go_to_implementation(
17966 &mut self,
17967 _: &GoToImplementation,
17968 window: &mut Window,
17969 cx: &mut Context<Self>,
17970 ) -> Task<Result<Navigated>> {
17971 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
17972 }
17973
17974 pub fn go_to_implementation_split(
17975 &mut self,
17976 _: &GoToImplementationSplit,
17977 window: &mut Window,
17978 cx: &mut Context<Self>,
17979 ) -> Task<Result<Navigated>> {
17980 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
17981 }
17982
17983 pub fn go_to_type_definition(
17984 &mut self,
17985 _: &GoToTypeDefinition,
17986 window: &mut Window,
17987 cx: &mut Context<Self>,
17988 ) -> Task<Result<Navigated>> {
17989 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
17990 }
17991
17992 pub fn go_to_definition_split(
17993 &mut self,
17994 _: &GoToDefinitionSplit,
17995 window: &mut Window,
17996 cx: &mut Context<Self>,
17997 ) -> Task<Result<Navigated>> {
17998 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
17999 }
18000
18001 pub fn go_to_type_definition_split(
18002 &mut self,
18003 _: &GoToTypeDefinitionSplit,
18004 window: &mut Window,
18005 cx: &mut Context<Self>,
18006 ) -> Task<Result<Navigated>> {
18007 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
18008 }
18009
18010 fn go_to_definition_of_kind(
18011 &mut self,
18012 kind: GotoDefinitionKind,
18013 split: bool,
18014 window: &mut Window,
18015 cx: &mut Context<Self>,
18016 ) -> Task<Result<Navigated>> {
18017 let Some(provider) = self.semantics_provider.clone() else {
18018 return Task::ready(Ok(Navigated::No));
18019 };
18020 let head = self
18021 .selections
18022 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
18023 .head();
18024 let buffer = self.buffer.read(cx);
18025 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
18026 return Task::ready(Ok(Navigated::No));
18027 };
18028 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
18029 return Task::ready(Ok(Navigated::No));
18030 };
18031
18032 let nav_entry = self.navigation_entry(self.selections.newest_anchor().head(), cx);
18033
18034 cx.spawn_in(window, async move |editor, cx| {
18035 let Some(definitions) = definitions.await? else {
18036 return Ok(Navigated::No);
18037 };
18038 let navigated = editor
18039 .update_in(cx, |editor, window, cx| {
18040 editor.navigate_to_hover_links(
18041 Some(kind),
18042 definitions
18043 .into_iter()
18044 .filter(|location| {
18045 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
18046 })
18047 .map(HoverLink::Text)
18048 .collect::<Vec<_>>(),
18049 nav_entry,
18050 split,
18051 window,
18052 cx,
18053 )
18054 })?
18055 .await?;
18056 anyhow::Ok(navigated)
18057 })
18058 }
18059
18060 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
18061 let selection = self.selections.newest_anchor();
18062 let head = selection.head();
18063 let tail = selection.tail();
18064
18065 let Some((buffer, start_position)) =
18066 self.buffer.read(cx).text_anchor_for_position(head, cx)
18067 else {
18068 return;
18069 };
18070
18071 let end_position = if head != tail {
18072 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
18073 return;
18074 };
18075 Some(pos)
18076 } else {
18077 None
18078 };
18079
18080 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
18081 let url = if let Some(end_pos) = end_position {
18082 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
18083 } else {
18084 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
18085 };
18086
18087 if let Some(url) = url {
18088 cx.update(|window, cx| {
18089 if parse_zed_link(&url, cx).is_some() {
18090 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
18091 } else {
18092 cx.open_url(&url);
18093 }
18094 })?;
18095 }
18096
18097 anyhow::Ok(())
18098 });
18099
18100 url_finder.detach();
18101 }
18102
18103 pub fn open_selected_filename(
18104 &mut self,
18105 _: &OpenSelectedFilename,
18106 window: &mut Window,
18107 cx: &mut Context<Self>,
18108 ) {
18109 let Some(workspace) = self.workspace() else {
18110 return;
18111 };
18112
18113 let position = self.selections.newest_anchor().head();
18114
18115 let Some((buffer, buffer_position)) =
18116 self.buffer.read(cx).text_anchor_for_position(position, cx)
18117 else {
18118 return;
18119 };
18120
18121 let project = self.project.clone();
18122
18123 cx.spawn_in(window, async move |_, cx| {
18124 let result = find_file(&buffer, project, buffer_position, cx).await;
18125
18126 if let Some((_, path)) = result {
18127 workspace
18128 .update_in(cx, |workspace, window, cx| {
18129 workspace.open_resolved_path(path, window, cx)
18130 })?
18131 .await?;
18132 }
18133 anyhow::Ok(())
18134 })
18135 .detach();
18136 }
18137
18138 pub(crate) fn navigate_to_hover_links(
18139 &mut self,
18140 kind: Option<GotoDefinitionKind>,
18141 definitions: Vec<HoverLink>,
18142 origin: Option<NavigationEntry>,
18143 split: bool,
18144 window: &mut Window,
18145 cx: &mut Context<Editor>,
18146 ) -> Task<Result<Navigated>> {
18147 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
18148 let mut first_url_or_file = None;
18149 let definitions: Vec<_> = definitions
18150 .into_iter()
18151 .filter_map(|def| match def {
18152 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
18153 HoverLink::InlayHint(lsp_location, server_id) => {
18154 let computation =
18155 self.compute_target_location(lsp_location, server_id, window, cx);
18156 Some(cx.background_spawn(computation))
18157 }
18158 HoverLink::Url(url) => {
18159 first_url_or_file = Some(Either::Left(url));
18160 None
18161 }
18162 HoverLink::File(path) => {
18163 first_url_or_file = Some(Either::Right(path));
18164 None
18165 }
18166 })
18167 .collect();
18168
18169 let workspace = self.workspace();
18170
18171 let excerpt_context_lines = multi_buffer::excerpt_context_lines(cx);
18172 cx.spawn_in(window, async move |editor, cx| {
18173 let locations: Vec<Location> = future::join_all(definitions)
18174 .await
18175 .into_iter()
18176 .filter_map(|location| location.transpose())
18177 .collect::<Result<_>>()
18178 .context("location tasks")?;
18179 let mut locations = cx.update(|_, cx| {
18180 locations
18181 .into_iter()
18182 .map(|location| {
18183 let buffer = location.buffer.read(cx);
18184 (location.buffer, location.range.to_point(buffer))
18185 })
18186 .into_group_map()
18187 })?;
18188 let mut num_locations = 0;
18189 for ranges in locations.values_mut() {
18190 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18191 ranges.dedup();
18192 let fits_in_one_excerpt = ranges
18193 .iter()
18194 .tuple_windows()
18195 .all(|(a, b)| b.start.row - a.end.row <= 2 * excerpt_context_lines);
18196 num_locations += if fits_in_one_excerpt { 1 } else { ranges.len() };
18197 }
18198
18199 if num_locations > 1 {
18200 let tab_kind = match kind {
18201 Some(GotoDefinitionKind::Implementation) => "Implementations",
18202 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
18203 Some(GotoDefinitionKind::Declaration) => "Declarations",
18204 Some(GotoDefinitionKind::Type) => "Types",
18205 };
18206 let title = editor
18207 .update_in(cx, |_, _, cx| {
18208 let target = locations
18209 .iter()
18210 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18211 .map(|(buffer, location)| {
18212 buffer
18213 .read(cx)
18214 .text_for_range(location.clone())
18215 .collect::<String>()
18216 })
18217 .filter(|text| !text.contains('\n'))
18218 .unique()
18219 .take(3)
18220 .join(", ");
18221 if target.is_empty() {
18222 tab_kind.to_owned()
18223 } else {
18224 format!("{tab_kind} for {target}")
18225 }
18226 })
18227 .context("buffer title")?;
18228
18229 let Some(workspace) = workspace else {
18230 return Ok(Navigated::No);
18231 };
18232
18233 let opened = workspace
18234 .update_in(cx, |workspace, window, cx| {
18235 let allow_preview = PreviewTabsSettings::get_global(cx)
18236 .enable_preview_multibuffer_from_code_navigation;
18237 if let Some((target_editor, target_pane)) =
18238 Self::open_locations_in_multibuffer(
18239 workspace,
18240 locations,
18241 title,
18242 split,
18243 allow_preview,
18244 MultibufferSelectionMode::First,
18245 window,
18246 cx,
18247 )
18248 {
18249 // We create our own nav history instead of using
18250 // `target_editor.nav_history` because `nav_history`
18251 // seems to be populated asynchronously when an item
18252 // is added to a pane
18253 let mut nav_history = target_pane
18254 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18255 target_editor.update(cx, |editor, cx| {
18256 let nav_data = editor
18257 .navigation_data(editor.selections.newest_anchor().head(), cx);
18258 let target =
18259 Some(nav_history.navigation_entry(Some(
18260 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18261 )));
18262 nav_history.push_tag(origin, target);
18263 })
18264 }
18265 })
18266 .is_ok();
18267
18268 anyhow::Ok(Navigated::from_bool(opened))
18269 } else if num_locations == 0 {
18270 // If there is one url or file, open it directly
18271 match first_url_or_file {
18272 Some(Either::Left(url)) => {
18273 cx.update(|window, cx| {
18274 if parse_zed_link(&url, cx).is_some() {
18275 window
18276 .dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
18277 } else {
18278 cx.open_url(&url);
18279 }
18280 })?;
18281 Ok(Navigated::Yes)
18282 }
18283 Some(Either::Right(path)) => {
18284 // TODO(andrew): respect preview tab settings
18285 // `enable_keep_preview_on_code_navigation` and
18286 // `enable_preview_file_from_code_navigation`
18287 let Some(workspace) = workspace else {
18288 return Ok(Navigated::No);
18289 };
18290 workspace
18291 .update_in(cx, |workspace, window, cx| {
18292 workspace.open_resolved_path(path, window, cx)
18293 })?
18294 .await?;
18295 Ok(Navigated::Yes)
18296 }
18297 None => Ok(Navigated::No),
18298 }
18299 } else {
18300 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18301
18302 editor.update_in(cx, |editor, window, cx| {
18303 let target_ranges = target_ranges
18304 .into_iter()
18305 .map(|r| editor.range_for_match(&r))
18306 .map(collapse_multiline_range)
18307 .collect::<Vec<_>>();
18308 if !split
18309 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
18310 {
18311 let multibuffer = editor.buffer.read(cx);
18312 let target_ranges = target_ranges
18313 .into_iter()
18314 .filter_map(|r| {
18315 let start = multibuffer.buffer_point_to_anchor(
18316 &target_buffer,
18317 r.start,
18318 cx,
18319 )?;
18320 let end = multibuffer.buffer_point_to_anchor(
18321 &target_buffer,
18322 r.end,
18323 cx,
18324 )?;
18325 Some(start..end)
18326 })
18327 .collect::<Vec<_>>();
18328 if target_ranges.is_empty() {
18329 return Navigated::No;
18330 }
18331
18332 editor.change_selections(
18333 SelectionEffects::default().nav_history(true),
18334 window,
18335 cx,
18336 |s| s.select_anchor_ranges(target_ranges),
18337 );
18338
18339 let target =
18340 editor.navigation_entry(editor.selections.newest_anchor().head(), cx);
18341 if let Some(mut nav_history) = editor.nav_history.clone() {
18342 nav_history.push_tag(origin, target);
18343 }
18344 } else {
18345 let Some(workspace) = workspace else {
18346 return Navigated::No;
18347 };
18348 let pane = workspace.read(cx).active_pane().clone();
18349 window.defer(cx, move |window, cx| {
18350 let (target_editor, target_pane): (Entity<Self>, Entity<Pane>) =
18351 workspace.update(cx, |workspace, cx| {
18352 let pane = if split {
18353 workspace.adjacent_pane(window, cx)
18354 } else {
18355 workspace.active_pane().clone()
18356 };
18357
18358 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18359 let keep_old_preview = preview_tabs_settings
18360 .enable_keep_preview_on_code_navigation;
18361 let allow_new_preview = preview_tabs_settings
18362 .enable_preview_file_from_code_navigation;
18363
18364 let editor = workspace.open_project_item(
18365 pane.clone(),
18366 target_buffer.clone(),
18367 true,
18368 true,
18369 keep_old_preview,
18370 allow_new_preview,
18371 window,
18372 cx,
18373 );
18374 (editor, pane)
18375 });
18376 // We create our own nav history instead of using
18377 // `target_editor.nav_history` because `nav_history`
18378 // seems to be populated asynchronously when an item
18379 // is added to a pane
18380 let mut nav_history = target_pane
18381 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18382 target_editor.update(cx, |target_editor, cx| {
18383 // When selecting a definition in a different buffer, disable the nav history
18384 // to avoid creating a history entry at the previous cursor location.
18385 pane.update(cx, |pane, _| pane.disable_history());
18386
18387 let multibuffer = target_editor.buffer.read(cx);
18388 let Some(target_buffer) = multibuffer.as_singleton() else {
18389 return Navigated::No;
18390 };
18391 let target_ranges = target_ranges
18392 .into_iter()
18393 .filter_map(|r| {
18394 let start = multibuffer.buffer_point_to_anchor(
18395 &target_buffer,
18396 r.start,
18397 cx,
18398 )?;
18399 let end = multibuffer.buffer_point_to_anchor(
18400 &target_buffer,
18401 r.end,
18402 cx,
18403 )?;
18404 Some(start..end)
18405 })
18406 .collect::<Vec<_>>();
18407 if target_ranges.is_empty() {
18408 return Navigated::No;
18409 }
18410
18411 target_editor.change_selections(
18412 SelectionEffects::default().nav_history(true),
18413 window,
18414 cx,
18415 |s| s.select_anchor_ranges(target_ranges),
18416 );
18417
18418 let nav_data = target_editor.navigation_data(
18419 target_editor.selections.newest_anchor().head(),
18420 cx,
18421 );
18422 let target =
18423 Some(nav_history.navigation_entry(Some(
18424 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18425 )));
18426 nav_history.push_tag(origin, target);
18427 pane.update(cx, |pane, _| pane.enable_history());
18428 Navigated::Yes
18429 });
18430 });
18431 }
18432 Navigated::Yes
18433 })
18434 }
18435 })
18436 }
18437
18438 fn compute_target_location(
18439 &self,
18440 lsp_location: lsp::Location,
18441 server_id: LanguageServerId,
18442 window: &mut Window,
18443 cx: &mut Context<Self>,
18444 ) -> Task<anyhow::Result<Option<Location>>> {
18445 let Some(project) = self.project.clone() else {
18446 return Task::ready(Ok(None));
18447 };
18448
18449 cx.spawn_in(window, async move |editor, cx| {
18450 let location_task = editor.update(cx, |_, cx| {
18451 project.update(cx, |project, cx| {
18452 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
18453 })
18454 })?;
18455 let location = Some({
18456 let target_buffer_handle = location_task.await.context("open local buffer")?;
18457 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
18458 let target_start = target_buffer
18459 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
18460 let target_end = target_buffer
18461 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
18462 target_buffer.anchor_after(target_start)
18463 ..target_buffer.anchor_before(target_end)
18464 });
18465 Location {
18466 buffer: target_buffer_handle,
18467 range,
18468 }
18469 });
18470 Ok(location)
18471 })
18472 }
18473
18474 fn go_to_next_reference(
18475 &mut self,
18476 _: &GoToNextReference,
18477 window: &mut Window,
18478 cx: &mut Context<Self>,
18479 ) {
18480 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
18481 if let Some(task) = task {
18482 task.detach();
18483 };
18484 }
18485
18486 fn go_to_prev_reference(
18487 &mut self,
18488 _: &GoToPreviousReference,
18489 window: &mut Window,
18490 cx: &mut Context<Self>,
18491 ) {
18492 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
18493 if let Some(task) = task {
18494 task.detach();
18495 };
18496 }
18497
18498 fn go_to_symbol_by_offset(
18499 &mut self,
18500 window: &mut Window,
18501 cx: &mut Context<Self>,
18502 offset: i8,
18503 ) -> Task<Result<()>> {
18504 let editor_snapshot = self.snapshot(window, cx);
18505
18506 // We don't care about multi-buffer symbols
18507 let Some((excerpt_id, _, _)) = editor_snapshot.as_singleton() else {
18508 return Task::ready(Ok(()));
18509 };
18510
18511 let cursor_offset = self
18512 .selections
18513 .newest::<MultiBufferOffset>(&editor_snapshot.display_snapshot)
18514 .head();
18515
18516 cx.spawn_in(window, async move |editor, wcx| -> Result<()> {
18517 let Ok(Some(remote_id)) = editor.update(wcx, |ed, cx| {
18518 let buffer = ed.buffer.read(cx).as_singleton()?;
18519 Some(buffer.read(cx).remote_id())
18520 }) else {
18521 return Ok(());
18522 };
18523
18524 let task = editor.update(wcx, |ed, cx| ed.buffer_outline_items(remote_id, cx))?;
18525 let outline_items: Vec<OutlineItem<text::Anchor>> = task.await;
18526
18527 let multi_snapshot = editor_snapshot.buffer();
18528 let buffer_range = |range: &Range<_>| {
18529 Anchor::range_in_buffer(excerpt_id, range.clone()).to_offset(multi_snapshot)
18530 };
18531
18532 wcx.update_window(wcx.window_handle(), |_, window, acx| {
18533 let current_idx = outline_items
18534 .iter()
18535 .enumerate()
18536 .filter_map(|(idx, item)| {
18537 // Find the closest outline item by distance between outline text and cursor location
18538 let source_range = buffer_range(&item.source_range_for_text);
18539 let distance_to_closest_endpoint = cmp::min(
18540 (source_range.start.0 as isize - cursor_offset.0 as isize).abs(),
18541 (source_range.end.0 as isize - cursor_offset.0 as isize).abs(),
18542 );
18543
18544 let item_towards_offset =
18545 (source_range.start.0 as isize - cursor_offset.0 as isize).signum()
18546 == (offset as isize).signum();
18547
18548 let source_range_contains_cursor = source_range.contains(&cursor_offset);
18549
18550 // To pick the next outline to jump to, we should jump in the direction of the offset, and
18551 // we should not already be within the outline's source range. We then pick the closest outline
18552 // item.
18553 (item_towards_offset && !source_range_contains_cursor)
18554 .then_some((distance_to_closest_endpoint, idx))
18555 })
18556 .min()
18557 .map(|(_, idx)| idx);
18558
18559 let Some(idx) = current_idx else {
18560 return;
18561 };
18562
18563 let range = buffer_range(&outline_items[idx].source_range_for_text);
18564 let selection = [range.start..range.start];
18565
18566 let _ = editor
18567 .update(acx, |editor, ecx| {
18568 editor.change_selections(
18569 SelectionEffects::scroll(Autoscroll::newest()),
18570 window,
18571 ecx,
18572 |s| s.select_ranges(selection),
18573 );
18574 })
18575 .ok();
18576 })?;
18577
18578 Ok(())
18579 })
18580 }
18581
18582 fn go_to_next_symbol(
18583 &mut self,
18584 _: &GoToNextSymbol,
18585 window: &mut Window,
18586 cx: &mut Context<Self>,
18587 ) {
18588 self.go_to_symbol_by_offset(window, cx, 1).detach();
18589 }
18590
18591 fn go_to_previous_symbol(
18592 &mut self,
18593 _: &GoToPreviousSymbol,
18594 window: &mut Window,
18595 cx: &mut Context<Self>,
18596 ) {
18597 self.go_to_symbol_by_offset(window, cx, -1).detach();
18598 }
18599
18600 pub fn go_to_reference_before_or_after_position(
18601 &mut self,
18602 direction: Direction,
18603 count: usize,
18604 window: &mut Window,
18605 cx: &mut Context<Self>,
18606 ) -> Option<Task<Result<()>>> {
18607 let selection = self.selections.newest_anchor();
18608 let head = selection.head();
18609
18610 let multi_buffer = self.buffer.read(cx);
18611
18612 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
18613 let workspace = self.workspace()?;
18614 let project = workspace.read(cx).project().clone();
18615 let references =
18616 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
18617 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
18618 let Some(locations) = references.await? else {
18619 return Ok(());
18620 };
18621
18622 if locations.is_empty() {
18623 // totally normal - the cursor may be on something which is not
18624 // a symbol (e.g. a keyword)
18625 log::info!("no references found under cursor");
18626 return Ok(());
18627 }
18628
18629 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
18630
18631 let (locations, current_location_index) =
18632 multi_buffer.update(cx, |multi_buffer, cx| {
18633 let mut locations = locations
18634 .into_iter()
18635 .filter_map(|loc| {
18636 let start = multi_buffer.buffer_anchor_to_anchor(
18637 &loc.buffer,
18638 loc.range.start,
18639 cx,
18640 )?;
18641 let end = multi_buffer.buffer_anchor_to_anchor(
18642 &loc.buffer,
18643 loc.range.end,
18644 cx,
18645 )?;
18646 Some(start..end)
18647 })
18648 .collect::<Vec<_>>();
18649
18650 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18651 // There is an O(n) implementation, but given this list will be
18652 // small (usually <100 items), the extra O(log(n)) factor isn't
18653 // worth the (surprisingly large amount of) extra complexity.
18654 locations
18655 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
18656
18657 let head_offset = head.to_offset(&multi_buffer_snapshot);
18658
18659 let current_location_index = locations.iter().position(|loc| {
18660 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
18661 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
18662 });
18663
18664 (locations, current_location_index)
18665 });
18666
18667 let Some(current_location_index) = current_location_index else {
18668 // This indicates something has gone wrong, because we already
18669 // handle the "no references" case above
18670 log::error!(
18671 "failed to find current reference under cursor. Total references: {}",
18672 locations.len()
18673 );
18674 return Ok(());
18675 };
18676
18677 let destination_location_index = match direction {
18678 Direction::Next => (current_location_index + count) % locations.len(),
18679 Direction::Prev => {
18680 (current_location_index + locations.len() - count % locations.len())
18681 % locations.len()
18682 }
18683 };
18684
18685 // TODO(cameron): is this needed?
18686 // the thinking is to avoid "jumping to the current location" (avoid
18687 // polluting "jumplist" in vim terms)
18688 if current_location_index == destination_location_index {
18689 return Ok(());
18690 }
18691
18692 let Range { start, end } = locations[destination_location_index];
18693
18694 editor.update_in(cx, |editor, window, cx| {
18695 let effects = SelectionEffects::default();
18696
18697 editor.unfold_ranges(&[start..end], false, false, cx);
18698 editor.change_selections(effects, window, cx, |s| {
18699 s.select_ranges([start..start]);
18700 });
18701 })?;
18702
18703 Ok(())
18704 }))
18705 }
18706
18707 pub fn find_all_references(
18708 &mut self,
18709 action: &FindAllReferences,
18710 window: &mut Window,
18711 cx: &mut Context<Self>,
18712 ) -> Option<Task<Result<Navigated>>> {
18713 let always_open_multibuffer = action.always_open_multibuffer;
18714 let selection = self.selections.newest_anchor();
18715 let multi_buffer = self.buffer.read(cx);
18716 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18717 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
18718 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
18719 let head = selection_offset.head();
18720
18721 let head_anchor = multi_buffer_snapshot.anchor_at(
18722 head,
18723 if head < selection_offset.tail() {
18724 Bias::Right
18725 } else {
18726 Bias::Left
18727 },
18728 );
18729
18730 match self
18731 .find_all_references_task_sources
18732 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18733 {
18734 Ok(_) => {
18735 log::info!(
18736 "Ignoring repeated FindAllReferences invocation with the position of already running task"
18737 );
18738 return None;
18739 }
18740 Err(i) => {
18741 self.find_all_references_task_sources.insert(i, head_anchor);
18742 }
18743 }
18744
18745 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
18746 let workspace = self.workspace()?;
18747 let project = workspace.read(cx).project().clone();
18748 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
18749 Some(cx.spawn_in(window, async move |editor, cx| {
18750 let _cleanup = cx.on_drop(&editor, move |editor, _| {
18751 if let Ok(i) = editor
18752 .find_all_references_task_sources
18753 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18754 {
18755 editor.find_all_references_task_sources.remove(i);
18756 }
18757 });
18758
18759 let Some(locations) = references.await? else {
18760 return anyhow::Ok(Navigated::No);
18761 };
18762 let mut locations = cx.update(|_, cx| {
18763 locations
18764 .into_iter()
18765 .map(|location| {
18766 let buffer = location.buffer.read(cx);
18767 (location.buffer, location.range.to_point(buffer))
18768 })
18769 // if special-casing the single-match case, remove ranges
18770 // that intersect current selection
18771 .filter(|(location_buffer, location)| {
18772 if always_open_multibuffer || &buffer != location_buffer {
18773 return true;
18774 }
18775
18776 !location.contains_inclusive(&selection_point.range())
18777 })
18778 .into_group_map()
18779 })?;
18780 if locations.is_empty() {
18781 return anyhow::Ok(Navigated::No);
18782 }
18783 for ranges in locations.values_mut() {
18784 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18785 ranges.dedup();
18786 }
18787 let mut num_locations = 0;
18788 for ranges in locations.values_mut() {
18789 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18790 ranges.dedup();
18791 num_locations += ranges.len();
18792 }
18793
18794 if num_locations == 1 && !always_open_multibuffer {
18795 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18796 let target_range = target_ranges.first().unwrap().clone();
18797
18798 return editor.update_in(cx, |editor, window, cx| {
18799 let range = target_range.to_point(target_buffer.read(cx));
18800 let range = editor.range_for_match(&range);
18801 let range = range.start..range.start;
18802
18803 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
18804 editor.go_to_singleton_buffer_range(range, window, cx);
18805 } else {
18806 let pane = workspace.read(cx).active_pane().clone();
18807 window.defer(cx, move |window, cx| {
18808 let target_editor: Entity<Self> =
18809 workspace.update(cx, |workspace, cx| {
18810 let pane = workspace.active_pane().clone();
18811
18812 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18813 let keep_old_preview = preview_tabs_settings
18814 .enable_keep_preview_on_code_navigation;
18815 let allow_new_preview = preview_tabs_settings
18816 .enable_preview_file_from_code_navigation;
18817
18818 workspace.open_project_item(
18819 pane,
18820 target_buffer.clone(),
18821 true,
18822 true,
18823 keep_old_preview,
18824 allow_new_preview,
18825 window,
18826 cx,
18827 )
18828 });
18829 target_editor.update(cx, |target_editor, cx| {
18830 // When selecting a definition in a different buffer, disable the nav history
18831 // to avoid creating a history entry at the previous cursor location.
18832 pane.update(cx, |pane, _| pane.disable_history());
18833 target_editor.go_to_singleton_buffer_range(range, window, cx);
18834 pane.update(cx, |pane, _| pane.enable_history());
18835 });
18836 });
18837 }
18838 Navigated::No
18839 });
18840 }
18841
18842 workspace.update_in(cx, |workspace, window, cx| {
18843 let target = locations
18844 .iter()
18845 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18846 .map(|(buffer, location)| {
18847 buffer
18848 .read(cx)
18849 .text_for_range(location.clone())
18850 .collect::<String>()
18851 })
18852 .filter(|text| !text.contains('\n'))
18853 .unique()
18854 .take(3)
18855 .join(", ");
18856 let title = if target.is_empty() {
18857 "References".to_owned()
18858 } else {
18859 format!("References to {target}")
18860 };
18861 let allow_preview = PreviewTabsSettings::get_global(cx)
18862 .enable_preview_multibuffer_from_code_navigation;
18863 Self::open_locations_in_multibuffer(
18864 workspace,
18865 locations,
18866 title,
18867 false,
18868 allow_preview,
18869 MultibufferSelectionMode::First,
18870 window,
18871 cx,
18872 );
18873 Navigated::Yes
18874 })
18875 }))
18876 }
18877
18878 /// Opens a multibuffer with the given project locations in it.
18879 pub fn open_locations_in_multibuffer(
18880 workspace: &mut Workspace,
18881 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
18882 title: String,
18883 split: bool,
18884 allow_preview: bool,
18885 multibuffer_selection_mode: MultibufferSelectionMode,
18886 window: &mut Window,
18887 cx: &mut Context<Workspace>,
18888 ) -> Option<(Entity<Editor>, Entity<Pane>)> {
18889 if locations.is_empty() {
18890 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
18891 return None;
18892 }
18893
18894 let capability = workspace.project().read(cx).capability();
18895 let mut ranges = <Vec<Range<Anchor>>>::new();
18896
18897 // a key to find existing multibuffer editors with the same set of locations
18898 // to prevent us from opening more and more multibuffer tabs for searches and the like
18899 let mut key = (title.clone(), vec![]);
18900 let excerpt_buffer = cx.new(|cx| {
18901 let key = &mut key.1;
18902 let mut multibuffer = MultiBuffer::new(capability);
18903 for (buffer, mut ranges_for_buffer) in locations {
18904 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
18905 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
18906 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
18907 PathKey::for_buffer(&buffer, cx),
18908 buffer.clone(),
18909 ranges_for_buffer,
18910 multibuffer_context_lines(cx),
18911 cx,
18912 );
18913 ranges.extend(new_ranges)
18914 }
18915
18916 multibuffer.with_title(title)
18917 });
18918 let existing = workspace.active_pane().update(cx, |pane, cx| {
18919 pane.items()
18920 .filter_map(|item| item.downcast::<Editor>())
18921 .find(|editor| {
18922 editor
18923 .read(cx)
18924 .lookup_key
18925 .as_ref()
18926 .and_then(|it| {
18927 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
18928 })
18929 .is_some_and(|it| *it == key)
18930 })
18931 });
18932 let was_existing = existing.is_some();
18933 let editor = existing.unwrap_or_else(|| {
18934 cx.new(|cx| {
18935 let mut editor = Editor::for_multibuffer(
18936 excerpt_buffer,
18937 Some(workspace.project().clone()),
18938 window,
18939 cx,
18940 );
18941 editor.lookup_key = Some(Box::new(key));
18942 editor
18943 })
18944 });
18945 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
18946 MultibufferSelectionMode::First => {
18947 if let Some(first_range) = ranges.first() {
18948 editor.change_selections(
18949 SelectionEffects::no_scroll(),
18950 window,
18951 cx,
18952 |selections| {
18953 selections.clear_disjoint();
18954 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
18955 },
18956 );
18957 }
18958 editor.highlight_background(
18959 HighlightKey::Editor,
18960 &ranges,
18961 |_, theme| theme.colors().editor_highlighted_line_background,
18962 cx,
18963 );
18964 }
18965 MultibufferSelectionMode::All => {
18966 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
18967 selections.clear_disjoint();
18968 selections.select_anchor_ranges(ranges);
18969 });
18970 }
18971 });
18972
18973 let item = Box::new(editor.clone());
18974
18975 let pane = if split {
18976 workspace.adjacent_pane(window, cx)
18977 } else {
18978 workspace.active_pane().clone()
18979 };
18980 let activate_pane = split;
18981
18982 let mut destination_index = None;
18983 pane.update(cx, |pane, cx| {
18984 if allow_preview && !was_existing {
18985 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
18986 }
18987 if was_existing && !allow_preview {
18988 pane.unpreview_item_if_preview(item.item_id());
18989 }
18990 pane.add_item(item, activate_pane, true, destination_index, window, cx);
18991 });
18992
18993 Some((editor, pane))
18994 }
18995
18996 pub fn rename(
18997 &mut self,
18998 _: &Rename,
18999 window: &mut Window,
19000 cx: &mut Context<Self>,
19001 ) -> Option<Task<Result<()>>> {
19002 use language::ToOffset as _;
19003
19004 let provider = self.semantics_provider.clone()?;
19005 let selection = self.selections.newest_anchor().clone();
19006 let (cursor_buffer, cursor_buffer_position) = self
19007 .buffer
19008 .read(cx)
19009 .text_anchor_for_position(selection.head(), cx)?;
19010 let (tail_buffer, cursor_buffer_position_end) = self
19011 .buffer
19012 .read(cx)
19013 .text_anchor_for_position(selection.tail(), cx)?;
19014 if tail_buffer != cursor_buffer {
19015 return None;
19016 }
19017
19018 let snapshot = cursor_buffer.read(cx).snapshot();
19019 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
19020 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
19021 let prepare_rename = provider
19022 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
19023 .unwrap_or_else(|| Task::ready(Ok(None)));
19024 drop(snapshot);
19025
19026 Some(cx.spawn_in(window, async move |this, cx| {
19027 let rename_range = if let Some(range) = prepare_rename.await? {
19028 Some(range)
19029 } else {
19030 this.update(cx, |this, cx| {
19031 let buffer = this.buffer.read(cx).snapshot(cx);
19032 let mut buffer_highlights = this
19033 .document_highlights_for_position(selection.head(), &buffer)
19034 .filter(|highlight| {
19035 highlight.start.excerpt_id == selection.head().excerpt_id
19036 && highlight.end.excerpt_id == selection.head().excerpt_id
19037 });
19038 buffer_highlights
19039 .next()
19040 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
19041 })?
19042 };
19043 if let Some(rename_range) = rename_range {
19044 this.update_in(cx, |this, window, cx| {
19045 let snapshot = cursor_buffer.read(cx).snapshot();
19046 let rename_buffer_range = rename_range.to_offset(&snapshot);
19047 let cursor_offset_in_rename_range =
19048 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
19049 let cursor_offset_in_rename_range_end =
19050 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
19051
19052 this.take_rename(false, window, cx);
19053 let buffer = this.buffer.read(cx).read(cx);
19054 let cursor_offset = selection.head().to_offset(&buffer);
19055 let rename_start =
19056 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
19057 let rename_end = rename_start + rename_buffer_range.len();
19058 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
19059 let mut old_highlight_id = None;
19060 let old_name: Arc<str> = buffer
19061 .chunks(rename_start..rename_end, true)
19062 .map(|chunk| {
19063 if old_highlight_id.is_none() {
19064 old_highlight_id = chunk.syntax_highlight_id;
19065 }
19066 chunk.text
19067 })
19068 .collect::<String>()
19069 .into();
19070
19071 drop(buffer);
19072
19073 // Position the selection in the rename editor so that it matches the current selection.
19074 this.show_local_selections = false;
19075 let rename_editor = cx.new(|cx| {
19076 let mut editor = Editor::single_line(window, cx);
19077 editor.buffer.update(cx, |buffer, cx| {
19078 buffer.edit(
19079 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
19080 None,
19081 cx,
19082 )
19083 });
19084 let cursor_offset_in_rename_range =
19085 MultiBufferOffset(cursor_offset_in_rename_range);
19086 let cursor_offset_in_rename_range_end =
19087 MultiBufferOffset(cursor_offset_in_rename_range_end);
19088 let rename_selection_range = match cursor_offset_in_rename_range
19089 .cmp(&cursor_offset_in_rename_range_end)
19090 {
19091 Ordering::Equal => {
19092 editor.select_all(&SelectAll, window, cx);
19093 return editor;
19094 }
19095 Ordering::Less => {
19096 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
19097 }
19098 Ordering::Greater => {
19099 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
19100 }
19101 };
19102 if rename_selection_range.end.0 > old_name.len() {
19103 editor.select_all(&SelectAll, window, cx);
19104 } else {
19105 editor.change_selections(Default::default(), window, cx, |s| {
19106 s.select_ranges([rename_selection_range]);
19107 });
19108 }
19109 editor
19110 });
19111 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
19112 if e == &EditorEvent::Focused {
19113 cx.emit(EditorEvent::FocusedIn)
19114 }
19115 })
19116 .detach();
19117
19118 let write_highlights =
19119 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
19120 let read_highlights =
19121 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
19122 let ranges = write_highlights
19123 .iter()
19124 .flat_map(|(_, ranges)| ranges.iter())
19125 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
19126 .cloned()
19127 .collect();
19128
19129 this.highlight_text(
19130 HighlightKey::Rename,
19131 ranges,
19132 HighlightStyle {
19133 fade_out: Some(0.6),
19134 ..Default::default()
19135 },
19136 cx,
19137 );
19138 let rename_focus_handle = rename_editor.focus_handle(cx);
19139 window.focus(&rename_focus_handle, cx);
19140 let block_id = this.insert_blocks(
19141 [BlockProperties {
19142 style: BlockStyle::Flex,
19143 placement: BlockPlacement::Below(range.start),
19144 height: Some(1),
19145 render: Arc::new({
19146 let rename_editor = rename_editor.clone();
19147 move |cx: &mut BlockContext| {
19148 let mut text_style = cx.editor_style.text.clone();
19149 if let Some(highlight_style) = old_highlight_id
19150 .and_then(|h| h.style(&cx.editor_style.syntax))
19151 {
19152 text_style = text_style.highlight(highlight_style);
19153 }
19154 div()
19155 .block_mouse_except_scroll()
19156 .pl(cx.anchor_x)
19157 .child(EditorElement::new(
19158 &rename_editor,
19159 EditorStyle {
19160 background: cx.theme().system().transparent,
19161 local_player: cx.editor_style.local_player,
19162 text: text_style,
19163 scrollbar_width: cx.editor_style.scrollbar_width,
19164 syntax: cx.editor_style.syntax.clone(),
19165 status: cx.editor_style.status.clone(),
19166 inlay_hints_style: HighlightStyle {
19167 font_weight: Some(FontWeight::BOLD),
19168 ..make_inlay_hints_style(cx.app)
19169 },
19170 edit_prediction_styles: make_suggestion_styles(
19171 cx.app,
19172 ),
19173 ..EditorStyle::default()
19174 },
19175 ))
19176 .into_any_element()
19177 }
19178 }),
19179 priority: 0,
19180 }],
19181 Some(Autoscroll::fit()),
19182 cx,
19183 )[0];
19184 this.pending_rename = Some(RenameState {
19185 range,
19186 old_name,
19187 editor: rename_editor,
19188 block_id,
19189 });
19190 })?;
19191 }
19192
19193 Ok(())
19194 }))
19195 }
19196
19197 pub fn confirm_rename(
19198 &mut self,
19199 _: &ConfirmRename,
19200 window: &mut Window,
19201 cx: &mut Context<Self>,
19202 ) -> Option<Task<Result<()>>> {
19203 let rename = self.take_rename(false, window, cx)?;
19204 let workspace = self.workspace()?.downgrade();
19205 let (buffer, start) = self
19206 .buffer
19207 .read(cx)
19208 .text_anchor_for_position(rename.range.start, cx)?;
19209 let (end_buffer, _) = self
19210 .buffer
19211 .read(cx)
19212 .text_anchor_for_position(rename.range.end, cx)?;
19213 if buffer != end_buffer {
19214 return None;
19215 }
19216
19217 let old_name = rename.old_name;
19218 let new_name = rename.editor.read(cx).text(cx);
19219
19220 let rename = self.semantics_provider.as_ref()?.perform_rename(
19221 &buffer,
19222 start,
19223 new_name.clone(),
19224 cx,
19225 )?;
19226
19227 Some(cx.spawn_in(window, async move |editor, cx| {
19228 let project_transaction = rename.await?;
19229 Self::open_project_transaction(
19230 &editor,
19231 workspace,
19232 project_transaction,
19233 format!("Rename: {} → {}", old_name, new_name),
19234 cx,
19235 )
19236 .await?;
19237
19238 editor.update(cx, |editor, cx| {
19239 editor.refresh_document_highlights(cx);
19240 })?;
19241 Ok(())
19242 }))
19243 }
19244
19245 fn take_rename(
19246 &mut self,
19247 moving_cursor: bool,
19248 window: &mut Window,
19249 cx: &mut Context<Self>,
19250 ) -> Option<RenameState> {
19251 let rename = self.pending_rename.take()?;
19252 if rename.editor.focus_handle(cx).is_focused(window) {
19253 window.focus(&self.focus_handle, cx);
19254 }
19255
19256 self.remove_blocks(
19257 [rename.block_id].into_iter().collect(),
19258 Some(Autoscroll::fit()),
19259 cx,
19260 );
19261 self.clear_highlights(HighlightKey::Rename, cx);
19262 self.show_local_selections = true;
19263
19264 if moving_cursor {
19265 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
19266 editor
19267 .selections
19268 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
19269 .head()
19270 });
19271
19272 // Update the selection to match the position of the selection inside
19273 // the rename editor.
19274 let snapshot = self.buffer.read(cx).read(cx);
19275 let rename_range = rename.range.to_offset(&snapshot);
19276 let cursor_in_editor = snapshot
19277 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
19278 .min(rename_range.end);
19279 drop(snapshot);
19280
19281 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19282 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
19283 });
19284 } else {
19285 self.refresh_document_highlights(cx);
19286 }
19287
19288 Some(rename)
19289 }
19290
19291 pub fn pending_rename(&self) -> Option<&RenameState> {
19292 self.pending_rename.as_ref()
19293 }
19294
19295 fn format(
19296 &mut self,
19297 _: &Format,
19298 window: &mut Window,
19299 cx: &mut Context<Self>,
19300 ) -> Option<Task<Result<()>>> {
19301 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19302
19303 let project = match &self.project {
19304 Some(project) => project.clone(),
19305 None => return None,
19306 };
19307
19308 Some(self.perform_format(
19309 project,
19310 FormatTrigger::Manual,
19311 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
19312 window,
19313 cx,
19314 ))
19315 }
19316
19317 fn format_selections(
19318 &mut self,
19319 _: &FormatSelections,
19320 window: &mut Window,
19321 cx: &mut Context<Self>,
19322 ) -> Option<Task<Result<()>>> {
19323 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19324
19325 let project = match &self.project {
19326 Some(project) => project.clone(),
19327 None => return None,
19328 };
19329
19330 let ranges = self
19331 .selections
19332 .all_adjusted(&self.display_snapshot(cx))
19333 .into_iter()
19334 .map(|selection| selection.range())
19335 .collect_vec();
19336
19337 Some(self.perform_format(
19338 project,
19339 FormatTrigger::Manual,
19340 FormatTarget::Ranges(ranges),
19341 window,
19342 cx,
19343 ))
19344 }
19345
19346 fn perform_format(
19347 &mut self,
19348 project: Entity<Project>,
19349 trigger: FormatTrigger,
19350 target: FormatTarget,
19351 window: &mut Window,
19352 cx: &mut Context<Self>,
19353 ) -> Task<Result<()>> {
19354 let buffer = self.buffer.clone();
19355 let (buffers, target) = match target {
19356 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
19357 FormatTarget::Ranges(selection_ranges) => {
19358 let multi_buffer = buffer.read(cx);
19359 let snapshot = multi_buffer.read(cx);
19360 let mut buffers = HashSet::default();
19361 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
19362 BTreeMap::new();
19363 for selection_range in selection_ranges {
19364 for (buffer, buffer_range, _) in
19365 snapshot.range_to_buffer_ranges(selection_range.start..=selection_range.end)
19366 {
19367 let buffer_id = buffer.remote_id();
19368 let start = buffer.anchor_before(buffer_range.start);
19369 let end = buffer.anchor_after(buffer_range.end);
19370 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
19371 buffer_id_to_ranges
19372 .entry(buffer_id)
19373 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
19374 .or_insert_with(|| vec![start..end]);
19375 }
19376 }
19377 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
19378 }
19379 };
19380
19381 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
19382 let selections_prev = transaction_id_prev
19383 .and_then(|transaction_id_prev| {
19384 // default to selections as they were after the last edit, if we have them,
19385 // instead of how they are now.
19386 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
19387 // will take you back to where you made the last edit, instead of staying where you scrolled
19388 self.selection_history
19389 .transaction(transaction_id_prev)
19390 .map(|t| t.0.clone())
19391 })
19392 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
19393
19394 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
19395 let format = project.update(cx, |project, cx| {
19396 project.format(buffers, target, true, trigger, cx)
19397 });
19398
19399 cx.spawn_in(window, async move |editor, cx| {
19400 let transaction = futures::select_biased! {
19401 transaction = format.log_err().fuse() => transaction,
19402 () = timeout => {
19403 log::warn!("timed out waiting for formatting");
19404 None
19405 }
19406 };
19407
19408 buffer.update(cx, |buffer, cx| {
19409 if let Some(transaction) = transaction
19410 && !buffer.is_singleton()
19411 {
19412 buffer.push_transaction(&transaction.0, cx);
19413 }
19414 cx.notify();
19415 });
19416
19417 if let Some(transaction_id_now) =
19418 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))
19419 {
19420 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
19421 if has_new_transaction {
19422 editor
19423 .update(cx, |editor, _| {
19424 editor
19425 .selection_history
19426 .insert_transaction(transaction_id_now, selections_prev);
19427 })
19428 .ok();
19429 }
19430 }
19431
19432 Ok(())
19433 })
19434 }
19435
19436 fn organize_imports(
19437 &mut self,
19438 _: &OrganizeImports,
19439 window: &mut Window,
19440 cx: &mut Context<Self>,
19441 ) -> Option<Task<Result<()>>> {
19442 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19443 let project = match &self.project {
19444 Some(project) => project.clone(),
19445 None => return None,
19446 };
19447 Some(self.perform_code_action_kind(
19448 project,
19449 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
19450 window,
19451 cx,
19452 ))
19453 }
19454
19455 fn perform_code_action_kind(
19456 &mut self,
19457 project: Entity<Project>,
19458 kind: CodeActionKind,
19459 window: &mut Window,
19460 cx: &mut Context<Self>,
19461 ) -> Task<Result<()>> {
19462 let buffer = self.buffer.clone();
19463 let buffers = buffer.read(cx).all_buffers();
19464 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
19465 let apply_action = project.update(cx, |project, cx| {
19466 project.apply_code_action_kind(buffers, kind, true, cx)
19467 });
19468 cx.spawn_in(window, async move |_, cx| {
19469 let transaction = futures::select_biased! {
19470 () = timeout => {
19471 log::warn!("timed out waiting for executing code action");
19472 None
19473 }
19474 transaction = apply_action.log_err().fuse() => transaction,
19475 };
19476 buffer.update(cx, |buffer, cx| {
19477 // check if we need this
19478 if let Some(transaction) = transaction
19479 && !buffer.is_singleton()
19480 {
19481 buffer.push_transaction(&transaction.0, cx);
19482 }
19483 cx.notify();
19484 });
19485 Ok(())
19486 })
19487 }
19488
19489 pub fn restart_language_server(
19490 &mut self,
19491 _: &RestartLanguageServer,
19492 _: &mut Window,
19493 cx: &mut Context<Self>,
19494 ) {
19495 if let Some(project) = self.project.clone() {
19496 self.buffer.update(cx, |multi_buffer, cx| {
19497 project.update(cx, |project, cx| {
19498 project.restart_language_servers_for_buffers(
19499 multi_buffer.all_buffers().into_iter().collect(),
19500 HashSet::default(),
19501 cx,
19502 );
19503 });
19504 })
19505 }
19506 }
19507
19508 pub fn stop_language_server(
19509 &mut self,
19510 _: &StopLanguageServer,
19511 _: &mut Window,
19512 cx: &mut Context<Self>,
19513 ) {
19514 if let Some(project) = self.project.clone() {
19515 self.buffer.update(cx, |multi_buffer, cx| {
19516 project.update(cx, |project, cx| {
19517 project.stop_language_servers_for_buffers(
19518 multi_buffer.all_buffers().into_iter().collect(),
19519 HashSet::default(),
19520 cx,
19521 );
19522 });
19523 });
19524 }
19525 }
19526
19527 fn cancel_language_server_work(
19528 workspace: &mut Workspace,
19529 _: &actions::CancelLanguageServerWork,
19530 _: &mut Window,
19531 cx: &mut Context<Workspace>,
19532 ) {
19533 let project = workspace.project();
19534 let buffers = workspace
19535 .active_item(cx)
19536 .and_then(|item| item.act_as::<Editor>(cx))
19537 .map_or(HashSet::default(), |editor| {
19538 editor.read(cx).buffer.read(cx).all_buffers()
19539 });
19540 project.update(cx, |project, cx| {
19541 project.cancel_language_server_work_for_buffers(buffers, cx);
19542 });
19543 }
19544
19545 fn show_character_palette(
19546 &mut self,
19547 _: &ShowCharacterPalette,
19548 window: &mut Window,
19549 _: &mut Context<Self>,
19550 ) {
19551 window.show_character_palette();
19552 }
19553
19554 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
19555 if !self.diagnostics_enabled() {
19556 return;
19557 }
19558
19559 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
19560 let buffer = self.buffer.read(cx).snapshot(cx);
19561 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
19562 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
19563 let is_valid = buffer
19564 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
19565 .any(|entry| {
19566 entry.diagnostic.is_primary
19567 && !entry.range.is_empty()
19568 && entry.range.start == primary_range_start
19569 && entry.diagnostic.message == active_diagnostics.active_message
19570 });
19571
19572 if !is_valid {
19573 self.dismiss_diagnostics(cx);
19574 }
19575 }
19576 }
19577
19578 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
19579 match &self.active_diagnostics {
19580 ActiveDiagnostic::Group(group) => Some(group),
19581 _ => None,
19582 }
19583 }
19584
19585 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
19586 if !self.diagnostics_enabled() {
19587 return;
19588 }
19589 self.dismiss_diagnostics(cx);
19590 self.active_diagnostics = ActiveDiagnostic::All;
19591 }
19592
19593 fn activate_diagnostics(
19594 &mut self,
19595 buffer_id: BufferId,
19596 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
19597 window: &mut Window,
19598 cx: &mut Context<Self>,
19599 ) {
19600 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19601 return;
19602 }
19603 self.dismiss_diagnostics(cx);
19604 let snapshot = self.snapshot(window, cx);
19605 let buffer = self.buffer.read(cx).snapshot(cx);
19606 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
19607 return;
19608 };
19609
19610 let diagnostic_group = buffer
19611 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
19612 .collect::<Vec<_>>();
19613
19614 let language_registry = self
19615 .project()
19616 .map(|project| project.read(cx).languages().clone());
19617
19618 let blocks = renderer.render_group(
19619 diagnostic_group,
19620 buffer_id,
19621 snapshot,
19622 cx.weak_entity(),
19623 language_registry,
19624 cx,
19625 );
19626
19627 let blocks = self.display_map.update(cx, |display_map, cx| {
19628 display_map.insert_blocks(blocks, cx).into_iter().collect()
19629 });
19630 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
19631 active_range: buffer.anchor_before(diagnostic.range.start)
19632 ..buffer.anchor_after(diagnostic.range.end),
19633 active_message: diagnostic.diagnostic.message.clone(),
19634 group_id: diagnostic.diagnostic.group_id,
19635 blocks,
19636 });
19637 cx.notify();
19638 }
19639
19640 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
19641 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19642 return;
19643 };
19644
19645 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
19646 if let ActiveDiagnostic::Group(group) = prev {
19647 self.display_map.update(cx, |display_map, cx| {
19648 display_map.remove_blocks(group.blocks, cx);
19649 });
19650 cx.notify();
19651 }
19652 }
19653
19654 /// Disable inline diagnostics rendering for this editor.
19655 pub fn disable_inline_diagnostics(&mut self) {
19656 self.inline_diagnostics_enabled = false;
19657 self.inline_diagnostics_update = Task::ready(());
19658 self.inline_diagnostics.clear();
19659 }
19660
19661 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
19662 self.diagnostics_enabled = false;
19663 self.dismiss_diagnostics(cx);
19664 self.inline_diagnostics_update = Task::ready(());
19665 self.inline_diagnostics.clear();
19666 }
19667
19668 pub fn disable_word_completions(&mut self) {
19669 self.word_completions_enabled = false;
19670 }
19671
19672 pub fn diagnostics_enabled(&self) -> bool {
19673 self.diagnostics_enabled && self.lsp_data_enabled()
19674 }
19675
19676 pub fn inline_diagnostics_enabled(&self) -> bool {
19677 self.inline_diagnostics_enabled && self.diagnostics_enabled()
19678 }
19679
19680 pub fn show_inline_diagnostics(&self) -> bool {
19681 self.show_inline_diagnostics
19682 }
19683
19684 pub fn toggle_inline_diagnostics(
19685 &mut self,
19686 _: &ToggleInlineDiagnostics,
19687 window: &mut Window,
19688 cx: &mut Context<Editor>,
19689 ) {
19690 self.show_inline_diagnostics = !self.show_inline_diagnostics;
19691 self.refresh_inline_diagnostics(false, window, cx);
19692 }
19693
19694 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
19695 self.diagnostics_max_severity = severity;
19696 self.display_map.update(cx, |display_map, _| {
19697 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
19698 });
19699 }
19700
19701 pub fn toggle_diagnostics(
19702 &mut self,
19703 _: &ToggleDiagnostics,
19704 window: &mut Window,
19705 cx: &mut Context<Editor>,
19706 ) {
19707 if !self.diagnostics_enabled() {
19708 return;
19709 }
19710
19711 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19712 EditorSettings::get_global(cx)
19713 .diagnostics_max_severity
19714 .filter(|severity| severity != &DiagnosticSeverity::Off)
19715 .unwrap_or(DiagnosticSeverity::Hint)
19716 } else {
19717 DiagnosticSeverity::Off
19718 };
19719 self.set_max_diagnostics_severity(new_severity, cx);
19720 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19721 self.active_diagnostics = ActiveDiagnostic::None;
19722 self.inline_diagnostics_update = Task::ready(());
19723 self.inline_diagnostics.clear();
19724 } else {
19725 self.refresh_inline_diagnostics(false, window, cx);
19726 }
19727
19728 cx.notify();
19729 }
19730
19731 pub fn toggle_minimap(
19732 &mut self,
19733 _: &ToggleMinimap,
19734 window: &mut Window,
19735 cx: &mut Context<Editor>,
19736 ) {
19737 if self.supports_minimap(cx) {
19738 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
19739 }
19740 }
19741
19742 fn refresh_inline_diagnostics(
19743 &mut self,
19744 debounce: bool,
19745 window: &mut Window,
19746 cx: &mut Context<Self>,
19747 ) {
19748 let max_severity = ProjectSettings::get_global(cx)
19749 .diagnostics
19750 .inline
19751 .max_severity
19752 .unwrap_or(self.diagnostics_max_severity);
19753
19754 if !self.inline_diagnostics_enabled()
19755 || !self.diagnostics_enabled()
19756 || !self.show_inline_diagnostics
19757 || max_severity == DiagnosticSeverity::Off
19758 {
19759 self.inline_diagnostics_update = Task::ready(());
19760 self.inline_diagnostics.clear();
19761 return;
19762 }
19763
19764 let debounce_ms = ProjectSettings::get_global(cx)
19765 .diagnostics
19766 .inline
19767 .update_debounce_ms;
19768 let debounce = if debounce && debounce_ms > 0 {
19769 Some(Duration::from_millis(debounce_ms))
19770 } else {
19771 None
19772 };
19773 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
19774 if let Some(debounce) = debounce {
19775 cx.background_executor().timer(debounce).await;
19776 }
19777 let Some(snapshot) = editor.upgrade().map(|editor| {
19778 editor.update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
19779 }) else {
19780 return;
19781 };
19782
19783 let new_inline_diagnostics = cx
19784 .background_spawn(async move {
19785 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
19786 for diagnostic_entry in
19787 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
19788 {
19789 let message = diagnostic_entry
19790 .diagnostic
19791 .message
19792 .split_once('\n')
19793 .map(|(line, _)| line)
19794 .map(SharedString::new)
19795 .unwrap_or_else(|| {
19796 SharedString::new(&*diagnostic_entry.diagnostic.message)
19797 });
19798 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
19799 let (Ok(i) | Err(i)) = inline_diagnostics
19800 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
19801 inline_diagnostics.insert(
19802 i,
19803 (
19804 start_anchor,
19805 InlineDiagnostic {
19806 message,
19807 group_id: diagnostic_entry.diagnostic.group_id,
19808 start: diagnostic_entry.range.start.to_point(&snapshot),
19809 is_primary: diagnostic_entry.diagnostic.is_primary,
19810 severity: diagnostic_entry.diagnostic.severity,
19811 },
19812 ),
19813 );
19814 }
19815 inline_diagnostics
19816 })
19817 .await;
19818
19819 editor
19820 .update(cx, |editor, cx| {
19821 editor.inline_diagnostics = new_inline_diagnostics;
19822 cx.notify();
19823 })
19824 .ok();
19825 });
19826 }
19827
19828 fn pull_diagnostics(
19829 &mut self,
19830 buffer_id: BufferId,
19831 _window: &Window,
19832 cx: &mut Context<Self>,
19833 ) -> Option<()> {
19834 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
19835 // skip any LSP updates for it.
19836
19837 if self.active_diagnostics == ActiveDiagnostic::All || !self.diagnostics_enabled() {
19838 return None;
19839 }
19840 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
19841 .diagnostics
19842 .lsp_pull_diagnostics;
19843 if !pull_diagnostics_settings.enabled {
19844 return None;
19845 }
19846 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
19847 let project = self.project()?.downgrade();
19848 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
19849
19850 self.pull_diagnostics_task = cx.spawn(async move |_, cx| {
19851 cx.background_executor().timer(debounce).await;
19852 if let Ok(task) = project.update(cx, |project, cx| {
19853 project.lsp_store().update(cx, |lsp_store, cx| {
19854 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
19855 })
19856 }) {
19857 task.await.log_err();
19858 }
19859 project
19860 .update(cx, |project, cx| {
19861 project.lsp_store().update(cx, |lsp_store, cx| {
19862 lsp_store.pull_document_diagnostics_for_buffer_edit(buffer_id, cx);
19863 })
19864 })
19865 .log_err();
19866 });
19867
19868 Some(())
19869 }
19870
19871 pub fn set_selections_from_remote(
19872 &mut self,
19873 selections: Vec<Selection<Anchor>>,
19874 pending_selection: Option<Selection<Anchor>>,
19875 window: &mut Window,
19876 cx: &mut Context<Self>,
19877 ) {
19878 let old_cursor_position = self.selections.newest_anchor().head();
19879 self.selections
19880 .change_with(&self.display_snapshot(cx), |s| {
19881 s.select_anchors(selections);
19882 if let Some(pending_selection) = pending_selection {
19883 s.set_pending(pending_selection, SelectMode::Character);
19884 } else {
19885 s.clear_pending();
19886 }
19887 });
19888 self.selections_did_change(
19889 false,
19890 &old_cursor_position,
19891 SelectionEffects::default(),
19892 window,
19893 cx,
19894 );
19895 }
19896
19897 pub fn transact(
19898 &mut self,
19899 window: &mut Window,
19900 cx: &mut Context<Self>,
19901 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
19902 ) -> Option<TransactionId> {
19903 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
19904 this.start_transaction_at(Instant::now(), window, cx);
19905 update(this, window, cx);
19906 this.end_transaction_at(Instant::now(), cx)
19907 })
19908 }
19909
19910 pub fn start_transaction_at(
19911 &mut self,
19912 now: Instant,
19913 window: &mut Window,
19914 cx: &mut Context<Self>,
19915 ) -> Option<TransactionId> {
19916 self.end_selection(window, cx);
19917 if let Some(tx_id) = self
19918 .buffer
19919 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
19920 {
19921 self.selection_history
19922 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
19923 cx.emit(EditorEvent::TransactionBegun {
19924 transaction_id: tx_id,
19925 });
19926 Some(tx_id)
19927 } else {
19928 None
19929 }
19930 }
19931
19932 pub fn end_transaction_at(
19933 &mut self,
19934 now: Instant,
19935 cx: &mut Context<Self>,
19936 ) -> Option<TransactionId> {
19937 if let Some(transaction_id) = self
19938 .buffer
19939 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
19940 {
19941 if let Some((_, end_selections)) =
19942 self.selection_history.transaction_mut(transaction_id)
19943 {
19944 *end_selections = Some(self.selections.disjoint_anchors_arc());
19945 } else {
19946 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
19947 }
19948
19949 cx.emit(EditorEvent::Edited { transaction_id });
19950 Some(transaction_id)
19951 } else {
19952 None
19953 }
19954 }
19955
19956 pub fn modify_transaction_selection_history(
19957 &mut self,
19958 transaction_id: TransactionId,
19959 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
19960 ) -> bool {
19961 self.selection_history
19962 .transaction_mut(transaction_id)
19963 .map(modify)
19964 .is_some()
19965 }
19966
19967 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
19968 if self.selection_mark_mode {
19969 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19970 s.move_with(&mut |_, sel| {
19971 sel.collapse_to(sel.head(), SelectionGoal::None);
19972 });
19973 })
19974 }
19975 self.selection_mark_mode = true;
19976 cx.notify();
19977 }
19978
19979 pub fn swap_selection_ends(
19980 &mut self,
19981 _: &actions::SwapSelectionEnds,
19982 window: &mut Window,
19983 cx: &mut Context<Self>,
19984 ) {
19985 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19986 s.move_with(&mut |_, sel| {
19987 if sel.start != sel.end {
19988 sel.reversed = !sel.reversed
19989 }
19990 });
19991 });
19992 self.request_autoscroll(Autoscroll::newest(), cx);
19993 cx.notify();
19994 }
19995
19996 pub fn toggle_focus(
19997 workspace: &mut Workspace,
19998 _: &actions::ToggleFocus,
19999 window: &mut Window,
20000 cx: &mut Context<Workspace>,
20001 ) {
20002 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
20003 return;
20004 };
20005 workspace.activate_item(&item, true, true, window, cx);
20006 }
20007
20008 pub fn toggle_fold(
20009 &mut self,
20010 _: &actions::ToggleFold,
20011 window: &mut Window,
20012 cx: &mut Context<Self>,
20013 ) {
20014 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
20015 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20016 let selection = self.selections.newest::<Point>(&display_map);
20017
20018 let range = if selection.is_empty() {
20019 let point = selection.head().to_display_point(&display_map);
20020 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
20021 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
20022 .to_point(&display_map);
20023 start..end
20024 } else {
20025 selection.range()
20026 };
20027 if display_map.folds_in_range(range).next().is_some() {
20028 self.unfold_lines(&Default::default(), window, cx)
20029 } else {
20030 self.fold(&Default::default(), window, cx)
20031 }
20032 } else {
20033 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20034 let buffer_ids: HashSet<_> = self
20035 .selections
20036 .disjoint_anchor_ranges()
20037 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20038 .collect();
20039
20040 let should_unfold = buffer_ids
20041 .iter()
20042 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
20043
20044 for buffer_id in buffer_ids {
20045 if should_unfold {
20046 self.unfold_buffer(buffer_id, cx);
20047 } else {
20048 self.fold_buffer(buffer_id, cx);
20049 }
20050 }
20051 }
20052 }
20053
20054 pub fn toggle_fold_recursive(
20055 &mut self,
20056 _: &actions::ToggleFoldRecursive,
20057 window: &mut Window,
20058 cx: &mut Context<Self>,
20059 ) {
20060 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20061
20062 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20063 let range = if selection.is_empty() {
20064 let point = selection.head().to_display_point(&display_map);
20065 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
20066 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
20067 .to_point(&display_map);
20068 start..end
20069 } else {
20070 selection.range()
20071 };
20072 if display_map.folds_in_range(range).next().is_some() {
20073 self.unfold_recursive(&Default::default(), window, cx)
20074 } else {
20075 self.fold_recursive(&Default::default(), window, cx)
20076 }
20077 }
20078
20079 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
20080 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
20081 let mut to_fold = Vec::new();
20082 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20083 let selections = self.selections.all_adjusted(&display_map);
20084
20085 for selection in selections {
20086 let range = selection.range().sorted();
20087 let buffer_start_row = range.start.row;
20088
20089 if range.start.row != range.end.row {
20090 let mut found = false;
20091 let mut row = range.start.row;
20092 while row <= range.end.row {
20093 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
20094 {
20095 found = true;
20096 row = crease.range().end.row + 1;
20097 to_fold.push(crease);
20098 } else {
20099 row += 1
20100 }
20101 }
20102 if found {
20103 continue;
20104 }
20105 }
20106
20107 for row in (0..=range.start.row).rev() {
20108 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
20109 && crease.range().end.row >= buffer_start_row
20110 {
20111 to_fold.push(crease);
20112 if row <= range.start.row {
20113 break;
20114 }
20115 }
20116 }
20117 }
20118
20119 self.fold_creases(to_fold, true, window, cx);
20120 } else {
20121 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20122 let buffer_ids = self
20123 .selections
20124 .disjoint_anchor_ranges()
20125 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20126 .collect::<HashSet<_>>();
20127 for buffer_id in buffer_ids {
20128 self.fold_buffer(buffer_id, cx);
20129 }
20130 }
20131 }
20132
20133 pub fn toggle_fold_all(
20134 &mut self,
20135 _: &actions::ToggleFoldAll,
20136 window: &mut Window,
20137 cx: &mut Context<Self>,
20138 ) {
20139 let has_folds = if self.buffer.read(cx).is_singleton() {
20140 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20141 let has_folds = display_map
20142 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
20143 .next()
20144 .is_some();
20145 has_folds
20146 } else {
20147 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
20148 let has_folds = buffer_ids
20149 .iter()
20150 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
20151 has_folds
20152 };
20153
20154 if has_folds {
20155 self.unfold_all(&actions::UnfoldAll, window, cx);
20156 } else {
20157 self.fold_all(&actions::FoldAll, window, cx);
20158 }
20159 }
20160
20161 fn fold_at_level(
20162 &mut self,
20163 fold_at: &FoldAtLevel,
20164 window: &mut Window,
20165 cx: &mut Context<Self>,
20166 ) {
20167 if !self.buffer.read(cx).is_singleton() {
20168 return;
20169 }
20170
20171 let fold_at_level = fold_at.0;
20172 let snapshot = self.buffer.read(cx).snapshot(cx);
20173 let mut to_fold = Vec::new();
20174 let mut stack = vec![(0, snapshot.max_row().0, 1)];
20175
20176 let row_ranges_to_keep: Vec<Range<u32>> = self
20177 .selections
20178 .all::<Point>(&self.display_snapshot(cx))
20179 .into_iter()
20180 .map(|sel| sel.start.row..sel.end.row)
20181 .collect();
20182
20183 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
20184 while start_row < end_row {
20185 match self
20186 .snapshot(window, cx)
20187 .crease_for_buffer_row(MultiBufferRow(start_row))
20188 {
20189 Some(crease) => {
20190 let nested_start_row = crease.range().start.row + 1;
20191 let nested_end_row = crease.range().end.row;
20192
20193 if current_level < fold_at_level {
20194 stack.push((nested_start_row, nested_end_row, current_level + 1));
20195 } else if current_level == fold_at_level {
20196 // Fold iff there is no selection completely contained within the fold region
20197 if !row_ranges_to_keep.iter().any(|selection| {
20198 selection.end >= nested_start_row
20199 && selection.start <= nested_end_row
20200 }) {
20201 to_fold.push(crease);
20202 }
20203 }
20204
20205 start_row = nested_end_row + 1;
20206 }
20207 None => start_row += 1,
20208 }
20209 }
20210 }
20211
20212 self.fold_creases(to_fold, true, window, cx);
20213 }
20214
20215 pub fn fold_at_level_1(
20216 &mut self,
20217 _: &actions::FoldAtLevel1,
20218 window: &mut Window,
20219 cx: &mut Context<Self>,
20220 ) {
20221 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
20222 }
20223
20224 pub fn fold_at_level_2(
20225 &mut self,
20226 _: &actions::FoldAtLevel2,
20227 window: &mut Window,
20228 cx: &mut Context<Self>,
20229 ) {
20230 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
20231 }
20232
20233 pub fn fold_at_level_3(
20234 &mut self,
20235 _: &actions::FoldAtLevel3,
20236 window: &mut Window,
20237 cx: &mut Context<Self>,
20238 ) {
20239 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
20240 }
20241
20242 pub fn fold_at_level_4(
20243 &mut self,
20244 _: &actions::FoldAtLevel4,
20245 window: &mut Window,
20246 cx: &mut Context<Self>,
20247 ) {
20248 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
20249 }
20250
20251 pub fn fold_at_level_5(
20252 &mut self,
20253 _: &actions::FoldAtLevel5,
20254 window: &mut Window,
20255 cx: &mut Context<Self>,
20256 ) {
20257 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
20258 }
20259
20260 pub fn fold_at_level_6(
20261 &mut self,
20262 _: &actions::FoldAtLevel6,
20263 window: &mut Window,
20264 cx: &mut Context<Self>,
20265 ) {
20266 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
20267 }
20268
20269 pub fn fold_at_level_7(
20270 &mut self,
20271 _: &actions::FoldAtLevel7,
20272 window: &mut Window,
20273 cx: &mut Context<Self>,
20274 ) {
20275 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
20276 }
20277
20278 pub fn fold_at_level_8(
20279 &mut self,
20280 _: &actions::FoldAtLevel8,
20281 window: &mut Window,
20282 cx: &mut Context<Self>,
20283 ) {
20284 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
20285 }
20286
20287 pub fn fold_at_level_9(
20288 &mut self,
20289 _: &actions::FoldAtLevel9,
20290 window: &mut Window,
20291 cx: &mut Context<Self>,
20292 ) {
20293 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
20294 }
20295
20296 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
20297 if self.buffer.read(cx).is_singleton() {
20298 let mut fold_ranges = Vec::new();
20299 let snapshot = self.buffer.read(cx).snapshot(cx);
20300
20301 for row in 0..snapshot.max_row().0 {
20302 if let Some(foldable_range) = self
20303 .snapshot(window, cx)
20304 .crease_for_buffer_row(MultiBufferRow(row))
20305 {
20306 fold_ranges.push(foldable_range);
20307 }
20308 }
20309
20310 self.fold_creases(fold_ranges, true, window, cx);
20311 } else {
20312 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
20313 editor
20314 .update_in(cx, |editor, _, cx| {
20315 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
20316 editor.fold_buffer(buffer_id, cx);
20317 }
20318 })
20319 .ok();
20320 });
20321 }
20322 }
20323
20324 pub fn fold_function_bodies(
20325 &mut self,
20326 _: &actions::FoldFunctionBodies,
20327 window: &mut Window,
20328 cx: &mut Context<Self>,
20329 ) {
20330 let snapshot = self.buffer.read(cx).snapshot(cx);
20331
20332 let ranges = snapshot
20333 .text_object_ranges(
20334 MultiBufferOffset(0)..snapshot.len(),
20335 TreeSitterOptions::default(),
20336 )
20337 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
20338 .collect::<Vec<_>>();
20339
20340 let creases = ranges
20341 .into_iter()
20342 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
20343 .collect();
20344
20345 self.fold_creases(creases, true, window, cx);
20346 }
20347
20348 pub fn fold_recursive(
20349 &mut self,
20350 _: &actions::FoldRecursive,
20351 window: &mut Window,
20352 cx: &mut Context<Self>,
20353 ) {
20354 let mut to_fold = Vec::new();
20355 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20356 let selections = self.selections.all_adjusted(&display_map);
20357
20358 for selection in selections {
20359 let range = selection.range().sorted();
20360 let buffer_start_row = range.start.row;
20361
20362 if range.start.row != range.end.row {
20363 let mut found = false;
20364 for row in range.start.row..=range.end.row {
20365 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
20366 found = true;
20367 to_fold.push(crease);
20368 }
20369 }
20370 if found {
20371 continue;
20372 }
20373 }
20374
20375 for row in (0..=range.start.row).rev() {
20376 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
20377 if crease.range().end.row >= buffer_start_row {
20378 to_fold.push(crease);
20379 } else {
20380 break;
20381 }
20382 }
20383 }
20384 }
20385
20386 self.fold_creases(to_fold, true, window, cx);
20387 }
20388
20389 pub fn fold_at(
20390 &mut self,
20391 buffer_row: MultiBufferRow,
20392 window: &mut Window,
20393 cx: &mut Context<Self>,
20394 ) {
20395 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20396
20397 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
20398 let autoscroll = self
20399 .selections
20400 .all::<Point>(&display_map)
20401 .iter()
20402 .any(|selection| crease.range().overlaps(&selection.range()));
20403
20404 self.fold_creases(vec![crease], autoscroll, window, cx);
20405 }
20406 }
20407
20408 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
20409 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
20410 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20411 let buffer = display_map.buffer_snapshot();
20412 let selections = self.selections.all::<Point>(&display_map);
20413 let ranges = selections
20414 .iter()
20415 .map(|s| {
20416 let range = s.display_range(&display_map).sorted();
20417 let mut start = range.start.to_point(&display_map);
20418 let mut end = range.end.to_point(&display_map);
20419 start.column = 0;
20420 end.column = buffer.line_len(MultiBufferRow(end.row));
20421 start..end
20422 })
20423 .collect::<Vec<_>>();
20424
20425 self.unfold_ranges(&ranges, true, true, cx);
20426 } else {
20427 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20428 let buffer_ids = self
20429 .selections
20430 .disjoint_anchor_ranges()
20431 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20432 .collect::<HashSet<_>>();
20433 for buffer_id in buffer_ids {
20434 self.unfold_buffer(buffer_id, cx);
20435 }
20436 }
20437 }
20438
20439 pub fn unfold_recursive(
20440 &mut self,
20441 _: &UnfoldRecursive,
20442 _window: &mut Window,
20443 cx: &mut Context<Self>,
20444 ) {
20445 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20446 let selections = self.selections.all::<Point>(&display_map);
20447 let ranges = selections
20448 .iter()
20449 .map(|s| {
20450 let mut range = s.display_range(&display_map).sorted();
20451 *range.start.column_mut() = 0;
20452 *range.end.column_mut() = display_map.line_len(range.end.row());
20453 let start = range.start.to_point(&display_map);
20454 let end = range.end.to_point(&display_map);
20455 start..end
20456 })
20457 .collect::<Vec<_>>();
20458
20459 self.unfold_ranges(&ranges, true, true, cx);
20460 }
20461
20462 pub fn unfold_at(
20463 &mut self,
20464 buffer_row: MultiBufferRow,
20465 _window: &mut Window,
20466 cx: &mut Context<Self>,
20467 ) {
20468 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20469
20470 let intersection_range = Point::new(buffer_row.0, 0)
20471 ..Point::new(
20472 buffer_row.0,
20473 display_map.buffer_snapshot().line_len(buffer_row),
20474 );
20475
20476 let autoscroll = self
20477 .selections
20478 .all::<Point>(&display_map)
20479 .iter()
20480 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
20481
20482 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
20483 }
20484
20485 pub fn unfold_all(
20486 &mut self,
20487 _: &actions::UnfoldAll,
20488 _window: &mut Window,
20489 cx: &mut Context<Self>,
20490 ) {
20491 if self.buffer.read(cx).is_singleton() {
20492 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20493 self.unfold_ranges(
20494 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
20495 true,
20496 true,
20497 cx,
20498 );
20499 } else {
20500 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
20501 editor
20502 .update(cx, |editor, cx| {
20503 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
20504 editor.unfold_buffer(buffer_id, cx);
20505 }
20506 })
20507 .ok();
20508 });
20509 }
20510 }
20511
20512 pub fn fold_selected_ranges(
20513 &mut self,
20514 _: &FoldSelectedRanges,
20515 window: &mut Window,
20516 cx: &mut Context<Self>,
20517 ) {
20518 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20519 let selections = self.selections.all_adjusted(&display_map);
20520 let ranges = selections
20521 .into_iter()
20522 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
20523 .collect::<Vec<_>>();
20524 self.fold_creases(ranges, true, window, cx);
20525 }
20526
20527 pub fn fold_ranges<T: ToOffset + Clone>(
20528 &mut self,
20529 ranges: Vec<Range<T>>,
20530 auto_scroll: bool,
20531 window: &mut Window,
20532 cx: &mut Context<Self>,
20533 ) {
20534 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20535 let ranges = ranges
20536 .into_iter()
20537 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
20538 .collect::<Vec<_>>();
20539 self.fold_creases(ranges, auto_scroll, window, cx);
20540 }
20541
20542 pub fn fold_creases<T: ToOffset + Clone>(
20543 &mut self,
20544 creases: Vec<Crease<T>>,
20545 auto_scroll: bool,
20546 window: &mut Window,
20547 cx: &mut Context<Self>,
20548 ) {
20549 if creases.is_empty() {
20550 return;
20551 }
20552
20553 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
20554
20555 if auto_scroll {
20556 self.request_autoscroll(Autoscroll::fit(), cx);
20557 }
20558
20559 cx.notify();
20560
20561 self.scrollbar_marker_state.dirty = true;
20562 self.update_data_on_scroll(window, cx);
20563 self.folds_did_change(cx);
20564 }
20565
20566 /// Removes any folds whose ranges intersect any of the given ranges.
20567 pub fn unfold_ranges<T: ToOffset + Clone>(
20568 &mut self,
20569 ranges: &[Range<T>],
20570 inclusive: bool,
20571 auto_scroll: bool,
20572 cx: &mut Context<Self>,
20573 ) {
20574 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20575 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx);
20576 });
20577 self.folds_did_change(cx);
20578 }
20579
20580 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20581 self.fold_buffers([buffer_id], cx);
20582 }
20583
20584 pub fn fold_buffers(
20585 &mut self,
20586 buffer_ids: impl IntoIterator<Item = BufferId>,
20587 cx: &mut Context<Self>,
20588 ) {
20589 if self.buffer().read(cx).is_singleton() {
20590 return;
20591 }
20592
20593 let ids_to_fold: Vec<BufferId> = buffer_ids
20594 .into_iter()
20595 .filter(|id| !self.is_buffer_folded(*id, cx))
20596 .collect();
20597
20598 if ids_to_fold.is_empty() {
20599 return;
20600 }
20601
20602 let mut all_folded_excerpt_ids = Vec::new();
20603 for buffer_id in &ids_to_fold {
20604 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(*buffer_id, cx);
20605 all_folded_excerpt_ids.extend(folded_excerpts.into_iter().map(|(id, _, _)| id));
20606 }
20607
20608 self.display_map.update(cx, |display_map, cx| {
20609 display_map.fold_buffers(ids_to_fold.clone(), cx)
20610 });
20611
20612 let snapshot = self.display_snapshot(cx);
20613 self.selections.change_with(&snapshot, |selections| {
20614 for buffer_id in ids_to_fold {
20615 selections.remove_selections_from_buffer(buffer_id);
20616 }
20617 });
20618
20619 cx.emit(EditorEvent::BufferFoldToggled {
20620 ids: all_folded_excerpt_ids,
20621 folded: true,
20622 });
20623 cx.notify();
20624 }
20625
20626 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20627 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
20628 return;
20629 }
20630 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
20631 self.display_map.update(cx, |display_map, cx| {
20632 display_map.unfold_buffers([buffer_id], cx);
20633 });
20634 cx.emit(EditorEvent::BufferFoldToggled {
20635 ids: unfolded_excerpts.iter().map(|&(id, _, _)| id).collect(),
20636 folded: false,
20637 });
20638 cx.notify();
20639 }
20640
20641 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
20642 self.display_map.read(cx).is_buffer_folded(buffer)
20643 }
20644
20645 pub fn has_any_buffer_folded(&self, cx: &App) -> bool {
20646 if self.buffer().read(cx).is_singleton() {
20647 return false;
20648 }
20649 !self.folded_buffers(cx).is_empty()
20650 }
20651
20652 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
20653 self.display_map.read(cx).folded_buffers()
20654 }
20655
20656 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20657 self.display_map.update(cx, |display_map, cx| {
20658 display_map.disable_header_for_buffer(buffer_id, cx);
20659 });
20660 cx.notify();
20661 }
20662
20663 /// Removes any folds with the given ranges.
20664 pub fn remove_folds_with_type<T: ToOffset + Clone>(
20665 &mut self,
20666 ranges: &[Range<T>],
20667 type_id: TypeId,
20668 auto_scroll: bool,
20669 cx: &mut Context<Self>,
20670 ) {
20671 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20672 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
20673 });
20674 self.folds_did_change(cx);
20675 }
20676
20677 fn remove_folds_with<T: ToOffset + Clone>(
20678 &mut self,
20679 ranges: &[Range<T>],
20680 auto_scroll: bool,
20681 cx: &mut Context<Self>,
20682 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
20683 ) {
20684 if ranges.is_empty() {
20685 return;
20686 }
20687
20688 let mut buffers_affected = HashSet::default();
20689 let multi_buffer = self.buffer().read(cx);
20690 for range in ranges {
20691 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
20692 buffers_affected.insert(buffer.read(cx).remote_id());
20693 };
20694 }
20695
20696 self.display_map.update(cx, update);
20697
20698 if auto_scroll {
20699 self.request_autoscroll(Autoscroll::fit(), cx);
20700 }
20701
20702 cx.notify();
20703 self.scrollbar_marker_state.dirty = true;
20704 self.active_indent_guides_state.dirty = true;
20705 }
20706
20707 pub fn update_renderer_widths(
20708 &mut self,
20709 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
20710 cx: &mut Context<Self>,
20711 ) -> bool {
20712 self.display_map
20713 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
20714 }
20715
20716 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
20717 self.display_map.read(cx).fold_placeholder.clone()
20718 }
20719
20720 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
20721 self.buffer.update(cx, |buffer, cx| {
20722 buffer.set_all_diff_hunks_expanded(cx);
20723 });
20724 }
20725
20726 pub fn expand_all_diff_hunks(
20727 &mut self,
20728 _: &ExpandAllDiffHunks,
20729 _window: &mut Window,
20730 cx: &mut Context<Self>,
20731 ) {
20732 self.buffer.update(cx, |buffer, cx| {
20733 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20734 });
20735 }
20736
20737 pub fn collapse_all_diff_hunks(
20738 &mut self,
20739 _: &CollapseAllDiffHunks,
20740 _window: &mut Window,
20741 cx: &mut Context<Self>,
20742 ) {
20743 self.buffer.update(cx, |buffer, cx| {
20744 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20745 });
20746 }
20747
20748 pub fn toggle_selected_diff_hunks(
20749 &mut self,
20750 _: &ToggleSelectedDiffHunks,
20751 _window: &mut Window,
20752 cx: &mut Context<Self>,
20753 ) {
20754 let ranges: Vec<_> = self
20755 .selections
20756 .disjoint_anchors()
20757 .iter()
20758 .map(|s| s.range())
20759 .collect();
20760 self.toggle_diff_hunks_in_ranges(ranges, cx);
20761 }
20762
20763 pub fn diff_hunks_in_ranges<'a>(
20764 &'a self,
20765 ranges: &'a [Range<Anchor>],
20766 buffer: &'a MultiBufferSnapshot,
20767 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
20768 ranges.iter().flat_map(move |range| {
20769 let end_excerpt_id = range.end.excerpt_id;
20770 let range = range.to_point(buffer);
20771 let mut peek_end = range.end;
20772 if range.end.row < buffer.max_row().0 {
20773 peek_end = Point::new(range.end.row + 1, 0);
20774 }
20775 buffer
20776 .diff_hunks_in_range(range.start..peek_end)
20777 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
20778 })
20779 }
20780
20781 pub fn has_stageable_diff_hunks_in_ranges(
20782 &self,
20783 ranges: &[Range<Anchor>],
20784 snapshot: &MultiBufferSnapshot,
20785 ) -> bool {
20786 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
20787 hunks.any(|hunk| hunk.status().has_secondary_hunk())
20788 }
20789
20790 pub fn toggle_staged_selected_diff_hunks(
20791 &mut self,
20792 _: &::git::ToggleStaged,
20793 _: &mut Window,
20794 cx: &mut Context<Self>,
20795 ) {
20796 let snapshot = self.buffer.read(cx).snapshot(cx);
20797 let ranges: Vec<_> = self
20798 .selections
20799 .disjoint_anchors()
20800 .iter()
20801 .map(|s| s.range())
20802 .collect();
20803 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
20804 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20805 }
20806
20807 pub fn set_render_diff_hunk_controls(
20808 &mut self,
20809 render_diff_hunk_controls: RenderDiffHunkControlsFn,
20810 cx: &mut Context<Self>,
20811 ) {
20812 self.render_diff_hunk_controls = render_diff_hunk_controls;
20813 cx.notify();
20814 }
20815
20816 pub fn stage_and_next(
20817 &mut self,
20818 _: &::git::StageAndNext,
20819 window: &mut Window,
20820 cx: &mut Context<Self>,
20821 ) {
20822 self.do_stage_or_unstage_and_next(true, window, cx);
20823 }
20824
20825 pub fn unstage_and_next(
20826 &mut self,
20827 _: &::git::UnstageAndNext,
20828 window: &mut Window,
20829 cx: &mut Context<Self>,
20830 ) {
20831 self.do_stage_or_unstage_and_next(false, window, cx);
20832 }
20833
20834 pub fn stage_or_unstage_diff_hunks(
20835 &mut self,
20836 stage: bool,
20837 ranges: Vec<Range<Anchor>>,
20838 cx: &mut Context<Self>,
20839 ) {
20840 if self.delegate_stage_and_restore {
20841 let snapshot = self.buffer.read(cx).snapshot(cx);
20842 let hunks: Vec<_> = self.diff_hunks_in_ranges(&ranges, &snapshot).collect();
20843 if !hunks.is_empty() {
20844 cx.emit(EditorEvent::StageOrUnstageRequested { stage, hunks });
20845 }
20846 return;
20847 }
20848 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
20849 cx.spawn(async move |this, cx| {
20850 task.await?;
20851 this.update(cx, |this, cx| {
20852 let snapshot = this.buffer.read(cx).snapshot(cx);
20853 let chunk_by = this
20854 .diff_hunks_in_ranges(&ranges, &snapshot)
20855 .chunk_by(|hunk| hunk.buffer_id);
20856 for (buffer_id, hunks) in &chunk_by {
20857 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
20858 }
20859 })
20860 })
20861 .detach_and_log_err(cx);
20862 }
20863
20864 fn save_buffers_for_ranges_if_needed(
20865 &mut self,
20866 ranges: &[Range<Anchor>],
20867 cx: &mut Context<Editor>,
20868 ) -> Task<Result<()>> {
20869 let multibuffer = self.buffer.read(cx);
20870 let snapshot = multibuffer.read(cx);
20871 let buffer_ids: HashSet<_> = ranges
20872 .iter()
20873 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
20874 .collect();
20875 drop(snapshot);
20876
20877 let mut buffers = HashSet::default();
20878 for buffer_id in buffer_ids {
20879 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
20880 let buffer = buffer_entity.read(cx);
20881 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
20882 {
20883 buffers.insert(buffer_entity);
20884 }
20885 }
20886 }
20887
20888 if let Some(project) = &self.project {
20889 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
20890 } else {
20891 Task::ready(Ok(()))
20892 }
20893 }
20894
20895 fn do_stage_or_unstage_and_next(
20896 &mut self,
20897 stage: bool,
20898 window: &mut Window,
20899 cx: &mut Context<Self>,
20900 ) {
20901 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
20902
20903 if ranges.iter().any(|range| range.start != range.end) {
20904 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20905 return;
20906 }
20907
20908 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20909
20910 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
20911 let wrap_around = !all_diff_hunks_expanded;
20912 let snapshot = self.snapshot(window, cx);
20913 let position = self
20914 .selections
20915 .newest::<Point>(&snapshot.display_snapshot)
20916 .head();
20917
20918 self.go_to_hunk_before_or_after_position(
20919 &snapshot,
20920 position,
20921 Direction::Next,
20922 wrap_around,
20923 window,
20924 cx,
20925 );
20926 }
20927
20928 pub(crate) fn do_stage_or_unstage(
20929 &self,
20930 stage: bool,
20931 buffer_id: BufferId,
20932 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
20933 cx: &mut App,
20934 ) -> Option<()> {
20935 let project = self.project()?;
20936 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
20937 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
20938 let buffer_snapshot = buffer.read(cx).snapshot();
20939 let file_exists = buffer_snapshot
20940 .file()
20941 .is_some_and(|file| file.disk_state().exists());
20942 diff.update(cx, |diff, cx| {
20943 diff.stage_or_unstage_hunks(
20944 stage,
20945 &hunks
20946 .map(|hunk| buffer_diff::DiffHunk {
20947 buffer_range: hunk.buffer_range,
20948 // We don't need to pass in word diffs here because they're only used for rendering and
20949 // this function changes internal state
20950 base_word_diffs: Vec::default(),
20951 buffer_word_diffs: Vec::default(),
20952 diff_base_byte_range: hunk.diff_base_byte_range.start.0
20953 ..hunk.diff_base_byte_range.end.0,
20954 secondary_status: hunk.status.secondary,
20955 range: Point::zero()..Point::zero(), // unused
20956 })
20957 .collect::<Vec<_>>(),
20958 &buffer_snapshot,
20959 file_exists,
20960 cx,
20961 )
20962 });
20963 None
20964 }
20965
20966 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
20967 let ranges: Vec<_> = self
20968 .selections
20969 .disjoint_anchors()
20970 .iter()
20971 .map(|s| s.range())
20972 .collect();
20973 self.buffer
20974 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
20975 }
20976
20977 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
20978 self.buffer.update(cx, |buffer, cx| {
20979 let ranges = vec![Anchor::min()..Anchor::max()];
20980 if !buffer.all_diff_hunks_expanded()
20981 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
20982 {
20983 buffer.collapse_diff_hunks(ranges, cx);
20984 true
20985 } else {
20986 false
20987 }
20988 })
20989 }
20990
20991 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
20992 if self.buffer.read(cx).all_diff_hunks_expanded() {
20993 return true;
20994 }
20995 let ranges = vec![Anchor::min()..Anchor::max()];
20996 self.buffer
20997 .read(cx)
20998 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
20999 }
21000
21001 fn toggle_diff_hunks_in_ranges(
21002 &mut self,
21003 ranges: Vec<Range<Anchor>>,
21004 cx: &mut Context<Editor>,
21005 ) {
21006 self.buffer.update(cx, |buffer, cx| {
21007 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
21008 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
21009 })
21010 }
21011
21012 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
21013 self.buffer.update(cx, |buffer, cx| {
21014 buffer.toggle_single_diff_hunk(range, cx);
21015 })
21016 }
21017
21018 pub(crate) fn apply_all_diff_hunks(
21019 &mut self,
21020 _: &ApplyAllDiffHunks,
21021 window: &mut Window,
21022 cx: &mut Context<Self>,
21023 ) {
21024 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
21025
21026 let buffers = self.buffer.read(cx).all_buffers();
21027 for branch_buffer in buffers {
21028 branch_buffer.update(cx, |branch_buffer, cx| {
21029 branch_buffer.merge_into_base(Vec::new(), cx);
21030 });
21031 }
21032
21033 if let Some(project) = self.project.clone() {
21034 self.save(
21035 SaveOptions {
21036 format: true,
21037 autosave: false,
21038 },
21039 project,
21040 window,
21041 cx,
21042 )
21043 .detach_and_log_err(cx);
21044 }
21045 }
21046
21047 pub(crate) fn apply_selected_diff_hunks(
21048 &mut self,
21049 _: &ApplyDiffHunk,
21050 window: &mut Window,
21051 cx: &mut Context<Self>,
21052 ) {
21053 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
21054 let snapshot = self.snapshot(window, cx);
21055 let hunks = snapshot.hunks_for_ranges(
21056 self.selections
21057 .all(&snapshot.display_snapshot)
21058 .into_iter()
21059 .map(|selection| selection.range()),
21060 );
21061 let mut ranges_by_buffer = HashMap::default();
21062 self.transact(window, cx, |editor, _window, cx| {
21063 for hunk in hunks {
21064 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
21065 ranges_by_buffer
21066 .entry(buffer.clone())
21067 .or_insert_with(Vec::new)
21068 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
21069 }
21070 }
21071
21072 for (buffer, ranges) in ranges_by_buffer {
21073 buffer.update(cx, |buffer, cx| {
21074 buffer.merge_into_base(ranges, cx);
21075 });
21076 }
21077 });
21078
21079 if let Some(project) = self.project.clone() {
21080 self.save(
21081 SaveOptions {
21082 format: true,
21083 autosave: false,
21084 },
21085 project,
21086 window,
21087 cx,
21088 )
21089 .detach_and_log_err(cx);
21090 }
21091 }
21092
21093 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
21094 if hovered != self.gutter_hovered {
21095 self.gutter_hovered = hovered;
21096 cx.notify();
21097 }
21098 }
21099
21100 pub fn insert_blocks(
21101 &mut self,
21102 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
21103 autoscroll: Option<Autoscroll>,
21104 cx: &mut Context<Self>,
21105 ) -> Vec<CustomBlockId> {
21106 let blocks = self
21107 .display_map
21108 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
21109 if let Some(autoscroll) = autoscroll {
21110 self.request_autoscroll(autoscroll, cx);
21111 }
21112 cx.notify();
21113 blocks
21114 }
21115
21116 pub fn resize_blocks(
21117 &mut self,
21118 heights: HashMap<CustomBlockId, u32>,
21119 autoscroll: Option<Autoscroll>,
21120 cx: &mut Context<Self>,
21121 ) {
21122 self.display_map
21123 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
21124 if let Some(autoscroll) = autoscroll {
21125 self.request_autoscroll(autoscroll, cx);
21126 }
21127 cx.notify();
21128 }
21129
21130 pub fn replace_blocks(
21131 &mut self,
21132 renderers: HashMap<CustomBlockId, RenderBlock>,
21133 autoscroll: Option<Autoscroll>,
21134 cx: &mut Context<Self>,
21135 ) {
21136 self.display_map
21137 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
21138 if let Some(autoscroll) = autoscroll {
21139 self.request_autoscroll(autoscroll, cx);
21140 }
21141 cx.notify();
21142 }
21143
21144 pub fn remove_blocks(
21145 &mut self,
21146 block_ids: HashSet<CustomBlockId>,
21147 autoscroll: Option<Autoscroll>,
21148 cx: &mut Context<Self>,
21149 ) {
21150 self.display_map.update(cx, |display_map, cx| {
21151 display_map.remove_blocks(block_ids, cx)
21152 });
21153 if let Some(autoscroll) = autoscroll {
21154 self.request_autoscroll(autoscroll, cx);
21155 }
21156 cx.notify();
21157 }
21158
21159 pub fn row_for_block(
21160 &self,
21161 block_id: CustomBlockId,
21162 cx: &mut Context<Self>,
21163 ) -> Option<DisplayRow> {
21164 self.display_map
21165 .update(cx, |map, cx| map.row_for_block(block_id, cx))
21166 }
21167
21168 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
21169 self.focused_block = Some(focused_block);
21170 }
21171
21172 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
21173 self.focused_block.take()
21174 }
21175
21176 pub fn insert_creases(
21177 &mut self,
21178 creases: impl IntoIterator<Item = Crease<Anchor>>,
21179 cx: &mut Context<Self>,
21180 ) -> Vec<CreaseId> {
21181 self.display_map
21182 .update(cx, |map, cx| map.insert_creases(creases, cx))
21183 }
21184
21185 pub fn remove_creases(
21186 &mut self,
21187 ids: impl IntoIterator<Item = CreaseId>,
21188 cx: &mut Context<Self>,
21189 ) -> Vec<(CreaseId, Range<Anchor>)> {
21190 self.display_map
21191 .update(cx, |map, cx| map.remove_creases(ids, cx))
21192 }
21193
21194 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
21195 self.display_map
21196 .update(cx, |map, cx| map.snapshot(cx))
21197 .longest_row()
21198 }
21199
21200 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
21201 self.display_map
21202 .update(cx, |map, cx| map.snapshot(cx))
21203 .max_point()
21204 }
21205
21206 pub fn text(&self, cx: &App) -> String {
21207 self.buffer.read(cx).read(cx).text()
21208 }
21209
21210 pub fn is_empty(&self, cx: &App) -> bool {
21211 self.buffer.read(cx).read(cx).is_empty()
21212 }
21213
21214 pub fn text_option(&self, cx: &App) -> Option<String> {
21215 let text = self.text(cx);
21216 let text = text.trim();
21217
21218 if text.is_empty() {
21219 return None;
21220 }
21221
21222 Some(text.to_string())
21223 }
21224
21225 pub fn set_text(
21226 &mut self,
21227 text: impl Into<Arc<str>>,
21228 window: &mut Window,
21229 cx: &mut Context<Self>,
21230 ) {
21231 self.transact(window, cx, |this, _, cx| {
21232 this.buffer
21233 .read(cx)
21234 .as_singleton()
21235 .expect("you can only call set_text on editors for singleton buffers")
21236 .update(cx, |buffer, cx| buffer.set_text(text, cx));
21237 });
21238 }
21239
21240 pub fn display_text(&self, cx: &mut App) -> String {
21241 self.display_map
21242 .update(cx, |map, cx| map.snapshot(cx))
21243 .text()
21244 }
21245
21246 fn create_minimap(
21247 &self,
21248 minimap_settings: MinimapSettings,
21249 window: &mut Window,
21250 cx: &mut Context<Self>,
21251 ) -> Option<Entity<Self>> {
21252 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
21253 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
21254 }
21255
21256 fn initialize_new_minimap(
21257 &self,
21258 minimap_settings: MinimapSettings,
21259 window: &mut Window,
21260 cx: &mut Context<Self>,
21261 ) -> Entity<Self> {
21262 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
21263 const MINIMAP_FONT_FAMILY: SharedString = SharedString::new_static(".ZedMono");
21264
21265 let mut minimap = Editor::new_internal(
21266 EditorMode::Minimap {
21267 parent: cx.weak_entity(),
21268 },
21269 self.buffer.clone(),
21270 None,
21271 Some(self.display_map.clone()),
21272 window,
21273 cx,
21274 );
21275 let my_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
21276 let minimap_snapshot = minimap.display_map.update(cx, |map, cx| map.snapshot(cx));
21277 minimap.scroll_manager.clone_state(
21278 &self.scroll_manager,
21279 &my_snapshot,
21280 &minimap_snapshot,
21281 cx,
21282 );
21283 minimap.set_text_style_refinement(TextStyleRefinement {
21284 font_size: Some(MINIMAP_FONT_SIZE),
21285 font_weight: Some(MINIMAP_FONT_WEIGHT),
21286 font_family: Some(MINIMAP_FONT_FAMILY),
21287 ..Default::default()
21288 });
21289 minimap.update_minimap_configuration(minimap_settings, cx);
21290 cx.new(|_| minimap)
21291 }
21292
21293 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
21294 let current_line_highlight = minimap_settings
21295 .current_line_highlight
21296 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
21297 self.set_current_line_highlight(Some(current_line_highlight));
21298 }
21299
21300 pub fn minimap(&self) -> Option<&Entity<Self>> {
21301 self.minimap
21302 .as_ref()
21303 .filter(|_| self.minimap_visibility.visible())
21304 }
21305
21306 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
21307 let mut wrap_guides = smallvec![];
21308
21309 if self.show_wrap_guides == Some(false) {
21310 return wrap_guides;
21311 }
21312
21313 let settings = self.buffer.read(cx).language_settings(cx);
21314 if settings.show_wrap_guides {
21315 match self.soft_wrap_mode(cx) {
21316 SoftWrap::Column(soft_wrap) => {
21317 wrap_guides.push((soft_wrap as usize, true));
21318 }
21319 SoftWrap::Bounded(soft_wrap) => {
21320 wrap_guides.push((soft_wrap as usize, true));
21321 }
21322 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
21323 }
21324 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
21325 }
21326
21327 wrap_guides
21328 }
21329
21330 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
21331 let settings = self.buffer.read(cx).language_settings(cx);
21332 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
21333 match mode {
21334 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
21335 SoftWrap::None
21336 }
21337 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
21338 language_settings::SoftWrap::PreferredLineLength => {
21339 SoftWrap::Column(settings.preferred_line_length)
21340 }
21341 language_settings::SoftWrap::Bounded => {
21342 SoftWrap::Bounded(settings.preferred_line_length)
21343 }
21344 }
21345 }
21346
21347 pub fn set_soft_wrap_mode(
21348 &mut self,
21349 mode: language_settings::SoftWrap,
21350 cx: &mut Context<Self>,
21351 ) {
21352 self.soft_wrap_mode_override = Some(mode);
21353 cx.notify();
21354 }
21355
21356 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
21357 self.hard_wrap = hard_wrap;
21358 cx.notify();
21359 }
21360
21361 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
21362 self.text_style_refinement = Some(style);
21363 }
21364
21365 /// called by the Element so we know what style we were most recently rendered with.
21366 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
21367 // We intentionally do not inform the display map about the minimap style
21368 // so that wrapping is not recalculated and stays consistent for the editor
21369 // and its linked minimap.
21370 if !self.mode.is_minimap() {
21371 let font = style.text.font();
21372 let font_size = style.text.font_size.to_pixels(window.rem_size());
21373 let display_map = self
21374 .placeholder_display_map
21375 .as_ref()
21376 .filter(|_| self.is_empty(cx))
21377 .unwrap_or(&self.display_map);
21378
21379 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
21380 }
21381 self.style = Some(style);
21382 }
21383
21384 pub fn style(&mut self, cx: &App) -> &EditorStyle {
21385 if self.style.is_none() {
21386 self.style = Some(self.create_style(cx));
21387 }
21388 self.style.as_ref().unwrap()
21389 }
21390
21391 // Called by the element. This method is not designed to be called outside of the editor
21392 // element's layout code because it does not notify when rewrapping is computed synchronously.
21393 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
21394 if self.is_empty(cx) {
21395 self.placeholder_display_map
21396 .as_ref()
21397 .map_or(false, |display_map| {
21398 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
21399 })
21400 } else {
21401 self.display_map
21402 .update(cx, |map, cx| map.set_wrap_width(width, cx))
21403 }
21404 }
21405
21406 pub fn set_soft_wrap(&mut self) {
21407 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
21408 }
21409
21410 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
21411 if self.soft_wrap_mode_override.is_some() {
21412 self.soft_wrap_mode_override.take();
21413 } else {
21414 let soft_wrap = match self.soft_wrap_mode(cx) {
21415 SoftWrap::GitDiff => return,
21416 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
21417 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
21418 language_settings::SoftWrap::None
21419 }
21420 };
21421 self.soft_wrap_mode_override = Some(soft_wrap);
21422 }
21423 cx.notify();
21424 }
21425
21426 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
21427 let Some(workspace) = self.workspace() else {
21428 return;
21429 };
21430 let fs = workspace.read(cx).app_state().fs.clone();
21431 let current_show = TabBarSettings::get_global(cx).show;
21432 update_settings_file(fs, cx, move |setting, _| {
21433 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
21434 });
21435 }
21436
21437 pub fn toggle_indent_guides(
21438 &mut self,
21439 _: &ToggleIndentGuides,
21440 _: &mut Window,
21441 cx: &mut Context<Self>,
21442 ) {
21443 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
21444 self.buffer
21445 .read(cx)
21446 .language_settings(cx)
21447 .indent_guides
21448 .enabled
21449 });
21450 self.show_indent_guides = Some(!currently_enabled);
21451 cx.notify();
21452 }
21453
21454 fn should_show_indent_guides(&self) -> Option<bool> {
21455 self.show_indent_guides
21456 }
21457
21458 pub fn disable_indent_guides_for_buffer(
21459 &mut self,
21460 buffer_id: BufferId,
21461 cx: &mut Context<Self>,
21462 ) {
21463 self.buffers_with_disabled_indent_guides.insert(buffer_id);
21464 cx.notify();
21465 }
21466
21467 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
21468 self.buffers_with_disabled_indent_guides
21469 .contains(&buffer_id)
21470 }
21471
21472 pub fn toggle_line_numbers(
21473 &mut self,
21474 _: &ToggleLineNumbers,
21475 _: &mut Window,
21476 cx: &mut Context<Self>,
21477 ) {
21478 let mut editor_settings = EditorSettings::get_global(cx).clone();
21479 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
21480 EditorSettings::override_global(editor_settings, cx);
21481 }
21482
21483 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
21484 if let Some(show_line_numbers) = self.show_line_numbers {
21485 return show_line_numbers;
21486 }
21487 EditorSettings::get_global(cx).gutter.line_numbers
21488 }
21489
21490 pub fn relative_line_numbers(&self, cx: &App) -> RelativeLineNumbers {
21491 match (
21492 self.use_relative_line_numbers,
21493 EditorSettings::get_global(cx).relative_line_numbers,
21494 ) {
21495 (None, setting) => setting,
21496 (Some(false), _) => RelativeLineNumbers::Disabled,
21497 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
21498 (Some(true), _) => RelativeLineNumbers::Enabled,
21499 }
21500 }
21501
21502 pub fn toggle_relative_line_numbers(
21503 &mut self,
21504 _: &ToggleRelativeLineNumbers,
21505 _: &mut Window,
21506 cx: &mut Context<Self>,
21507 ) {
21508 let is_relative = self.relative_line_numbers(cx);
21509 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
21510 }
21511
21512 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
21513 self.use_relative_line_numbers = is_relative;
21514 cx.notify();
21515 }
21516
21517 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
21518 self.show_gutter = show_gutter;
21519 cx.notify();
21520 }
21521
21522 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
21523 self.show_scrollbars = ScrollbarAxes {
21524 horizontal: show,
21525 vertical: show,
21526 };
21527 cx.notify();
21528 }
21529
21530 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21531 self.show_scrollbars.vertical = show;
21532 cx.notify();
21533 }
21534
21535 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21536 self.show_scrollbars.horizontal = show;
21537 cx.notify();
21538 }
21539
21540 pub fn set_minimap_visibility(
21541 &mut self,
21542 minimap_visibility: MinimapVisibility,
21543 window: &mut Window,
21544 cx: &mut Context<Self>,
21545 ) {
21546 if self.minimap_visibility != minimap_visibility {
21547 if minimap_visibility.visible() && self.minimap.is_none() {
21548 let minimap_settings = EditorSettings::get_global(cx).minimap;
21549 self.minimap =
21550 self.create_minimap(minimap_settings.with_show_override(), window, cx);
21551 }
21552 self.minimap_visibility = minimap_visibility;
21553 cx.notify();
21554 }
21555 }
21556
21557 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21558 self.set_show_scrollbars(false, cx);
21559 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
21560 }
21561
21562 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21563 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
21564 }
21565
21566 /// Normally the text in full mode and auto height editors is padded on the
21567 /// left side by roughly half a character width for improved hit testing.
21568 ///
21569 /// Use this method to disable this for cases where this is not wanted (e.g.
21570 /// if you want to align the editor text with some other text above or below)
21571 /// or if you want to add this padding to single-line editors.
21572 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
21573 self.offset_content = offset_content;
21574 cx.notify();
21575 }
21576
21577 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
21578 self.show_line_numbers = Some(show_line_numbers);
21579 cx.notify();
21580 }
21581
21582 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
21583 self.disable_expand_excerpt_buttons = true;
21584 cx.notify();
21585 }
21586
21587 pub fn set_number_deleted_lines(&mut self, number: bool, cx: &mut Context<Self>) {
21588 self.number_deleted_lines = number;
21589 cx.notify();
21590 }
21591
21592 pub fn set_delegate_expand_excerpts(&mut self, delegate: bool) {
21593 self.delegate_expand_excerpts = delegate;
21594 }
21595
21596 pub fn set_delegate_stage_and_restore(&mut self, delegate: bool) {
21597 self.delegate_stage_and_restore = delegate;
21598 }
21599
21600 pub fn set_delegate_open_excerpts(&mut self, delegate: bool) {
21601 self.delegate_open_excerpts = delegate;
21602 }
21603
21604 pub fn set_on_local_selections_changed(
21605 &mut self,
21606 callback: Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
21607 ) {
21608 self.on_local_selections_changed = callback;
21609 }
21610
21611 pub fn set_suppress_selection_callback(&mut self, suppress: bool) {
21612 self.suppress_selection_callback = suppress;
21613 }
21614
21615 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
21616 self.show_git_diff_gutter = Some(show_git_diff_gutter);
21617 cx.notify();
21618 }
21619
21620 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
21621 self.show_code_actions = Some(show_code_actions);
21622 cx.notify();
21623 }
21624
21625 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
21626 self.show_runnables = Some(show_runnables);
21627 cx.notify();
21628 }
21629
21630 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
21631 self.show_breakpoints = Some(show_breakpoints);
21632 cx.notify();
21633 }
21634
21635 pub fn set_show_diff_review_button(&mut self, show: bool, cx: &mut Context<Self>) {
21636 self.show_diff_review_button = show;
21637 cx.notify();
21638 }
21639
21640 pub fn show_diff_review_button(&self) -> bool {
21641 self.show_diff_review_button
21642 }
21643
21644 pub fn render_diff_review_button(
21645 &self,
21646 display_row: DisplayRow,
21647 width: Pixels,
21648 cx: &mut Context<Self>,
21649 ) -> impl IntoElement {
21650 let text_color = cx.theme().colors().text;
21651 let icon_color = cx.theme().colors().icon_accent;
21652
21653 h_flex()
21654 .id("diff_review_button")
21655 .cursor_pointer()
21656 .w(width - px(1.))
21657 .h(relative(0.9))
21658 .justify_center()
21659 .rounded_sm()
21660 .border_1()
21661 .border_color(text_color.opacity(0.1))
21662 .bg(text_color.opacity(0.15))
21663 .hover(|s| {
21664 s.bg(icon_color.opacity(0.4))
21665 .border_color(icon_color.opacity(0.5))
21666 })
21667 .child(Icon::new(IconName::Plus).size(IconSize::Small))
21668 .tooltip(Tooltip::text("Add Review (drag to select multiple lines)"))
21669 .on_mouse_down(
21670 gpui::MouseButton::Left,
21671 cx.listener(move |editor, _event: &gpui::MouseDownEvent, window, cx| {
21672 editor.start_diff_review_drag(display_row, window, cx);
21673 }),
21674 )
21675 }
21676
21677 pub fn start_diff_review_drag(
21678 &mut self,
21679 display_row: DisplayRow,
21680 window: &mut Window,
21681 cx: &mut Context<Self>,
21682 ) {
21683 let snapshot = self.snapshot(window, cx);
21684 let point = snapshot
21685 .display_snapshot
21686 .display_point_to_point(DisplayPoint::new(display_row, 0), Bias::Left);
21687 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21688 self.diff_review_drag_state = Some(DiffReviewDragState {
21689 start_anchor: anchor,
21690 current_anchor: anchor,
21691 });
21692 cx.notify();
21693 }
21694
21695 pub fn update_diff_review_drag(
21696 &mut self,
21697 display_row: DisplayRow,
21698 window: &mut Window,
21699 cx: &mut Context<Self>,
21700 ) {
21701 if self.diff_review_drag_state.is_none() {
21702 return;
21703 }
21704 let snapshot = self.snapshot(window, cx);
21705 let point = snapshot
21706 .display_snapshot
21707 .display_point_to_point(display_row.as_display_point(), Bias::Left);
21708 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21709 if let Some(drag_state) = &mut self.diff_review_drag_state {
21710 drag_state.current_anchor = anchor;
21711 cx.notify();
21712 }
21713 }
21714
21715 pub fn end_diff_review_drag(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21716 if let Some(drag_state) = self.diff_review_drag_state.take() {
21717 let snapshot = self.snapshot(window, cx);
21718 let range = drag_state.row_range(&snapshot.display_snapshot);
21719 self.show_diff_review_overlay(*range.start()..*range.end(), window, cx);
21720 }
21721 cx.notify();
21722 }
21723
21724 pub fn cancel_diff_review_drag(&mut self, cx: &mut Context<Self>) {
21725 self.diff_review_drag_state = None;
21726 cx.notify();
21727 }
21728
21729 /// Calculates the appropriate block height for the diff review overlay.
21730 /// Height is in lines: 2 for input row, 1 for header when comments exist,
21731 /// and 2 lines per comment when expanded.
21732 fn calculate_overlay_height(
21733 &self,
21734 hunk_key: &DiffHunkKey,
21735 comments_expanded: bool,
21736 snapshot: &MultiBufferSnapshot,
21737 ) -> u32 {
21738 let comment_count = self.hunk_comment_count(hunk_key, snapshot);
21739 let base_height: u32 = 2; // Input row with avatar and buttons
21740
21741 if comment_count == 0 {
21742 base_height
21743 } else if comments_expanded {
21744 // Header (1 line) + 2 lines per comment
21745 base_height + 1 + (comment_count as u32 * 2)
21746 } else {
21747 // Just header when collapsed
21748 base_height + 1
21749 }
21750 }
21751
21752 pub fn show_diff_review_overlay(
21753 &mut self,
21754 display_range: Range<DisplayRow>,
21755 window: &mut Window,
21756 cx: &mut Context<Self>,
21757 ) {
21758 let Range { start, end } = display_range.sorted();
21759
21760 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21761 let editor_snapshot = self.snapshot(window, cx);
21762
21763 // Convert display rows to multibuffer points
21764 let start_point = editor_snapshot
21765 .display_snapshot
21766 .display_point_to_point(start.as_display_point(), Bias::Left);
21767 let end_point = editor_snapshot
21768 .display_snapshot
21769 .display_point_to_point(end.as_display_point(), Bias::Left);
21770 let end_multi_buffer_row = MultiBufferRow(end_point.row);
21771
21772 // Create anchor range for the selected lines (start of first line to end of last line)
21773 let line_end = Point::new(
21774 end_point.row,
21775 buffer_snapshot.line_len(end_multi_buffer_row),
21776 );
21777 let anchor_range =
21778 buffer_snapshot.anchor_after(start_point)..buffer_snapshot.anchor_before(line_end);
21779
21780 // Compute the hunk key for this display row
21781 let file_path = buffer_snapshot
21782 .file_at(start_point)
21783 .map(|file: &Arc<dyn language::File>| file.path().clone())
21784 .unwrap_or_else(|| Arc::from(util::rel_path::RelPath::empty()));
21785 let hunk_start_anchor = buffer_snapshot.anchor_before(start_point);
21786 let new_hunk_key = DiffHunkKey {
21787 file_path,
21788 hunk_start_anchor,
21789 };
21790
21791 // Check if we already have an overlay for this hunk
21792 if let Some(existing_overlay) = self.diff_review_overlays.iter().find(|overlay| {
21793 Self::hunk_keys_match(&overlay.hunk_key, &new_hunk_key, &buffer_snapshot)
21794 }) {
21795 // Just focus the existing overlay's prompt editor
21796 let focus_handle = existing_overlay.prompt_editor.focus_handle(cx);
21797 window.focus(&focus_handle, cx);
21798 return;
21799 }
21800
21801 // Dismiss overlays that have no comments for their hunks
21802 self.dismiss_overlays_without_comments(cx);
21803
21804 // Get the current user's avatar URI from the project's user_store
21805 let user_avatar_uri = self.project.as_ref().and_then(|project| {
21806 let user_store = project.read(cx).user_store();
21807 user_store
21808 .read(cx)
21809 .current_user()
21810 .map(|user| user.avatar_uri.clone())
21811 });
21812
21813 // Create anchor at the end of the last row so the block appears immediately below it
21814 // Use multibuffer coordinates for anchor creation
21815 let line_len = buffer_snapshot.line_len(end_multi_buffer_row);
21816 let anchor = buffer_snapshot.anchor_after(Point::new(end_multi_buffer_row.0, line_len));
21817
21818 // Use the hunk key we already computed
21819 let hunk_key = new_hunk_key;
21820
21821 // Create the prompt editor for the review input
21822 let prompt_editor = cx.new(|cx| {
21823 let mut editor = Editor::single_line(window, cx);
21824 editor.set_placeholder_text("Add a review comment...", window, cx);
21825 editor
21826 });
21827
21828 // Register the Newline action on the prompt editor to submit the review
21829 let parent_editor = cx.entity().downgrade();
21830 let subscription = prompt_editor.update(cx, |prompt_editor, _cx| {
21831 prompt_editor.register_action({
21832 let parent_editor = parent_editor.clone();
21833 move |_: &crate::actions::Newline, window, cx| {
21834 if let Some(editor) = parent_editor.upgrade() {
21835 editor.update(cx, |editor, cx| {
21836 editor.submit_diff_review_comment(window, cx);
21837 });
21838 }
21839 }
21840 })
21841 });
21842
21843 // Calculate initial height based on existing comments for this hunk
21844 let initial_height = self.calculate_overlay_height(&hunk_key, true, &buffer_snapshot);
21845
21846 // Create the overlay block
21847 let prompt_editor_for_render = prompt_editor.clone();
21848 let hunk_key_for_render = hunk_key.clone();
21849 let editor_handle = cx.entity().downgrade();
21850 let block = BlockProperties {
21851 style: BlockStyle::Sticky,
21852 placement: BlockPlacement::Below(anchor),
21853 height: Some(initial_height),
21854 render: Arc::new(move |cx| {
21855 Self::render_diff_review_overlay(
21856 &prompt_editor_for_render,
21857 &hunk_key_for_render,
21858 &editor_handle,
21859 cx,
21860 )
21861 }),
21862 priority: 0,
21863 };
21864
21865 let block_ids = self.insert_blocks([block], None, cx);
21866 let Some(block_id) = block_ids.into_iter().next() else {
21867 log::error!("Failed to insert diff review overlay block");
21868 return;
21869 };
21870
21871 self.diff_review_overlays.push(DiffReviewOverlay {
21872 anchor_range,
21873 block_id,
21874 prompt_editor: prompt_editor.clone(),
21875 hunk_key,
21876 comments_expanded: true,
21877 inline_edit_editors: HashMap::default(),
21878 inline_edit_subscriptions: HashMap::default(),
21879 user_avatar_uri,
21880 _subscription: subscription,
21881 });
21882
21883 // Focus the prompt editor
21884 let focus_handle = prompt_editor.focus_handle(cx);
21885 window.focus(&focus_handle, cx);
21886
21887 cx.notify();
21888 }
21889
21890 /// Dismisses all diff review overlays.
21891 pub fn dismiss_all_diff_review_overlays(&mut self, cx: &mut Context<Self>) {
21892 if self.diff_review_overlays.is_empty() {
21893 return;
21894 }
21895 let block_ids: HashSet<_> = self
21896 .diff_review_overlays
21897 .drain(..)
21898 .map(|overlay| overlay.block_id)
21899 .collect();
21900 self.remove_blocks(block_ids, None, cx);
21901 cx.notify();
21902 }
21903
21904 /// Dismisses overlays that have no comments stored for their hunks.
21905 /// Keeps overlays that have at least one comment.
21906 fn dismiss_overlays_without_comments(&mut self, cx: &mut Context<Self>) {
21907 let snapshot = self.buffer.read(cx).snapshot(cx);
21908
21909 // First, compute which overlays have comments (to avoid borrow issues with retain)
21910 let overlays_with_comments: Vec<bool> = self
21911 .diff_review_overlays
21912 .iter()
21913 .map(|overlay| self.hunk_comment_count(&overlay.hunk_key, &snapshot) > 0)
21914 .collect();
21915
21916 // Now collect block IDs to remove and retain overlays
21917 let mut block_ids_to_remove = HashSet::default();
21918 let mut index = 0;
21919 self.diff_review_overlays.retain(|overlay| {
21920 let has_comments = overlays_with_comments[index];
21921 index += 1;
21922 if !has_comments {
21923 block_ids_to_remove.insert(overlay.block_id);
21924 }
21925 has_comments
21926 });
21927
21928 if !block_ids_to_remove.is_empty() {
21929 self.remove_blocks(block_ids_to_remove, None, cx);
21930 cx.notify();
21931 }
21932 }
21933
21934 /// Refreshes the diff review overlay block to update its height and render function.
21935 /// Uses resize_blocks and replace_blocks to avoid visual flicker from remove+insert.
21936 fn refresh_diff_review_overlay_height(
21937 &mut self,
21938 hunk_key: &DiffHunkKey,
21939 _window: &mut Window,
21940 cx: &mut Context<Self>,
21941 ) {
21942 // Extract all needed data from overlay first to avoid borrow conflicts
21943 let snapshot = self.buffer.read(cx).snapshot(cx);
21944 let (comments_expanded, block_id, prompt_editor) = {
21945 let Some(overlay) = self
21946 .diff_review_overlays
21947 .iter()
21948 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
21949 else {
21950 return;
21951 };
21952
21953 (
21954 overlay.comments_expanded,
21955 overlay.block_id,
21956 overlay.prompt_editor.clone(),
21957 )
21958 };
21959
21960 // Calculate new height
21961 let snapshot = self.buffer.read(cx).snapshot(cx);
21962 let new_height = self.calculate_overlay_height(hunk_key, comments_expanded, &snapshot);
21963
21964 // Update the block height using resize_blocks (avoids flicker)
21965 let mut heights = HashMap::default();
21966 heights.insert(block_id, new_height);
21967 self.resize_blocks(heights, None, cx);
21968
21969 // Update the render function using replace_blocks (avoids flicker)
21970 let hunk_key_for_render = hunk_key.clone();
21971 let editor_handle = cx.entity().downgrade();
21972 let render: Arc<dyn Fn(&mut BlockContext) -> AnyElement + Send + Sync> =
21973 Arc::new(move |cx| {
21974 Self::render_diff_review_overlay(
21975 &prompt_editor,
21976 &hunk_key_for_render,
21977 &editor_handle,
21978 cx,
21979 )
21980 });
21981
21982 let mut renderers = HashMap::default();
21983 renderers.insert(block_id, render);
21984 self.replace_blocks(renderers, None, cx);
21985 }
21986
21987 /// Action handler for SubmitDiffReviewComment.
21988 pub fn submit_diff_review_comment_action(
21989 &mut self,
21990 _: &SubmitDiffReviewComment,
21991 window: &mut Window,
21992 cx: &mut Context<Self>,
21993 ) {
21994 self.submit_diff_review_comment(window, cx);
21995 }
21996
21997 /// Stores the diff review comment locally.
21998 /// Comments are stored per-hunk and can later be batch-submitted to the Agent panel.
21999 pub fn submit_diff_review_comment(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22000 // Find the overlay that currently has focus
22001 let overlay_index = self
22002 .diff_review_overlays
22003 .iter()
22004 .position(|overlay| overlay.prompt_editor.focus_handle(cx).is_focused(window));
22005 let Some(overlay_index) = overlay_index else {
22006 return;
22007 };
22008 let overlay = &self.diff_review_overlays[overlay_index];
22009
22010 let comment_text = overlay.prompt_editor.read(cx).text(cx).trim().to_string();
22011 if comment_text.is_empty() {
22012 return;
22013 }
22014
22015 let anchor_range = overlay.anchor_range.clone();
22016 let hunk_key = overlay.hunk_key.clone();
22017
22018 self.add_review_comment(hunk_key.clone(), comment_text, anchor_range, cx);
22019
22020 // Clear the prompt editor but keep the overlay open
22021 if let Some(overlay) = self.diff_review_overlays.get(overlay_index) {
22022 overlay.prompt_editor.update(cx, |editor, cx| {
22023 editor.clear(window, cx);
22024 });
22025 }
22026
22027 // Refresh the overlay to update the block height for the new comment
22028 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22029
22030 cx.notify();
22031 }
22032
22033 /// Returns the prompt editor for the diff review overlay, if one is active.
22034 /// This is primarily used for testing.
22035 pub fn diff_review_prompt_editor(&self) -> Option<&Entity<Editor>> {
22036 self.diff_review_overlays
22037 .first()
22038 .map(|overlay| &overlay.prompt_editor)
22039 }
22040
22041 /// Returns the line range for the first diff review overlay, if one is active.
22042 /// Returns (start_row, end_row) as physical line numbers in the underlying file.
22043 pub fn diff_review_line_range(&self, cx: &App) -> Option<(u32, u32)> {
22044 let overlay = self.diff_review_overlays.first()?;
22045 let snapshot = self.buffer.read(cx).snapshot(cx);
22046 let start_point = overlay.anchor_range.start.to_point(&snapshot);
22047 let end_point = overlay.anchor_range.end.to_point(&snapshot);
22048 let start_row = snapshot
22049 .point_to_buffer_point(start_point)
22050 .map(|(_, p, _)| p.row)
22051 .unwrap_or(start_point.row);
22052 let end_row = snapshot
22053 .point_to_buffer_point(end_point)
22054 .map(|(_, p, _)| p.row)
22055 .unwrap_or(end_point.row);
22056 Some((start_row, end_row))
22057 }
22058
22059 /// Sets whether the comments section is expanded in the diff review overlay.
22060 /// This is primarily used for testing.
22061 pub fn set_diff_review_comments_expanded(&mut self, expanded: bool, cx: &mut Context<Self>) {
22062 for overlay in &mut self.diff_review_overlays {
22063 overlay.comments_expanded = expanded;
22064 }
22065 cx.notify();
22066 }
22067
22068 /// Compares two DiffHunkKeys for equality by resolving their anchors.
22069 fn hunk_keys_match(a: &DiffHunkKey, b: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> bool {
22070 a.file_path == b.file_path
22071 && a.hunk_start_anchor.to_point(snapshot) == b.hunk_start_anchor.to_point(snapshot)
22072 }
22073
22074 /// Returns comments for a specific hunk, ordered by creation time.
22075 pub fn comments_for_hunk<'a>(
22076 &'a self,
22077 key: &DiffHunkKey,
22078 snapshot: &MultiBufferSnapshot,
22079 ) -> &'a [StoredReviewComment] {
22080 let key_point = key.hunk_start_anchor.to_point(snapshot);
22081 self.stored_review_comments
22082 .iter()
22083 .find(|(k, _)| {
22084 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
22085 })
22086 .map(|(_, comments)| comments.as_slice())
22087 .unwrap_or(&[])
22088 }
22089
22090 /// Returns the total count of stored review comments across all hunks.
22091 pub fn total_review_comment_count(&self) -> usize {
22092 self.stored_review_comments
22093 .iter()
22094 .map(|(_, v)| v.len())
22095 .sum()
22096 }
22097
22098 /// Returns the count of comments for a specific hunk.
22099 pub fn hunk_comment_count(&self, key: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> usize {
22100 let key_point = key.hunk_start_anchor.to_point(snapshot);
22101 self.stored_review_comments
22102 .iter()
22103 .find(|(k, _)| {
22104 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
22105 })
22106 .map(|(_, v)| v.len())
22107 .unwrap_or(0)
22108 }
22109
22110 /// Adds a new review comment to a specific hunk.
22111 pub fn add_review_comment(
22112 &mut self,
22113 hunk_key: DiffHunkKey,
22114 comment: String,
22115 anchor_range: Range<Anchor>,
22116 cx: &mut Context<Self>,
22117 ) -> usize {
22118 let id = self.next_review_comment_id;
22119 self.next_review_comment_id += 1;
22120
22121 let stored_comment = StoredReviewComment::new(id, comment, anchor_range);
22122
22123 let snapshot = self.buffer.read(cx).snapshot(cx);
22124 let key_point = hunk_key.hunk_start_anchor.to_point(&snapshot);
22125
22126 // Find existing entry for this hunk or add a new one
22127 if let Some((_, comments)) = self.stored_review_comments.iter_mut().find(|(k, _)| {
22128 k.file_path == hunk_key.file_path
22129 && k.hunk_start_anchor.to_point(&snapshot) == key_point
22130 }) {
22131 comments.push(stored_comment);
22132 } else {
22133 self.stored_review_comments
22134 .push((hunk_key, vec![stored_comment]));
22135 }
22136
22137 cx.emit(EditorEvent::ReviewCommentsChanged {
22138 total_count: self.total_review_comment_count(),
22139 });
22140 cx.notify();
22141 id
22142 }
22143
22144 /// Removes a review comment by ID from any hunk.
22145 pub fn remove_review_comment(&mut self, id: usize, cx: &mut Context<Self>) -> bool {
22146 for (_, comments) in self.stored_review_comments.iter_mut() {
22147 if let Some(index) = comments.iter().position(|c| c.id == id) {
22148 comments.remove(index);
22149 cx.emit(EditorEvent::ReviewCommentsChanged {
22150 total_count: self.total_review_comment_count(),
22151 });
22152 cx.notify();
22153 return true;
22154 }
22155 }
22156 false
22157 }
22158
22159 /// Updates a review comment's text by ID.
22160 pub fn update_review_comment(
22161 &mut self,
22162 id: usize,
22163 new_comment: String,
22164 cx: &mut Context<Self>,
22165 ) -> bool {
22166 for (_, comments) in self.stored_review_comments.iter_mut() {
22167 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
22168 comment.comment = new_comment;
22169 comment.is_editing = false;
22170 cx.emit(EditorEvent::ReviewCommentsChanged {
22171 total_count: self.total_review_comment_count(),
22172 });
22173 cx.notify();
22174 return true;
22175 }
22176 }
22177 false
22178 }
22179
22180 /// Sets a comment's editing state.
22181 pub fn set_comment_editing(&mut self, id: usize, is_editing: bool, cx: &mut Context<Self>) {
22182 for (_, comments) in self.stored_review_comments.iter_mut() {
22183 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
22184 comment.is_editing = is_editing;
22185 cx.notify();
22186 return;
22187 }
22188 }
22189 }
22190
22191 /// Takes all stored comments from all hunks, clearing the storage.
22192 /// Returns a Vec of (hunk_key, comments) pairs.
22193 pub fn take_all_review_comments(
22194 &mut self,
22195 cx: &mut Context<Self>,
22196 ) -> Vec<(DiffHunkKey, Vec<StoredReviewComment>)> {
22197 // Dismiss all overlays when taking comments (e.g., when sending to agent)
22198 self.dismiss_all_diff_review_overlays(cx);
22199 let comments = std::mem::take(&mut self.stored_review_comments);
22200 // Reset the ID counter since all comments have been taken
22201 self.next_review_comment_id = 0;
22202 cx.emit(EditorEvent::ReviewCommentsChanged { total_count: 0 });
22203 cx.notify();
22204 comments
22205 }
22206
22207 /// Removes review comments whose anchors are no longer valid or whose
22208 /// associated diff hunks no longer exist.
22209 ///
22210 /// This should be called when the buffer changes to prevent orphaned comments
22211 /// from accumulating.
22212 pub fn cleanup_orphaned_review_comments(&mut self, cx: &mut Context<Self>) {
22213 let snapshot = self.buffer.read(cx).snapshot(cx);
22214 let original_count = self.total_review_comment_count();
22215
22216 // Remove comments with invalid hunk anchors
22217 self.stored_review_comments
22218 .retain(|(hunk_key, _)| hunk_key.hunk_start_anchor.is_valid(&snapshot));
22219
22220 // Also clean up individual comments with invalid anchor ranges
22221 for (_, comments) in &mut self.stored_review_comments {
22222 comments.retain(|comment| {
22223 comment.range.start.is_valid(&snapshot) && comment.range.end.is_valid(&snapshot)
22224 });
22225 }
22226
22227 // Remove empty hunk entries
22228 self.stored_review_comments
22229 .retain(|(_, comments)| !comments.is_empty());
22230
22231 let new_count = self.total_review_comment_count();
22232 if new_count != original_count {
22233 cx.emit(EditorEvent::ReviewCommentsChanged {
22234 total_count: new_count,
22235 });
22236 cx.notify();
22237 }
22238 }
22239
22240 /// Toggles the expanded state of the comments section in the overlay.
22241 pub fn toggle_review_comments_expanded(
22242 &mut self,
22243 _: &ToggleReviewCommentsExpanded,
22244 window: &mut Window,
22245 cx: &mut Context<Self>,
22246 ) {
22247 // Find the overlay that currently has focus, or use the first one
22248 let overlay_info = self.diff_review_overlays.iter_mut().find_map(|overlay| {
22249 if overlay.prompt_editor.focus_handle(cx).is_focused(window) {
22250 overlay.comments_expanded = !overlay.comments_expanded;
22251 Some(overlay.hunk_key.clone())
22252 } else {
22253 None
22254 }
22255 });
22256
22257 // If no focused overlay found, toggle the first one
22258 let hunk_key = overlay_info.or_else(|| {
22259 self.diff_review_overlays.first_mut().map(|overlay| {
22260 overlay.comments_expanded = !overlay.comments_expanded;
22261 overlay.hunk_key.clone()
22262 })
22263 });
22264
22265 if let Some(hunk_key) = hunk_key {
22266 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22267 cx.notify();
22268 }
22269 }
22270
22271 /// Handles the EditReviewComment action - sets a comment into editing mode.
22272 pub fn edit_review_comment(
22273 &mut self,
22274 action: &EditReviewComment,
22275 window: &mut Window,
22276 cx: &mut Context<Self>,
22277 ) {
22278 let comment_id = action.id;
22279
22280 // Set the comment to editing mode
22281 self.set_comment_editing(comment_id, true, cx);
22282
22283 // Find the overlay that contains this comment and create an inline editor if needed
22284 // First, find which hunk this comment belongs to
22285 let hunk_key = self
22286 .stored_review_comments
22287 .iter()
22288 .find_map(|(key, comments)| {
22289 if comments.iter().any(|c| c.id == comment_id) {
22290 Some(key.clone())
22291 } else {
22292 None
22293 }
22294 });
22295
22296 let snapshot = self.buffer.read(cx).snapshot(cx);
22297 if let Some(hunk_key) = hunk_key {
22298 if let Some(overlay) = self
22299 .diff_review_overlays
22300 .iter_mut()
22301 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22302 {
22303 if let std::collections::hash_map::Entry::Vacant(entry) =
22304 overlay.inline_edit_editors.entry(comment_id)
22305 {
22306 // Find the comment text
22307 let comment_text = self
22308 .stored_review_comments
22309 .iter()
22310 .flat_map(|(_, comments)| comments)
22311 .find(|c| c.id == comment_id)
22312 .map(|c| c.comment.clone())
22313 .unwrap_or_default();
22314
22315 // Create inline editor
22316 let parent_editor = cx.entity().downgrade();
22317 let inline_editor = cx.new(|cx| {
22318 let mut editor = Editor::single_line(window, cx);
22319 editor.set_text(&*comment_text, window, cx);
22320 // Select all text for easy replacement
22321 editor.select_all(&crate::actions::SelectAll, window, cx);
22322 editor
22323 });
22324
22325 // Register the Newline action to confirm the edit
22326 let subscription = inline_editor.update(cx, |inline_editor, _cx| {
22327 inline_editor.register_action({
22328 let parent_editor = parent_editor.clone();
22329 move |_: &crate::actions::Newline, window, cx| {
22330 if let Some(editor) = parent_editor.upgrade() {
22331 editor.update(cx, |editor, cx| {
22332 editor.confirm_edit_review_comment(comment_id, window, cx);
22333 });
22334 }
22335 }
22336 })
22337 });
22338
22339 // Store the subscription to keep the action handler alive
22340 overlay
22341 .inline_edit_subscriptions
22342 .insert(comment_id, subscription);
22343
22344 // Focus the inline editor
22345 let focus_handle = inline_editor.focus_handle(cx);
22346 window.focus(&focus_handle, cx);
22347
22348 entry.insert(inline_editor);
22349 }
22350 }
22351 }
22352
22353 cx.notify();
22354 }
22355
22356 /// Confirms an inline edit of a review comment.
22357 pub fn confirm_edit_review_comment(
22358 &mut self,
22359 comment_id: usize,
22360 _window: &mut Window,
22361 cx: &mut Context<Self>,
22362 ) {
22363 // Get the new text from the inline editor
22364 // Find the overlay containing this comment's inline editor
22365 let snapshot = self.buffer.read(cx).snapshot(cx);
22366 let hunk_key = self
22367 .stored_review_comments
22368 .iter()
22369 .find_map(|(key, comments)| {
22370 if comments.iter().any(|c| c.id == comment_id) {
22371 Some(key.clone())
22372 } else {
22373 None
22374 }
22375 });
22376
22377 let new_text = hunk_key
22378 .as_ref()
22379 .and_then(|hunk_key| {
22380 self.diff_review_overlays
22381 .iter()
22382 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
22383 })
22384 .as_ref()
22385 .and_then(|overlay| overlay.inline_edit_editors.get(&comment_id))
22386 .map(|editor| editor.read(cx).text(cx).trim().to_string());
22387
22388 if let Some(new_text) = new_text {
22389 if !new_text.is_empty() {
22390 self.update_review_comment(comment_id, new_text, cx);
22391 }
22392 }
22393
22394 // Remove the inline editor and its subscription
22395 if let Some(hunk_key) = hunk_key {
22396 if let Some(overlay) = self
22397 .diff_review_overlays
22398 .iter_mut()
22399 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22400 {
22401 overlay.inline_edit_editors.remove(&comment_id);
22402 overlay.inline_edit_subscriptions.remove(&comment_id);
22403 }
22404 }
22405
22406 // Clear editing state
22407 self.set_comment_editing(comment_id, false, cx);
22408 }
22409
22410 /// Cancels an inline edit of a review comment.
22411 pub fn cancel_edit_review_comment(
22412 &mut self,
22413 comment_id: usize,
22414 _window: &mut Window,
22415 cx: &mut Context<Self>,
22416 ) {
22417 // Find which hunk this comment belongs to
22418 let hunk_key = self
22419 .stored_review_comments
22420 .iter()
22421 .find_map(|(key, comments)| {
22422 if comments.iter().any(|c| c.id == comment_id) {
22423 Some(key.clone())
22424 } else {
22425 None
22426 }
22427 });
22428
22429 // Remove the inline editor and its subscription
22430 if let Some(hunk_key) = hunk_key {
22431 let snapshot = self.buffer.read(cx).snapshot(cx);
22432 if let Some(overlay) = self
22433 .diff_review_overlays
22434 .iter_mut()
22435 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22436 {
22437 overlay.inline_edit_editors.remove(&comment_id);
22438 overlay.inline_edit_subscriptions.remove(&comment_id);
22439 }
22440 }
22441
22442 // Clear editing state
22443 self.set_comment_editing(comment_id, false, cx);
22444 }
22445
22446 /// Action handler for ConfirmEditReviewComment.
22447 pub fn confirm_edit_review_comment_action(
22448 &mut self,
22449 action: &ConfirmEditReviewComment,
22450 window: &mut Window,
22451 cx: &mut Context<Self>,
22452 ) {
22453 self.confirm_edit_review_comment(action.id, window, cx);
22454 }
22455
22456 /// Action handler for CancelEditReviewComment.
22457 pub fn cancel_edit_review_comment_action(
22458 &mut self,
22459 action: &CancelEditReviewComment,
22460 window: &mut Window,
22461 cx: &mut Context<Self>,
22462 ) {
22463 self.cancel_edit_review_comment(action.id, window, cx);
22464 }
22465
22466 /// Handles the DeleteReviewComment action - removes a comment.
22467 pub fn delete_review_comment(
22468 &mut self,
22469 action: &DeleteReviewComment,
22470 window: &mut Window,
22471 cx: &mut Context<Self>,
22472 ) {
22473 // Get the hunk key before removing the comment
22474 // Find the hunk key from the comment itself
22475 let comment_id = action.id;
22476 let hunk_key = self
22477 .stored_review_comments
22478 .iter()
22479 .find_map(|(key, comments)| {
22480 if comments.iter().any(|c| c.id == comment_id) {
22481 Some(key.clone())
22482 } else {
22483 None
22484 }
22485 });
22486
22487 // Also get it from the overlay for refresh purposes
22488 let overlay_hunk_key = self
22489 .diff_review_overlays
22490 .first()
22491 .map(|o| o.hunk_key.clone());
22492
22493 self.remove_review_comment(action.id, cx);
22494
22495 // Refresh the overlay height after removing a comment
22496 if let Some(hunk_key) = hunk_key.or(overlay_hunk_key) {
22497 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22498 }
22499 }
22500
22501 fn render_diff_review_overlay(
22502 prompt_editor: &Entity<Editor>,
22503 hunk_key: &DiffHunkKey,
22504 editor_handle: &WeakEntity<Editor>,
22505 cx: &mut BlockContext,
22506 ) -> AnyElement {
22507 fn format_line_ranges(ranges: &[(u32, u32)]) -> Option<String> {
22508 if ranges.is_empty() {
22509 return None;
22510 }
22511 let formatted: Vec<String> = ranges
22512 .iter()
22513 .map(|(start, end)| {
22514 let start_line = start + 1;
22515 let end_line = end + 1;
22516 if start_line == end_line {
22517 format!("Line {start_line}")
22518 } else {
22519 format!("Lines {start_line}-{end_line}")
22520 }
22521 })
22522 .collect();
22523 // Don't show label for single line in single excerpt
22524 if ranges.len() == 1 && ranges[0].0 == ranges[0].1 {
22525 return None;
22526 }
22527 Some(formatted.join(" ⋯ "))
22528 }
22529
22530 let theme = cx.theme();
22531 let colors = theme.colors();
22532
22533 let (comments, comments_expanded, inline_editors, user_avatar_uri, line_ranges) =
22534 editor_handle
22535 .upgrade()
22536 .map(|editor| {
22537 let editor = editor.read(cx);
22538 let snapshot = editor.buffer().read(cx).snapshot(cx);
22539 let comments = editor.comments_for_hunk(hunk_key, &snapshot).to_vec();
22540 let (expanded, editors, avatar_uri, line_ranges) = editor
22541 .diff_review_overlays
22542 .iter()
22543 .find(|overlay| {
22544 Editor::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot)
22545 })
22546 .map(|o| {
22547 let start_point = o.anchor_range.start.to_point(&snapshot);
22548 let end_point = o.anchor_range.end.to_point(&snapshot);
22549 // Get line ranges per excerpt to detect discontinuities
22550 let buffer_ranges =
22551 snapshot.range_to_buffer_ranges(start_point..end_point);
22552 let ranges: Vec<(u32, u32)> = buffer_ranges
22553 .iter()
22554 .map(|(buffer, range, _)| {
22555 let start = buffer.offset_to_point(range.start.0).row;
22556 let end = buffer.offset_to_point(range.end.0).row;
22557 (start, end)
22558 })
22559 .collect();
22560 (
22561 o.comments_expanded,
22562 o.inline_edit_editors.clone(),
22563 o.user_avatar_uri.clone(),
22564 if ranges.is_empty() {
22565 None
22566 } else {
22567 Some(ranges)
22568 },
22569 )
22570 })
22571 .unwrap_or((true, HashMap::default(), None, None));
22572 (comments, expanded, editors, avatar_uri, line_ranges)
22573 })
22574 .unwrap_or((Vec::new(), true, HashMap::default(), None, None));
22575
22576 let comment_count = comments.len();
22577 let avatar_size = px(20.);
22578 let action_icon_size = IconSize::XSmall;
22579
22580 v_flex()
22581 .w_full()
22582 .bg(colors.editor_background)
22583 .border_b_1()
22584 .border_color(colors.border)
22585 .px_2()
22586 .pb_2()
22587 .gap_2()
22588 // Line range indicator (only shown for multi-line selections or multiple excerpts)
22589 .when_some(line_ranges, |el, ranges| {
22590 let label = format_line_ranges(&ranges);
22591 if let Some(label) = label {
22592 el.child(
22593 h_flex()
22594 .w_full()
22595 .px_2()
22596 .child(Label::new(label).size(LabelSize::Small).color(Color::Muted)),
22597 )
22598 } else {
22599 el
22600 }
22601 })
22602 // Top row: editable input with user's avatar
22603 .child(
22604 h_flex()
22605 .w_full()
22606 .items_center()
22607 .gap_2()
22608 .px_2()
22609 .py_1p5()
22610 .rounded_md()
22611 .bg(colors.surface_background)
22612 .child(
22613 div()
22614 .size(avatar_size)
22615 .flex_shrink_0()
22616 .rounded_full()
22617 .overflow_hidden()
22618 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22619 Avatar::new(avatar_uri.clone())
22620 .size(avatar_size)
22621 .into_any_element()
22622 } else {
22623 Icon::new(IconName::Person)
22624 .size(IconSize::Small)
22625 .color(ui::Color::Muted)
22626 .into_any_element()
22627 }),
22628 )
22629 .child(
22630 div()
22631 .flex_1()
22632 .border_1()
22633 .border_color(colors.border)
22634 .rounded_md()
22635 .bg(colors.editor_background)
22636 .px_2()
22637 .py_1()
22638 .child(prompt_editor.clone()),
22639 )
22640 .child(
22641 h_flex()
22642 .flex_shrink_0()
22643 .gap_1()
22644 .child(
22645 IconButton::new("diff-review-close", IconName::Close)
22646 .icon_color(ui::Color::Muted)
22647 .icon_size(action_icon_size)
22648 .tooltip(Tooltip::text("Close"))
22649 .on_click(|_, window, cx| {
22650 window
22651 .dispatch_action(Box::new(crate::actions::Cancel), cx);
22652 }),
22653 )
22654 .child(
22655 IconButton::new("diff-review-add", IconName::Return)
22656 .icon_color(ui::Color::Muted)
22657 .icon_size(action_icon_size)
22658 .tooltip(Tooltip::text("Add comment"))
22659 .on_click(|_, window, cx| {
22660 window.dispatch_action(
22661 Box::new(crate::actions::SubmitDiffReviewComment),
22662 cx,
22663 );
22664 }),
22665 ),
22666 ),
22667 )
22668 // Expandable comments section (only shown when there are comments)
22669 .when(comment_count > 0, |el| {
22670 el.child(Self::render_comments_section(
22671 comments,
22672 comments_expanded,
22673 inline_editors,
22674 user_avatar_uri,
22675 avatar_size,
22676 action_icon_size,
22677 colors,
22678 ))
22679 })
22680 .into_any_element()
22681 }
22682
22683 fn render_comments_section(
22684 comments: Vec<StoredReviewComment>,
22685 expanded: bool,
22686 inline_editors: HashMap<usize, Entity<Editor>>,
22687 user_avatar_uri: Option<SharedUri>,
22688 avatar_size: Pixels,
22689 action_icon_size: IconSize,
22690 colors: &theme::ThemeColors,
22691 ) -> impl IntoElement {
22692 let comment_count = comments.len();
22693
22694 v_flex()
22695 .w_full()
22696 .gap_1()
22697 // Header with expand/collapse toggle
22698 .child(
22699 h_flex()
22700 .id("review-comments-header")
22701 .w_full()
22702 .items_center()
22703 .gap_1()
22704 .px_2()
22705 .py_1()
22706 .cursor_pointer()
22707 .rounded_md()
22708 .hover(|style| style.bg(colors.ghost_element_hover))
22709 .on_click(|_, window: &mut Window, cx| {
22710 window.dispatch_action(
22711 Box::new(crate::actions::ToggleReviewCommentsExpanded),
22712 cx,
22713 );
22714 })
22715 .child(
22716 Icon::new(if expanded {
22717 IconName::ChevronDown
22718 } else {
22719 IconName::ChevronRight
22720 })
22721 .size(IconSize::Small)
22722 .color(ui::Color::Muted),
22723 )
22724 .child(
22725 Label::new(format!(
22726 "{} Comment{}",
22727 comment_count,
22728 if comment_count == 1 { "" } else { "s" }
22729 ))
22730 .size(LabelSize::Small)
22731 .color(Color::Muted),
22732 ),
22733 )
22734 // Comments list (when expanded)
22735 .when(expanded, |el| {
22736 el.children(comments.into_iter().map(|comment| {
22737 let inline_editor = inline_editors.get(&comment.id).cloned();
22738 Self::render_comment_row(
22739 comment,
22740 inline_editor,
22741 user_avatar_uri.clone(),
22742 avatar_size,
22743 action_icon_size,
22744 colors,
22745 )
22746 }))
22747 })
22748 }
22749
22750 fn render_comment_row(
22751 comment: StoredReviewComment,
22752 inline_editor: Option<Entity<Editor>>,
22753 user_avatar_uri: Option<SharedUri>,
22754 avatar_size: Pixels,
22755 action_icon_size: IconSize,
22756 colors: &theme::ThemeColors,
22757 ) -> impl IntoElement {
22758 let comment_id = comment.id;
22759 let is_editing = inline_editor.is_some();
22760
22761 h_flex()
22762 .w_full()
22763 .items_center()
22764 .gap_2()
22765 .px_2()
22766 .py_1p5()
22767 .rounded_md()
22768 .bg(colors.surface_background)
22769 .child(
22770 div()
22771 .size(avatar_size)
22772 .flex_shrink_0()
22773 .rounded_full()
22774 .overflow_hidden()
22775 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22776 Avatar::new(avatar_uri.clone())
22777 .size(avatar_size)
22778 .into_any_element()
22779 } else {
22780 Icon::new(IconName::Person)
22781 .size(IconSize::Small)
22782 .color(ui::Color::Muted)
22783 .into_any_element()
22784 }),
22785 )
22786 .child(if let Some(editor) = inline_editor {
22787 // Inline edit mode: show an editable text field
22788 div()
22789 .flex_1()
22790 .border_1()
22791 .border_color(colors.border)
22792 .rounded_md()
22793 .bg(colors.editor_background)
22794 .px_2()
22795 .py_1()
22796 .child(editor)
22797 .into_any_element()
22798 } else {
22799 // Display mode: show the comment text
22800 div()
22801 .flex_1()
22802 .text_sm()
22803 .text_color(colors.text)
22804 .child(comment.comment)
22805 .into_any_element()
22806 })
22807 .child(if is_editing {
22808 // Editing mode: show close and confirm buttons
22809 h_flex()
22810 .gap_1()
22811 .child(
22812 IconButton::new(
22813 format!("diff-review-cancel-edit-{comment_id}"),
22814 IconName::Close,
22815 )
22816 .icon_color(ui::Color::Muted)
22817 .icon_size(action_icon_size)
22818 .tooltip(Tooltip::text("Cancel"))
22819 .on_click(move |_, window, cx| {
22820 window.dispatch_action(
22821 Box::new(crate::actions::CancelEditReviewComment {
22822 id: comment_id,
22823 }),
22824 cx,
22825 );
22826 }),
22827 )
22828 .child(
22829 IconButton::new(
22830 format!("diff-review-confirm-edit-{comment_id}"),
22831 IconName::Return,
22832 )
22833 .icon_color(ui::Color::Muted)
22834 .icon_size(action_icon_size)
22835 .tooltip(Tooltip::text("Confirm"))
22836 .on_click(move |_, window, cx| {
22837 window.dispatch_action(
22838 Box::new(crate::actions::ConfirmEditReviewComment {
22839 id: comment_id,
22840 }),
22841 cx,
22842 );
22843 }),
22844 )
22845 .into_any_element()
22846 } else {
22847 // Display mode: no action buttons for now (edit/delete not yet implemented)
22848 gpui::Empty.into_any_element()
22849 })
22850 }
22851
22852 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
22853 if self.display_map.read(cx).masked != masked {
22854 self.display_map.update(cx, |map, _| map.masked = masked);
22855 }
22856 cx.notify()
22857 }
22858
22859 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
22860 self.show_wrap_guides = Some(show_wrap_guides);
22861 cx.notify();
22862 }
22863
22864 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
22865 self.show_indent_guides = Some(show_indent_guides);
22866 cx.notify();
22867 }
22868
22869 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
22870 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
22871 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
22872 && let Some(dir) = file.abs_path(cx).parent()
22873 {
22874 return Some(dir.to_owned());
22875 }
22876 }
22877
22878 None
22879 }
22880
22881 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
22882 self.active_excerpt(cx)?
22883 .1
22884 .read(cx)
22885 .file()
22886 .and_then(|f| f.as_local())
22887 }
22888
22889 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
22890 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22891 let buffer = buffer.read(cx);
22892 if let Some(project_path) = buffer.project_path(cx) {
22893 let project = self.project()?.read(cx);
22894 project.absolute_path(&project_path, cx)
22895 } else {
22896 buffer
22897 .file()
22898 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
22899 }
22900 })
22901 }
22902
22903 pub fn reveal_in_finder(
22904 &mut self,
22905 _: &RevealInFileManager,
22906 _window: &mut Window,
22907 cx: &mut Context<Self>,
22908 ) {
22909 if let Some(path) = self.target_file_abs_path(cx) {
22910 if let Some(project) = self.project() {
22911 project.update(cx, |project, cx| project.reveal_path(&path, cx));
22912 } else {
22913 cx.reveal_path(&path);
22914 }
22915 }
22916 }
22917
22918 pub fn copy_path(
22919 &mut self,
22920 _: &zed_actions::workspace::CopyPath,
22921 _window: &mut Window,
22922 cx: &mut Context<Self>,
22923 ) {
22924 if let Some(path) = self.target_file_abs_path(cx)
22925 && let Some(path) = path.to_str()
22926 {
22927 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22928 } else {
22929 cx.propagate();
22930 }
22931 }
22932
22933 pub fn copy_relative_path(
22934 &mut self,
22935 _: &zed_actions::workspace::CopyRelativePath,
22936 _window: &mut Window,
22937 cx: &mut Context<Self>,
22938 ) {
22939 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22940 let project = self.project()?.read(cx);
22941 let path = buffer.read(cx).file()?.path();
22942 let path = path.display(project.path_style(cx));
22943 Some(path)
22944 }) {
22945 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22946 } else {
22947 cx.propagate();
22948 }
22949 }
22950
22951 /// Returns the project path for the editor's buffer, if any buffer is
22952 /// opened in the editor.
22953 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
22954 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
22955 buffer.read(cx).project_path(cx)
22956 } else {
22957 None
22958 }
22959 }
22960
22961 // Returns true if the editor handled a go-to-line request
22962 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
22963 maybe!({
22964 let breakpoint_store = self.breakpoint_store.as_ref()?;
22965
22966 let (active_stack_frame, debug_line_pane_id) = {
22967 let store = breakpoint_store.read(cx);
22968 let active_stack_frame = store.active_position().cloned();
22969 let debug_line_pane_id = store.active_debug_line_pane_id();
22970 (active_stack_frame, debug_line_pane_id)
22971 };
22972
22973 let Some(active_stack_frame) = active_stack_frame else {
22974 self.clear_row_highlights::<ActiveDebugLine>();
22975 return None;
22976 };
22977
22978 if let Some(debug_line_pane_id) = debug_line_pane_id {
22979 if let Some(workspace) = self
22980 .workspace
22981 .as_ref()
22982 .and_then(|(workspace, _)| workspace.upgrade())
22983 {
22984 let editor_pane_id = workspace
22985 .read(cx)
22986 .pane_for_item_id(cx.entity_id())
22987 .map(|pane| pane.entity_id());
22988
22989 if editor_pane_id.is_some_and(|id| id != debug_line_pane_id) {
22990 self.clear_row_highlights::<ActiveDebugLine>();
22991 return None;
22992 }
22993 }
22994 }
22995
22996 let position = active_stack_frame.position;
22997 let buffer_id = position.buffer_id?;
22998 let snapshot = self
22999 .project
23000 .as_ref()?
23001 .read(cx)
23002 .buffer_for_id(buffer_id, cx)?
23003 .read(cx)
23004 .snapshot();
23005
23006 let mut handled = false;
23007 for (id, _, ExcerptRange { context, .. }) in
23008 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
23009 {
23010 if context.start.cmp(&position, &snapshot).is_ge()
23011 || context.end.cmp(&position, &snapshot).is_lt()
23012 {
23013 continue;
23014 }
23015 let snapshot = self.buffer.read(cx).snapshot(cx);
23016 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
23017
23018 handled = true;
23019 self.clear_row_highlights::<ActiveDebugLine>();
23020
23021 self.go_to_line::<ActiveDebugLine>(
23022 multibuffer_anchor,
23023 Some(cx.theme().colors().editor_debugger_active_line_background),
23024 window,
23025 cx,
23026 );
23027
23028 cx.notify();
23029 }
23030
23031 handled.then_some(())
23032 })
23033 .is_some()
23034 }
23035
23036 pub fn copy_file_name_without_extension(
23037 &mut self,
23038 _: &CopyFileNameWithoutExtension,
23039 _: &mut Window,
23040 cx: &mut Context<Self>,
23041 ) {
23042 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
23043 let file = buffer.read(cx).file()?;
23044 file.path().file_stem()
23045 }) {
23046 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
23047 }
23048 }
23049
23050 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
23051 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
23052 let file = buffer.read(cx).file()?;
23053 Some(file.file_name(cx))
23054 }) {
23055 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
23056 }
23057 }
23058
23059 pub fn toggle_git_blame(
23060 &mut self,
23061 _: &::git::Blame,
23062 window: &mut Window,
23063 cx: &mut Context<Self>,
23064 ) {
23065 self.show_git_blame_gutter = !self.show_git_blame_gutter;
23066
23067 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
23068 self.start_git_blame(true, window, cx);
23069 }
23070
23071 cx.notify();
23072 }
23073
23074 pub fn toggle_git_blame_inline(
23075 &mut self,
23076 _: &ToggleGitBlameInline,
23077 window: &mut Window,
23078 cx: &mut Context<Self>,
23079 ) {
23080 self.toggle_git_blame_inline_internal(true, window, cx);
23081 cx.notify();
23082 }
23083
23084 pub fn open_git_blame_commit(
23085 &mut self,
23086 _: &OpenGitBlameCommit,
23087 window: &mut Window,
23088 cx: &mut Context<Self>,
23089 ) {
23090 self.open_git_blame_commit_internal(window, cx);
23091 }
23092
23093 fn open_git_blame_commit_internal(
23094 &mut self,
23095 window: &mut Window,
23096 cx: &mut Context<Self>,
23097 ) -> Option<()> {
23098 let blame = self.blame.as_ref()?;
23099 let snapshot = self.snapshot(window, cx);
23100 let cursor = self
23101 .selections
23102 .newest::<Point>(&snapshot.display_snapshot)
23103 .head();
23104 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
23105 let (_, blame_entry) = blame
23106 .update(cx, |blame, cx| {
23107 blame
23108 .blame_for_rows(
23109 &[RowInfo {
23110 buffer_id: Some(buffer.remote_id()),
23111 buffer_row: Some(point.row),
23112 ..Default::default()
23113 }],
23114 cx,
23115 )
23116 .next()
23117 })
23118 .flatten()?;
23119 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23120 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
23121 let workspace = self.workspace()?.downgrade();
23122 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
23123 None
23124 }
23125
23126 pub fn git_blame_inline_enabled(&self) -> bool {
23127 self.git_blame_inline_enabled
23128 }
23129
23130 pub fn toggle_selection_menu(
23131 &mut self,
23132 _: &ToggleSelectionMenu,
23133 _: &mut Window,
23134 cx: &mut Context<Self>,
23135 ) {
23136 self.show_selection_menu = self
23137 .show_selection_menu
23138 .map(|show_selections_menu| !show_selections_menu)
23139 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
23140
23141 cx.notify();
23142 }
23143
23144 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
23145 self.show_selection_menu
23146 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
23147 }
23148
23149 fn start_git_blame(
23150 &mut self,
23151 user_triggered: bool,
23152 window: &mut Window,
23153 cx: &mut Context<Self>,
23154 ) {
23155 if let Some(project) = self.project() {
23156 if let Some(buffer) = self.buffer().read(cx).as_singleton()
23157 && buffer.read(cx).file().is_none()
23158 {
23159 return;
23160 }
23161
23162 let focused = self.focus_handle(cx).contains_focused(window, cx);
23163
23164 let project = project.clone();
23165 let blame = cx
23166 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
23167 self.blame_subscription =
23168 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
23169 self.blame = Some(blame);
23170 }
23171 }
23172
23173 fn toggle_git_blame_inline_internal(
23174 &mut self,
23175 user_triggered: bool,
23176 window: &mut Window,
23177 cx: &mut Context<Self>,
23178 ) {
23179 if self.git_blame_inline_enabled {
23180 self.git_blame_inline_enabled = false;
23181 self.show_git_blame_inline = false;
23182 self.show_git_blame_inline_delay_task.take();
23183 } else {
23184 self.git_blame_inline_enabled = true;
23185 self.start_git_blame_inline(user_triggered, window, cx);
23186 }
23187
23188 cx.notify();
23189 }
23190
23191 fn start_git_blame_inline(
23192 &mut self,
23193 user_triggered: bool,
23194 window: &mut Window,
23195 cx: &mut Context<Self>,
23196 ) {
23197 self.start_git_blame(user_triggered, window, cx);
23198
23199 if ProjectSettings::get_global(cx)
23200 .git
23201 .inline_blame_delay()
23202 .is_some()
23203 {
23204 self.start_inline_blame_timer(window, cx);
23205 } else {
23206 self.show_git_blame_inline = true
23207 }
23208 }
23209
23210 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
23211 self.blame.as_ref()
23212 }
23213
23214 pub fn show_git_blame_gutter(&self) -> bool {
23215 self.show_git_blame_gutter
23216 }
23217
23218 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
23219 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
23220 }
23221
23222 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
23223 self.show_git_blame_inline
23224 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
23225 && !self.newest_selection_head_on_empty_line(cx)
23226 && self.has_blame_entries(cx)
23227 }
23228
23229 fn has_blame_entries(&self, cx: &App) -> bool {
23230 self.blame()
23231 .is_some_and(|blame| blame.read(cx).has_generated_entries())
23232 }
23233
23234 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
23235 let cursor_anchor = self.selections.newest_anchor().head();
23236
23237 let snapshot = self.buffer.read(cx).snapshot(cx);
23238 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
23239
23240 snapshot.line_len(buffer_row) == 0
23241 }
23242
23243 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
23244 let buffer_and_selection = maybe!({
23245 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
23246 let selection_range = selection.range();
23247
23248 let multi_buffer = self.buffer().read(cx);
23249 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
23250 let buffer_ranges = multi_buffer_snapshot
23251 .range_to_buffer_ranges(selection_range.start..=selection_range.end);
23252
23253 let (buffer, range, _) = if selection.reversed {
23254 buffer_ranges.first()
23255 } else {
23256 buffer_ranges.last()
23257 }?;
23258
23259 let buffer_range = range.to_point(buffer);
23260
23261 let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else {
23262 return Some((
23263 multi_buffer.buffer(buffer.remote_id()).unwrap(),
23264 buffer_range.start.row..buffer_range.end.row,
23265 ));
23266 };
23267
23268 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
23269 let start =
23270 buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.start, buffer);
23271 let end =
23272 buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.end, buffer);
23273
23274 Some((
23275 multi_buffer.buffer(buffer.remote_id()).unwrap(),
23276 start.row..end.row,
23277 ))
23278 });
23279
23280 let Some((buffer, selection)) = buffer_and_selection else {
23281 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
23282 };
23283
23284 let Some(project) = self.project() else {
23285 return Task::ready(Err(anyhow!("editor does not have project")));
23286 };
23287
23288 project.update(cx, |project, cx| {
23289 project.get_permalink_to_line(&buffer, selection, cx)
23290 })
23291 }
23292
23293 pub fn copy_permalink_to_line(
23294 &mut self,
23295 _: &CopyPermalinkToLine,
23296 window: &mut Window,
23297 cx: &mut Context<Self>,
23298 ) {
23299 let permalink_task = self.get_permalink_to_line(cx);
23300 let workspace = self.workspace();
23301
23302 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
23303 Ok(permalink) => {
23304 cx.update(|_, cx| {
23305 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
23306 })
23307 .ok();
23308 }
23309 Err(err) => {
23310 let message = format!("Failed to copy permalink: {err}");
23311
23312 anyhow::Result::<()>::Err(err).log_err();
23313
23314 if let Some(workspace) = workspace {
23315 workspace
23316 .update_in(cx, |workspace, _, cx| {
23317 struct CopyPermalinkToLine;
23318
23319 workspace.show_toast(
23320 Toast::new(
23321 NotificationId::unique::<CopyPermalinkToLine>(),
23322 message,
23323 ),
23324 cx,
23325 )
23326 })
23327 .ok();
23328 }
23329 }
23330 })
23331 .detach();
23332 }
23333
23334 pub fn copy_file_location(
23335 &mut self,
23336 _: &CopyFileLocation,
23337 _: &mut Window,
23338 cx: &mut Context<Self>,
23339 ) {
23340 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
23341
23342 let start_line = selection.start.row + 1;
23343 let end_line = selection.end.row + 1;
23344
23345 let end_line = if selection.end.column == 0 && end_line > start_line {
23346 end_line - 1
23347 } else {
23348 end_line
23349 };
23350
23351 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
23352 let project = self.project()?.read(cx);
23353 let file = buffer.read(cx).file()?;
23354 let path = file.path().display(project.path_style(cx));
23355
23356 let location = if start_line == end_line {
23357 format!("{path}:{start_line}")
23358 } else {
23359 format!("{path}:{start_line}-{end_line}")
23360 };
23361 Some(location)
23362 }) {
23363 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
23364 }
23365 }
23366
23367 pub fn open_permalink_to_line(
23368 &mut self,
23369 _: &OpenPermalinkToLine,
23370 window: &mut Window,
23371 cx: &mut Context<Self>,
23372 ) {
23373 let permalink_task = self.get_permalink_to_line(cx);
23374 let workspace = self.workspace();
23375
23376 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
23377 Ok(permalink) => {
23378 cx.update(|_, cx| {
23379 cx.open_url(permalink.as_ref());
23380 })
23381 .ok();
23382 }
23383 Err(err) => {
23384 let message = format!("Failed to open permalink: {err}");
23385
23386 anyhow::Result::<()>::Err(err).log_err();
23387
23388 if let Some(workspace) = workspace {
23389 workspace.update(cx, |workspace, cx| {
23390 struct OpenPermalinkToLine;
23391
23392 workspace.show_toast(
23393 Toast::new(NotificationId::unique::<OpenPermalinkToLine>(), message),
23394 cx,
23395 )
23396 });
23397 }
23398 }
23399 })
23400 .detach();
23401 }
23402
23403 pub fn insert_uuid_v4(
23404 &mut self,
23405 _: &InsertUuidV4,
23406 window: &mut Window,
23407 cx: &mut Context<Self>,
23408 ) {
23409 self.insert_uuid(UuidVersion::V4, window, cx);
23410 }
23411
23412 pub fn insert_uuid_v7(
23413 &mut self,
23414 _: &InsertUuidV7,
23415 window: &mut Window,
23416 cx: &mut Context<Self>,
23417 ) {
23418 self.insert_uuid(UuidVersion::V7, window, cx);
23419 }
23420
23421 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
23422 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
23423 self.transact(window, cx, |this, window, cx| {
23424 let edits = this
23425 .selections
23426 .all::<Point>(&this.display_snapshot(cx))
23427 .into_iter()
23428 .map(|selection| {
23429 let uuid = match version {
23430 UuidVersion::V4 => uuid::Uuid::new_v4(),
23431 UuidVersion::V7 => uuid::Uuid::now_v7(),
23432 };
23433
23434 (selection.range(), uuid.to_string())
23435 });
23436 this.edit(edits, cx);
23437 this.refresh_edit_prediction(true, false, window, cx);
23438 });
23439 }
23440
23441 pub fn open_selections_in_multibuffer(
23442 &mut self,
23443 _: &OpenSelectionsInMultibuffer,
23444 window: &mut Window,
23445 cx: &mut Context<Self>,
23446 ) {
23447 let multibuffer = self.buffer.read(cx);
23448
23449 let Some(buffer) = multibuffer.as_singleton() else {
23450 return;
23451 };
23452
23453 let Some(workspace) = self.workspace() else {
23454 return;
23455 };
23456
23457 let title = multibuffer.title(cx).to_string();
23458
23459 let locations = self
23460 .selections
23461 .all_anchors(&self.display_snapshot(cx))
23462 .iter()
23463 .map(|selection| {
23464 (
23465 buffer.clone(),
23466 (selection.start.text_anchor..selection.end.text_anchor)
23467 .to_point(buffer.read(cx)),
23468 )
23469 })
23470 .into_group_map();
23471
23472 cx.spawn_in(window, async move |_, cx| {
23473 workspace.update_in(cx, |workspace, window, cx| {
23474 Self::open_locations_in_multibuffer(
23475 workspace,
23476 locations,
23477 format!("Selections for '{title}'"),
23478 false,
23479 false,
23480 MultibufferSelectionMode::All,
23481 window,
23482 cx,
23483 );
23484 })
23485 })
23486 .detach();
23487 }
23488
23489 /// Adds a row highlight for the given range. If a row has multiple highlights, the
23490 /// last highlight added will be used.
23491 ///
23492 /// If the range ends at the beginning of a line, then that line will not be highlighted.
23493 pub fn highlight_rows<T: 'static>(
23494 &mut self,
23495 range: Range<Anchor>,
23496 color: Hsla,
23497 options: RowHighlightOptions,
23498 cx: &mut Context<Self>,
23499 ) {
23500 let snapshot = self.buffer().read(cx).snapshot(cx);
23501 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23502 let ix = row_highlights.binary_search_by(|highlight| {
23503 Ordering::Equal
23504 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
23505 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
23506 });
23507
23508 if let Err(mut ix) = ix {
23509 let index = post_inc(&mut self.highlight_order);
23510
23511 // If this range intersects with the preceding highlight, then merge it with
23512 // the preceding highlight. Otherwise insert a new highlight.
23513 let mut merged = false;
23514 if ix > 0 {
23515 let prev_highlight = &mut row_highlights[ix - 1];
23516 if prev_highlight
23517 .range
23518 .end
23519 .cmp(&range.start, &snapshot)
23520 .is_ge()
23521 {
23522 ix -= 1;
23523 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
23524 prev_highlight.range.end = range.end;
23525 }
23526 merged = true;
23527 prev_highlight.index = index;
23528 prev_highlight.color = color;
23529 prev_highlight.options = options;
23530 }
23531 }
23532
23533 if !merged {
23534 row_highlights.insert(
23535 ix,
23536 RowHighlight {
23537 range,
23538 index,
23539 color,
23540 options,
23541 type_id: TypeId::of::<T>(),
23542 },
23543 );
23544 }
23545
23546 // If any of the following highlights intersect with this one, merge them.
23547 while let Some(next_highlight) = row_highlights.get(ix + 1) {
23548 let highlight = &row_highlights[ix];
23549 if next_highlight
23550 .range
23551 .start
23552 .cmp(&highlight.range.end, &snapshot)
23553 .is_le()
23554 {
23555 if next_highlight
23556 .range
23557 .end
23558 .cmp(&highlight.range.end, &snapshot)
23559 .is_gt()
23560 {
23561 row_highlights[ix].range.end = next_highlight.range.end;
23562 }
23563 row_highlights.remove(ix + 1);
23564 } else {
23565 break;
23566 }
23567 }
23568 }
23569 }
23570
23571 /// Remove any highlighted row ranges of the given type that intersect the
23572 /// given ranges.
23573 pub fn remove_highlighted_rows<T: 'static>(
23574 &mut self,
23575 ranges_to_remove: Vec<Range<Anchor>>,
23576 cx: &mut Context<Self>,
23577 ) {
23578 let snapshot = self.buffer().read(cx).snapshot(cx);
23579 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23580 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23581 row_highlights.retain(|highlight| {
23582 while let Some(range_to_remove) = ranges_to_remove.peek() {
23583 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
23584 Ordering::Less | Ordering::Equal => {
23585 ranges_to_remove.next();
23586 }
23587 Ordering::Greater => {
23588 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
23589 Ordering::Less | Ordering::Equal => {
23590 return false;
23591 }
23592 Ordering::Greater => break,
23593 }
23594 }
23595 }
23596 }
23597
23598 true
23599 })
23600 }
23601
23602 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
23603 pub fn clear_row_highlights<T: 'static>(&mut self) {
23604 self.highlighted_rows.remove(&TypeId::of::<T>());
23605 }
23606
23607 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
23608 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
23609 self.highlighted_rows
23610 .get(&TypeId::of::<T>())
23611 .map_or(&[] as &[_], |vec| vec.as_slice())
23612 .iter()
23613 .map(|highlight| (highlight.range.clone(), highlight.color))
23614 }
23615
23616 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
23617 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
23618 /// Allows to ignore certain kinds of highlights.
23619 pub fn highlighted_display_rows(
23620 &self,
23621 window: &mut Window,
23622 cx: &mut App,
23623 ) -> BTreeMap<DisplayRow, LineHighlight> {
23624 let snapshot = self.snapshot(window, cx);
23625 let mut used_highlight_orders = HashMap::default();
23626 self.highlighted_rows
23627 .iter()
23628 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
23629 .fold(
23630 BTreeMap::<DisplayRow, LineHighlight>::new(),
23631 |mut unique_rows, highlight| {
23632 let start = highlight.range.start.to_display_point(&snapshot);
23633 let end = highlight.range.end.to_display_point(&snapshot);
23634 let start_row = start.row().0;
23635 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
23636 {
23637 end.row().0.saturating_sub(1)
23638 } else {
23639 end.row().0
23640 };
23641 for row in start_row..=end_row {
23642 let used_index =
23643 used_highlight_orders.entry(row).or_insert(highlight.index);
23644 if highlight.index >= *used_index {
23645 *used_index = highlight.index;
23646 unique_rows.insert(
23647 DisplayRow(row),
23648 LineHighlight {
23649 include_gutter: highlight.options.include_gutter,
23650 border: None,
23651 background: highlight.color.into(),
23652 type_id: Some(highlight.type_id),
23653 },
23654 );
23655 }
23656 }
23657 unique_rows
23658 },
23659 )
23660 }
23661
23662 pub fn highlighted_display_row_for_autoscroll(
23663 &self,
23664 snapshot: &DisplaySnapshot,
23665 ) -> Option<DisplayRow> {
23666 self.highlighted_rows
23667 .values()
23668 .flat_map(|highlighted_rows| highlighted_rows.iter())
23669 .filter_map(|highlight| {
23670 if highlight.options.autoscroll {
23671 Some(highlight.range.start.to_display_point(snapshot).row())
23672 } else {
23673 None
23674 }
23675 })
23676 .min()
23677 }
23678
23679 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
23680 self.highlight_background(
23681 HighlightKey::SearchWithinRange,
23682 ranges,
23683 |_, colors| colors.colors().editor_document_highlight_read_background,
23684 cx,
23685 )
23686 }
23687
23688 pub fn set_breadcrumb_header(&mut self, new_header: String) {
23689 self.breadcrumb_header = Some(new_header);
23690 }
23691
23692 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
23693 self.clear_background_highlights(HighlightKey::SearchWithinRange, cx);
23694 }
23695
23696 pub fn highlight_background(
23697 &mut self,
23698 key: HighlightKey,
23699 ranges: &[Range<Anchor>],
23700 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
23701 cx: &mut Context<Self>,
23702 ) {
23703 self.background_highlights
23704 .insert(key, (Arc::new(color_fetcher), Arc::from(ranges)));
23705 self.scrollbar_marker_state.dirty = true;
23706 cx.notify();
23707 }
23708
23709 pub fn clear_background_highlights(
23710 &mut self,
23711 key: HighlightKey,
23712 cx: &mut Context<Self>,
23713 ) -> Option<BackgroundHighlight> {
23714 let text_highlights = self.background_highlights.remove(&key)?;
23715 if !text_highlights.1.is_empty() {
23716 self.scrollbar_marker_state.dirty = true;
23717 cx.notify();
23718 }
23719 Some(text_highlights)
23720 }
23721
23722 pub fn highlight_gutter<T: 'static>(
23723 &mut self,
23724 ranges: impl Into<Vec<Range<Anchor>>>,
23725 color_fetcher: fn(&App) -> Hsla,
23726 cx: &mut Context<Self>,
23727 ) {
23728 self.gutter_highlights
23729 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
23730 cx.notify();
23731 }
23732
23733 pub fn clear_gutter_highlights<T: 'static>(
23734 &mut self,
23735 cx: &mut Context<Self>,
23736 ) -> Option<GutterHighlight> {
23737 cx.notify();
23738 self.gutter_highlights.remove(&TypeId::of::<T>())
23739 }
23740
23741 pub fn insert_gutter_highlight<T: 'static>(
23742 &mut self,
23743 range: Range<Anchor>,
23744 color_fetcher: fn(&App) -> Hsla,
23745 cx: &mut Context<Self>,
23746 ) {
23747 let snapshot = self.buffer().read(cx).snapshot(cx);
23748 let mut highlights = self
23749 .gutter_highlights
23750 .remove(&TypeId::of::<T>())
23751 .map(|(_, highlights)| highlights)
23752 .unwrap_or_default();
23753 let ix = highlights.binary_search_by(|highlight| {
23754 Ordering::Equal
23755 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
23756 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
23757 });
23758 if let Err(ix) = ix {
23759 highlights.insert(ix, range);
23760 }
23761 self.gutter_highlights
23762 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
23763 }
23764
23765 pub fn remove_gutter_highlights<T: 'static>(
23766 &mut self,
23767 ranges_to_remove: Vec<Range<Anchor>>,
23768 cx: &mut Context<Self>,
23769 ) {
23770 let snapshot = self.buffer().read(cx).snapshot(cx);
23771 let Some((color_fetcher, mut gutter_highlights)) =
23772 self.gutter_highlights.remove(&TypeId::of::<T>())
23773 else {
23774 return;
23775 };
23776 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23777 gutter_highlights.retain(|highlight| {
23778 while let Some(range_to_remove) = ranges_to_remove.peek() {
23779 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
23780 Ordering::Less | Ordering::Equal => {
23781 ranges_to_remove.next();
23782 }
23783 Ordering::Greater => {
23784 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
23785 Ordering::Less | Ordering::Equal => {
23786 return false;
23787 }
23788 Ordering::Greater => break,
23789 }
23790 }
23791 }
23792 }
23793
23794 true
23795 });
23796 self.gutter_highlights
23797 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
23798 }
23799
23800 #[cfg(any(test, feature = "test-support"))]
23801 pub fn all_text_highlights(
23802 &self,
23803 window: &mut Window,
23804 cx: &mut Context<Self>,
23805 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
23806 let snapshot = self.snapshot(window, cx);
23807 self.display_map.update(cx, |display_map, _| {
23808 display_map
23809 .all_text_highlights()
23810 .map(|(_, highlight)| {
23811 let (style, ranges) = highlight.as_ref();
23812 (
23813 *style,
23814 ranges
23815 .iter()
23816 .map(|range| range.clone().to_display_points(&snapshot))
23817 .collect(),
23818 )
23819 })
23820 .collect()
23821 })
23822 }
23823
23824 #[cfg(any(test, feature = "test-support"))]
23825 pub fn all_text_background_highlights(
23826 &self,
23827 window: &mut Window,
23828 cx: &mut Context<Self>,
23829 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23830 let snapshot = self.snapshot(window, cx);
23831 let buffer = &snapshot.buffer_snapshot();
23832 let start = buffer.anchor_before(MultiBufferOffset(0));
23833 let end = buffer.anchor_after(buffer.len());
23834 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
23835 }
23836
23837 #[cfg(any(test, feature = "test-support"))]
23838 pub fn sorted_background_highlights_in_range(
23839 &self,
23840 search_range: Range<Anchor>,
23841 display_snapshot: &DisplaySnapshot,
23842 theme: &Theme,
23843 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23844 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
23845 res.sort_by(|a, b| {
23846 a.0.start
23847 .cmp(&b.0.start)
23848 .then_with(|| a.0.end.cmp(&b.0.end))
23849 .then_with(|| a.1.cmp(&b.1))
23850 });
23851 res
23852 }
23853
23854 #[cfg(any(test, feature = "test-support"))]
23855 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
23856 let snapshot = self.buffer().read(cx).snapshot(cx);
23857
23858 let highlights = self
23859 .background_highlights
23860 .get(&HighlightKey::BufferSearchHighlights);
23861
23862 if let Some((_color, ranges)) = highlights {
23863 ranges
23864 .iter()
23865 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
23866 .collect_vec()
23867 } else {
23868 vec![]
23869 }
23870 }
23871
23872 fn document_highlights_for_position<'a>(
23873 &'a self,
23874 position: Anchor,
23875 buffer: &'a MultiBufferSnapshot,
23876 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
23877 let read_highlights = self
23878 .background_highlights
23879 .get(&HighlightKey::DocumentHighlightRead)
23880 .map(|h| &h.1);
23881 let write_highlights = self
23882 .background_highlights
23883 .get(&HighlightKey::DocumentHighlightWrite)
23884 .map(|h| &h.1);
23885 let left_position = position.bias_left(buffer);
23886 let right_position = position.bias_right(buffer);
23887 read_highlights
23888 .into_iter()
23889 .chain(write_highlights)
23890 .flat_map(move |ranges| {
23891 let start_ix = match ranges.binary_search_by(|probe| {
23892 let cmp = probe.end.cmp(&left_position, buffer);
23893 if cmp.is_ge() {
23894 Ordering::Greater
23895 } else {
23896 Ordering::Less
23897 }
23898 }) {
23899 Ok(i) | Err(i) => i,
23900 };
23901
23902 ranges[start_ix..]
23903 .iter()
23904 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
23905 })
23906 }
23907
23908 pub fn has_background_highlights(&self, key: HighlightKey) -> bool {
23909 self.background_highlights
23910 .get(&key)
23911 .is_some_and(|(_, highlights)| !highlights.is_empty())
23912 }
23913
23914 /// Returns all background highlights for a given range.
23915 ///
23916 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
23917 pub fn background_highlights_in_range(
23918 &self,
23919 search_range: Range<Anchor>,
23920 display_snapshot: &DisplaySnapshot,
23921 theme: &Theme,
23922 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23923 let mut results = Vec::new();
23924 for (color_fetcher, ranges) in self.background_highlights.values() {
23925 let start_ix = match ranges.binary_search_by(|probe| {
23926 let cmp = probe
23927 .end
23928 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23929 if cmp.is_gt() {
23930 Ordering::Greater
23931 } else {
23932 Ordering::Less
23933 }
23934 }) {
23935 Ok(i) | Err(i) => i,
23936 };
23937 for (index, range) in ranges[start_ix..].iter().enumerate() {
23938 if range
23939 .start
23940 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23941 .is_ge()
23942 {
23943 break;
23944 }
23945
23946 let color = color_fetcher(&(start_ix + index), theme);
23947 let start = range.start.to_display_point(display_snapshot);
23948 let end = range.end.to_display_point(display_snapshot);
23949 results.push((start..end, color))
23950 }
23951 }
23952 results
23953 }
23954
23955 pub fn gutter_highlights_in_range(
23956 &self,
23957 search_range: Range<Anchor>,
23958 display_snapshot: &DisplaySnapshot,
23959 cx: &App,
23960 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23961 let mut results = Vec::new();
23962 for (color_fetcher, ranges) in self.gutter_highlights.values() {
23963 let color = color_fetcher(cx);
23964 let start_ix = match ranges.binary_search_by(|probe| {
23965 let cmp = probe
23966 .end
23967 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23968 if cmp.is_gt() {
23969 Ordering::Greater
23970 } else {
23971 Ordering::Less
23972 }
23973 }) {
23974 Ok(i) | Err(i) => i,
23975 };
23976 for range in &ranges[start_ix..] {
23977 if range
23978 .start
23979 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23980 .is_ge()
23981 {
23982 break;
23983 }
23984
23985 let start = range.start.to_display_point(display_snapshot);
23986 let end = range.end.to_display_point(display_snapshot);
23987 results.push((start..end, color))
23988 }
23989 }
23990 results
23991 }
23992
23993 /// Get the text ranges corresponding to the redaction query
23994 pub fn redacted_ranges(
23995 &self,
23996 search_range: Range<Anchor>,
23997 display_snapshot: &DisplaySnapshot,
23998 cx: &App,
23999 ) -> Vec<Range<DisplayPoint>> {
24000 display_snapshot
24001 .buffer_snapshot()
24002 .redacted_ranges(search_range, |file| {
24003 if let Some(file) = file {
24004 file.is_private()
24005 && EditorSettings::get(
24006 Some(SettingsLocation {
24007 worktree_id: file.worktree_id(cx),
24008 path: file.path().as_ref(),
24009 }),
24010 cx,
24011 )
24012 .redact_private_values
24013 } else {
24014 false
24015 }
24016 })
24017 .map(|range| {
24018 range.start.to_display_point(display_snapshot)
24019 ..range.end.to_display_point(display_snapshot)
24020 })
24021 .collect()
24022 }
24023
24024 pub fn highlight_text_key(
24025 &mut self,
24026 key: HighlightKey,
24027 ranges: Vec<Range<Anchor>>,
24028 style: HighlightStyle,
24029 merge: bool,
24030 cx: &mut Context<Self>,
24031 ) {
24032 self.display_map.update(cx, |map, cx| {
24033 map.highlight_text(key, ranges, style, merge, cx);
24034 });
24035 cx.notify();
24036 }
24037
24038 pub fn highlight_text(
24039 &mut self,
24040 key: HighlightKey,
24041 ranges: Vec<Range<Anchor>>,
24042 style: HighlightStyle,
24043 cx: &mut Context<Self>,
24044 ) {
24045 self.display_map.update(cx, |map, cx| {
24046 map.highlight_text(key, ranges, style, false, cx)
24047 });
24048 cx.notify();
24049 }
24050
24051 pub fn text_highlights<'a>(
24052 &'a self,
24053 key: HighlightKey,
24054 cx: &'a App,
24055 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
24056 self.display_map.read(cx).text_highlights(key)
24057 }
24058
24059 pub fn clear_highlights(&mut self, key: HighlightKey, cx: &mut Context<Self>) {
24060 let cleared = self
24061 .display_map
24062 .update(cx, |map, _| map.clear_highlights(key));
24063 if cleared {
24064 cx.notify();
24065 }
24066 }
24067
24068 pub fn clear_highlights_with(
24069 &mut self,
24070 f: &mut dyn FnMut(&HighlightKey) -> bool,
24071 cx: &mut Context<Self>,
24072 ) {
24073 let cleared = self
24074 .display_map
24075 .update(cx, |map, _| map.clear_highlights_with(f));
24076 if cleared {
24077 cx.notify();
24078 }
24079 }
24080
24081 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
24082 (self.read_only(cx) || self.blink_manager.read(cx).visible())
24083 && self.focus_handle.is_focused(window)
24084 }
24085
24086 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
24087 self.show_cursor_when_unfocused = is_enabled;
24088 cx.notify();
24089 }
24090
24091 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
24092 cx.notify();
24093 }
24094
24095 fn on_debug_session_event(
24096 &mut self,
24097 _session: Entity<Session>,
24098 event: &SessionEvent,
24099 cx: &mut Context<Self>,
24100 ) {
24101 if let SessionEvent::InvalidateInlineValue = event {
24102 self.refresh_inline_values(cx);
24103 }
24104 }
24105
24106 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
24107 let Some(semantics) = self.semantics_provider.clone() else {
24108 return;
24109 };
24110
24111 if !self.inline_value_cache.enabled {
24112 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
24113 self.splice_inlays(&inlays, Vec::new(), cx);
24114 return;
24115 }
24116
24117 let current_execution_position = self
24118 .highlighted_rows
24119 .get(&TypeId::of::<ActiveDebugLine>())
24120 .and_then(|lines| lines.last().map(|line| line.range.end));
24121
24122 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
24123 let inline_values = editor
24124 .update(cx, |editor, cx| {
24125 let Some(current_execution_position) = current_execution_position else {
24126 return Some(Task::ready(Ok(Vec::new())));
24127 };
24128
24129 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
24130 let snapshot = buffer.snapshot(cx);
24131
24132 let excerpt = snapshot.excerpt_containing(
24133 current_execution_position..current_execution_position,
24134 )?;
24135
24136 editor.buffer.read(cx).buffer(excerpt.buffer_id())
24137 })?;
24138
24139 if current_execution_position
24140 .text_anchor
24141 .buffer_id
24142 .is_some_and(|id| id != buffer.read(cx).remote_id())
24143 {
24144 return Some(Task::ready(Ok(Vec::new())));
24145 }
24146
24147 let range =
24148 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
24149
24150 semantics.inline_values(buffer, range, cx)
24151 })
24152 .ok()
24153 .flatten()?
24154 .await
24155 .context("refreshing debugger inlays")
24156 .log_err()?;
24157
24158 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
24159
24160 for (buffer_id, inline_value) in inline_values
24161 .into_iter()
24162 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
24163 {
24164 buffer_inline_values
24165 .entry(buffer_id)
24166 .or_default()
24167 .push(inline_value);
24168 }
24169
24170 editor
24171 .update(cx, |editor, cx| {
24172 let snapshot = editor.buffer.read(cx).snapshot(cx);
24173 let mut new_inlays = Vec::default();
24174
24175 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
24176 let buffer_id = buffer_snapshot.remote_id();
24177 buffer_inline_values
24178 .get(&buffer_id)
24179 .into_iter()
24180 .flatten()
24181 .for_each(|hint| {
24182 let inlay = Inlay::debugger(
24183 post_inc(&mut editor.next_inlay_id),
24184 Anchor::in_buffer(excerpt_id, hint.position),
24185 hint.text(),
24186 );
24187 if !inlay.text().chars().contains(&'\n') {
24188 new_inlays.push(inlay);
24189 }
24190 });
24191 }
24192
24193 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
24194 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
24195
24196 editor.splice_inlays(&inlay_ids, new_inlays, cx);
24197 })
24198 .ok()?;
24199 Some(())
24200 });
24201 }
24202
24203 fn on_buffer_event(
24204 &mut self,
24205 multibuffer: &Entity<MultiBuffer>,
24206 event: &multi_buffer::Event,
24207 window: &mut Window,
24208 cx: &mut Context<Self>,
24209 ) {
24210 match event {
24211 multi_buffer::Event::Edited {
24212 edited_buffer,
24213 is_local,
24214 } => {
24215 self.scrollbar_marker_state.dirty = true;
24216 self.active_indent_guides_state.dirty = true;
24217 self.refresh_active_diagnostics(cx);
24218 self.refresh_code_actions(window, cx);
24219 self.refresh_single_line_folds(window, cx);
24220 let snapshot = self.snapshot(window, cx);
24221 self.refresh_matching_bracket_highlights(&snapshot, cx);
24222 self.refresh_outline_symbols_at_cursor(cx);
24223 self.refresh_sticky_headers(&snapshot, cx);
24224 if *is_local && self.has_active_edit_prediction() {
24225 self.update_visible_edit_prediction(window, cx);
24226 }
24227
24228 // Clean up orphaned review comments after edits
24229 self.cleanup_orphaned_review_comments(cx);
24230
24231 if let Some(buffer) = edited_buffer {
24232 if buffer.read(cx).file().is_none() {
24233 cx.emit(EditorEvent::TitleChanged);
24234 }
24235
24236 if self.project.is_some() {
24237 let buffer_id = buffer.read(cx).remote_id();
24238 self.register_buffer(buffer_id, cx);
24239 self.update_lsp_data(Some(buffer_id), window, cx);
24240 self.refresh_inlay_hints(
24241 InlayHintRefreshReason::BufferEdited(buffer_id),
24242 cx,
24243 );
24244 }
24245 }
24246
24247 cx.emit(EditorEvent::BufferEdited);
24248 cx.emit(SearchEvent::MatchesInvalidated);
24249
24250 let Some(project) = &self.project else { return };
24251 let (telemetry, is_via_ssh) = {
24252 let project = project.read(cx);
24253 let telemetry = project.client().telemetry().clone();
24254 let is_via_ssh = project.is_via_remote_server();
24255 (telemetry, is_via_ssh)
24256 };
24257 telemetry.log_edit_event("editor", is_via_ssh);
24258 }
24259 multi_buffer::Event::ExcerptsAdded {
24260 buffer,
24261 predecessor,
24262 excerpts,
24263 } => {
24264 let buffer_id = buffer.read(cx).remote_id();
24265 if self.buffer.read(cx).diff_for(buffer_id).is_none()
24266 && let Some(project) = &self.project
24267 {
24268 update_uncommitted_diff_for_buffer(
24269 cx.entity(),
24270 project,
24271 [buffer.clone()],
24272 self.buffer.clone(),
24273 cx,
24274 )
24275 .detach();
24276 }
24277 self.semantic_token_state
24278 .invalidate_buffer(&buffer.read(cx).remote_id());
24279 self.update_lsp_data(Some(buffer_id), window, cx);
24280 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
24281 self.refresh_runnables(None, window, cx);
24282 self.colorize_brackets(false, cx);
24283 self.refresh_selected_text_highlights(&self.display_snapshot(cx), true, window, cx);
24284 cx.emit(EditorEvent::ExcerptsAdded {
24285 buffer: buffer.clone(),
24286 predecessor: *predecessor,
24287 excerpts: excerpts.clone(),
24288 });
24289 }
24290 multi_buffer::Event::ExcerptsRemoved {
24291 ids,
24292 removed_buffer_ids,
24293 } => {
24294 if let Some(inlay_hints) = &mut self.inlay_hints {
24295 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
24296 }
24297 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
24298 for buffer_id in removed_buffer_ids {
24299 self.registered_buffers.remove(buffer_id);
24300 self.clear_runnables(Some(*buffer_id));
24301 self.semantic_token_state.invalidate_buffer(buffer_id);
24302 self.display_map.update(cx, |display_map, cx| {
24303 display_map.invalidate_semantic_highlights(*buffer_id);
24304 display_map.clear_lsp_folding_ranges(*buffer_id, cx);
24305 });
24306 }
24307
24308 self.display_map.update(cx, |display_map, cx| {
24309 display_map.unfold_buffers(removed_buffer_ids.iter().copied(), cx);
24310 });
24311
24312 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24313 cx.emit(EditorEvent::ExcerptsRemoved {
24314 ids: ids.clone(),
24315 removed_buffer_ids: removed_buffer_ids.clone(),
24316 });
24317 }
24318 multi_buffer::Event::ExcerptsEdited {
24319 excerpt_ids,
24320 buffer_ids,
24321 } => {
24322 self.display_map.update(cx, |map, cx| {
24323 map.unfold_buffers(buffer_ids.iter().copied(), cx)
24324 });
24325 cx.emit(EditorEvent::ExcerptsEdited {
24326 ids: excerpt_ids.clone(),
24327 });
24328 }
24329 multi_buffer::Event::ExcerptsExpanded { ids } => {
24330 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
24331 self.refresh_document_highlights(cx);
24332 let snapshot = multibuffer.read(cx).snapshot(cx);
24333 for id in ids {
24334 self.bracket_fetched_tree_sitter_chunks.remove(id);
24335 if let Some(buffer) = snapshot.buffer_for_excerpt(*id) {
24336 self.semantic_token_state
24337 .invalidate_buffer(&buffer.remote_id());
24338 }
24339 }
24340 self.colorize_brackets(false, cx);
24341 self.update_lsp_data(None, window, cx);
24342 self.refresh_runnables(None, window, cx);
24343 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
24344 }
24345 multi_buffer::Event::Reparsed(buffer_id) => {
24346 self.refresh_runnables(Some(*buffer_id), window, cx);
24347 self.refresh_selected_text_highlights(&self.display_snapshot(cx), true, window, cx);
24348 self.colorize_brackets(true, cx);
24349 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24350
24351 cx.emit(EditorEvent::Reparsed(*buffer_id));
24352 }
24353 multi_buffer::Event::DiffHunksToggled => {
24354 self.refresh_runnables(None, window, cx);
24355 }
24356 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
24357 if !is_fresh_language {
24358 self.registered_buffers.remove(&buffer_id);
24359 }
24360 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24361 cx.emit(EditorEvent::Reparsed(*buffer_id));
24362 self.update_edit_prediction_settings(cx);
24363 cx.notify();
24364 }
24365 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
24366 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
24367 multi_buffer::Event::FileHandleChanged
24368 | multi_buffer::Event::Reloaded
24369 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
24370 multi_buffer::Event::DiagnosticsUpdated => {
24371 self.update_diagnostics_state(window, cx);
24372 }
24373 _ => {}
24374 };
24375 }
24376
24377 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
24378 if !self.diagnostics_enabled() {
24379 return;
24380 }
24381 self.refresh_active_diagnostics(cx);
24382 self.refresh_inline_diagnostics(true, window, cx);
24383 self.scrollbar_marker_state.dirty = true;
24384 cx.notify();
24385 }
24386
24387 pub fn start_temporary_diff_override(&mut self) {
24388 self.load_diff_task.take();
24389 self.temporary_diff_override = true;
24390 }
24391
24392 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
24393 self.temporary_diff_override = false;
24394 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
24395 self.buffer.update(cx, |buffer, cx| {
24396 buffer.set_all_diff_hunks_collapsed(cx);
24397 });
24398
24399 if let Some(project) = self.project.clone() {
24400 self.load_diff_task = Some(
24401 update_uncommitted_diff_for_buffer(
24402 cx.entity(),
24403 &project,
24404 self.buffer.read(cx).all_buffers(),
24405 self.buffer.clone(),
24406 cx,
24407 )
24408 .shared(),
24409 );
24410 }
24411 }
24412
24413 fn on_display_map_changed(
24414 &mut self,
24415 _: Entity<DisplayMap>,
24416 _: &mut Window,
24417 cx: &mut Context<Self>,
24418 ) {
24419 cx.notify();
24420 }
24421
24422 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
24423 if !self.mode.is_full() {
24424 return None;
24425 }
24426
24427 let theme_settings = theme::ThemeSettings::get_global(cx);
24428 let theme = cx.theme();
24429 let accent_colors = theme.accents().clone();
24430
24431 let accent_overrides = theme_settings
24432 .theme_overrides
24433 .get(theme.name.as_ref())
24434 .map(|theme_style| &theme_style.accents)
24435 .into_iter()
24436 .flatten()
24437 .chain(
24438 theme_settings
24439 .experimental_theme_overrides
24440 .as_ref()
24441 .map(|overrides| &overrides.accents)
24442 .into_iter()
24443 .flatten(),
24444 )
24445 .flat_map(|accent| accent.0.clone().map(SharedString::from))
24446 .collect();
24447
24448 Some(AccentData {
24449 colors: accent_colors,
24450 overrides: accent_overrides,
24451 })
24452 }
24453
24454 fn fetch_applicable_language_settings(
24455 &self,
24456 cx: &App,
24457 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
24458 if !self.mode.is_full() {
24459 return HashMap::default();
24460 }
24461
24462 self.buffer().read(cx).all_buffers().into_iter().fold(
24463 HashMap::default(),
24464 |mut acc, buffer| {
24465 let buffer = buffer.read(cx);
24466 let language = buffer.language().map(|language| language.name());
24467 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
24468 let file = buffer.file();
24469 v.insert(language_settings(language, file, cx).into_owned());
24470 }
24471 acc
24472 },
24473 )
24474 }
24475
24476 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24477 let new_language_settings = self.fetch_applicable_language_settings(cx);
24478 let language_settings_changed = new_language_settings != self.applicable_language_settings;
24479 self.applicable_language_settings = new_language_settings;
24480
24481 let new_accents = self.fetch_accent_data(cx);
24482 let accents_changed = new_accents != self.accent_data;
24483 self.accent_data = new_accents;
24484
24485 if self.diagnostics_enabled() {
24486 let new_severity = EditorSettings::get_global(cx)
24487 .diagnostics_max_severity
24488 .unwrap_or(DiagnosticSeverity::Hint);
24489 self.set_max_diagnostics_severity(new_severity, cx);
24490 }
24491 self.refresh_runnables(None, window, cx);
24492 self.update_edit_prediction_settings(cx);
24493 self.refresh_edit_prediction(true, false, window, cx);
24494 self.refresh_inline_values(cx);
24495
24496 let old_cursor_shape = self.cursor_shape;
24497 let old_show_breadcrumbs = self.show_breadcrumbs;
24498
24499 {
24500 let editor_settings = EditorSettings::get_global(cx);
24501 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
24502 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
24503 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
24504 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
24505 }
24506
24507 if old_cursor_shape != self.cursor_shape {
24508 cx.emit(EditorEvent::CursorShapeChanged);
24509 }
24510
24511 if old_show_breadcrumbs != self.show_breadcrumbs {
24512 cx.emit(EditorEvent::BreadcrumbsChanged);
24513 }
24514
24515 let (restore_unsaved_buffers, show_inline_diagnostics, inline_blame_enabled) = {
24516 let project_settings = ProjectSettings::get_global(cx);
24517 (
24518 project_settings.session.restore_unsaved_buffers,
24519 project_settings.diagnostics.inline.enabled,
24520 project_settings.git.inline_blame.enabled,
24521 )
24522 };
24523 self.buffer_serialization = self
24524 .should_serialize_buffer()
24525 .then(|| BufferSerialization::new(restore_unsaved_buffers));
24526
24527 if self.mode.is_full() {
24528 if self.show_inline_diagnostics != show_inline_diagnostics {
24529 self.show_inline_diagnostics = show_inline_diagnostics;
24530 self.refresh_inline_diagnostics(false, window, cx);
24531 }
24532
24533 if self.git_blame_inline_enabled != inline_blame_enabled {
24534 self.toggle_git_blame_inline_internal(false, window, cx);
24535 }
24536
24537 let minimap_settings = EditorSettings::get_global(cx).minimap;
24538 if self.minimap_visibility != MinimapVisibility::Disabled {
24539 if self.minimap_visibility.settings_visibility()
24540 != minimap_settings.minimap_enabled()
24541 {
24542 self.set_minimap_visibility(
24543 MinimapVisibility::for_mode(self.mode(), cx),
24544 window,
24545 cx,
24546 );
24547 } else if let Some(minimap_entity) = self.minimap.as_ref() {
24548 minimap_entity.update(cx, |minimap_editor, cx| {
24549 minimap_editor.update_minimap_configuration(minimap_settings, cx)
24550 })
24551 }
24552 }
24553
24554 if language_settings_changed || accents_changed {
24555 self.colorize_brackets(true, cx);
24556 }
24557
24558 if language_settings_changed {
24559 self.clear_disabled_lsp_folding_ranges(window, cx);
24560 self.refresh_document_symbols(None, cx);
24561 }
24562
24563 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
24564 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
24565 }) {
24566 if !inlay_splice.is_empty() {
24567 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
24568 }
24569 self.refresh_document_colors(None, window, cx);
24570 }
24571
24572 self.refresh_inlay_hints(
24573 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
24574 self.selections.newest_anchor().head(),
24575 &self.buffer.read(cx).snapshot(cx),
24576 cx,
24577 )),
24578 cx,
24579 );
24580
24581 let new_semantic_token_rules = ProjectSettings::get_global(cx)
24582 .global_lsp_settings
24583 .semantic_token_rules
24584 .clone();
24585 let semantic_token_rules_changed = self
24586 .semantic_token_state
24587 .update_rules(new_semantic_token_rules);
24588 if language_settings_changed || semantic_token_rules_changed {
24589 self.invalidate_semantic_tokens(None);
24590 self.refresh_semantic_tokens(None, None, cx);
24591 }
24592 }
24593
24594 cx.notify();
24595 }
24596
24597 fn theme_changed(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24598 if !self.mode.is_full() {
24599 return;
24600 }
24601
24602 let new_accents = self.fetch_accent_data(cx);
24603 if new_accents != self.accent_data {
24604 self.accent_data = new_accents;
24605 self.colorize_brackets(true, cx);
24606 }
24607
24608 self.invalidate_semantic_tokens(None);
24609 self.refresh_semantic_tokens(None, None, cx);
24610 }
24611
24612 pub fn set_searchable(&mut self, searchable: bool) {
24613 self.searchable = searchable;
24614 }
24615
24616 pub fn searchable(&self) -> bool {
24617 self.searchable
24618 }
24619
24620 pub fn open_excerpts_in_split(
24621 &mut self,
24622 _: &OpenExcerptsSplit,
24623 window: &mut Window,
24624 cx: &mut Context<Self>,
24625 ) {
24626 self.open_excerpts_common(None, true, window, cx)
24627 }
24628
24629 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
24630 self.open_excerpts_common(None, false, window, cx)
24631 }
24632
24633 pub(crate) fn open_excerpts_common(
24634 &mut self,
24635 jump_data: Option<JumpData>,
24636 split: bool,
24637 window: &mut Window,
24638 cx: &mut Context<Self>,
24639 ) {
24640 if self.buffer.read(cx).is_singleton() {
24641 cx.propagate();
24642 return;
24643 }
24644
24645 let mut new_selections_by_buffer = HashMap::default();
24646 match &jump_data {
24647 Some(JumpData::MultiBufferPoint {
24648 excerpt_id,
24649 position,
24650 anchor,
24651 line_offset_from_top,
24652 }) => {
24653 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
24654 if let Some(buffer) = multi_buffer_snapshot
24655 .buffer_id_for_excerpt(*excerpt_id)
24656 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
24657 {
24658 let buffer_snapshot = buffer.read(cx).snapshot();
24659 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
24660 language::ToPoint::to_point(anchor, &buffer_snapshot)
24661 } else {
24662 buffer_snapshot.clip_point(*position, Bias::Left)
24663 };
24664 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
24665 new_selections_by_buffer.insert(
24666 buffer,
24667 (
24668 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
24669 Some(*line_offset_from_top),
24670 ),
24671 );
24672 }
24673 }
24674 Some(JumpData::MultiBufferRow {
24675 row,
24676 line_offset_from_top,
24677 }) => {
24678 let point = MultiBufferPoint::new(row.0, 0);
24679 if let Some((buffer, buffer_point, _)) =
24680 self.buffer.read(cx).point_to_buffer_point(point, cx)
24681 {
24682 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
24683 new_selections_by_buffer
24684 .entry(buffer)
24685 .or_insert((Vec::new(), Some(*line_offset_from_top)))
24686 .0
24687 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
24688 }
24689 }
24690 None => {
24691 let selections = self
24692 .selections
24693 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
24694 let multi_buffer = self.buffer.read(cx);
24695 for selection in selections {
24696 for (snapshot, range, _, anchor) in multi_buffer
24697 .snapshot(cx)
24698 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
24699 {
24700 if let Some(anchor) = anchor {
24701 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
24702 else {
24703 continue;
24704 };
24705 let offset = text::ToOffset::to_offset(
24706 &anchor.text_anchor,
24707 &buffer_handle.read(cx).snapshot(),
24708 );
24709 let range = BufferOffset(offset)..BufferOffset(offset);
24710 new_selections_by_buffer
24711 .entry(buffer_handle)
24712 .or_insert((Vec::new(), None))
24713 .0
24714 .push(range)
24715 } else {
24716 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
24717 else {
24718 continue;
24719 };
24720 new_selections_by_buffer
24721 .entry(buffer_handle)
24722 .or_insert((Vec::new(), None))
24723 .0
24724 .push(range)
24725 }
24726 }
24727 }
24728 }
24729 }
24730
24731 if self.delegate_open_excerpts {
24732 let selections_by_buffer: HashMap<_, _> = new_selections_by_buffer
24733 .into_iter()
24734 .map(|(buffer, value)| (buffer.read(cx).remote_id(), value))
24735 .collect();
24736 if !selections_by_buffer.is_empty() {
24737 cx.emit(EditorEvent::OpenExcerptsRequested {
24738 selections_by_buffer,
24739 split,
24740 });
24741 }
24742 return;
24743 }
24744
24745 let Some(workspace) = self.workspace() else {
24746 cx.propagate();
24747 return;
24748 };
24749
24750 new_selections_by_buffer
24751 .retain(|buffer, _| buffer.read(cx).file().is_none_or(|file| file.can_open()));
24752
24753 if new_selections_by_buffer.is_empty() {
24754 return;
24755 }
24756
24757 Self::open_buffers_in_workspace(
24758 workspace.downgrade(),
24759 new_selections_by_buffer,
24760 split,
24761 window,
24762 cx,
24763 );
24764 }
24765
24766 pub(crate) fn open_buffers_in_workspace(
24767 workspace: WeakEntity<Workspace>,
24768 new_selections_by_buffer: HashMap<
24769 Entity<language::Buffer>,
24770 (Vec<Range<BufferOffset>>, Option<u32>),
24771 >,
24772 split: bool,
24773 window: &mut Window,
24774 cx: &mut App,
24775 ) {
24776 // We defer the pane interaction because we ourselves are a workspace item
24777 // and activating a new item causes the pane to call a method on us reentrantly,
24778 // which panics if we're on the stack.
24779 window.defer(cx, move |window, cx| {
24780 workspace
24781 .update(cx, |workspace, cx| {
24782 let pane = if split {
24783 workspace.adjacent_pane(window, cx)
24784 } else {
24785 workspace.active_pane().clone()
24786 };
24787
24788 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
24789 let buffer_read = buffer.read(cx);
24790 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
24791 (true, project::File::from_dyn(Some(file)).is_some())
24792 } else {
24793 (false, false)
24794 };
24795
24796 // If project file is none workspace.open_project_item will fail to open the excerpt
24797 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
24798 // so we check if there's a tab match in that case first
24799 let editor = (!has_file || !is_project_file)
24800 .then(|| {
24801 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
24802 // so `workspace.open_project_item` will never find them, always opening a new editor.
24803 // Instead, we try to activate the existing editor in the pane first.
24804 let (editor, pane_item_index, pane_item_id) =
24805 pane.read(cx).items().enumerate().find_map(|(i, item)| {
24806 let editor = item.downcast::<Editor>()?;
24807 let singleton_buffer =
24808 editor.read(cx).buffer().read(cx).as_singleton()?;
24809 if singleton_buffer == buffer {
24810 Some((editor, i, item.item_id()))
24811 } else {
24812 None
24813 }
24814 })?;
24815 pane.update(cx, |pane, cx| {
24816 pane.activate_item(pane_item_index, true, true, window, cx);
24817 if !PreviewTabsSettings::get_global(cx)
24818 .enable_preview_from_multibuffer
24819 {
24820 pane.unpreview_item_if_preview(pane_item_id);
24821 }
24822 });
24823 Some(editor)
24824 })
24825 .flatten()
24826 .unwrap_or_else(|| {
24827 let keep_old_preview = PreviewTabsSettings::get_global(cx)
24828 .enable_keep_preview_on_code_navigation;
24829 let allow_new_preview = PreviewTabsSettings::get_global(cx)
24830 .enable_preview_from_multibuffer;
24831 workspace.open_project_item::<Self>(
24832 pane.clone(),
24833 buffer,
24834 true,
24835 true,
24836 keep_old_preview,
24837 allow_new_preview,
24838 window,
24839 cx,
24840 )
24841 });
24842
24843 editor.update(cx, |editor, cx| {
24844 if has_file && !is_project_file {
24845 editor.set_read_only(true);
24846 }
24847 let autoscroll = match scroll_offset {
24848 Some(scroll_offset) => {
24849 Autoscroll::top_relative(scroll_offset as usize)
24850 }
24851 None => Autoscroll::newest(),
24852 };
24853 let nav_history = editor.nav_history.take();
24854 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
24855 let Some((excerpt_id, _, buffer_snapshot)) =
24856 multibuffer_snapshot.as_singleton()
24857 else {
24858 return;
24859 };
24860 editor.change_selections(
24861 SelectionEffects::scroll(autoscroll),
24862 window,
24863 cx,
24864 |s| {
24865 s.select_ranges(ranges.into_iter().map(|range| {
24866 let range = buffer_snapshot.anchor_before(range.start)
24867 ..buffer_snapshot.anchor_after(range.end);
24868 multibuffer_snapshot
24869 .anchor_range_in_excerpt(excerpt_id, range)
24870 .unwrap()
24871 }));
24872 },
24873 );
24874 editor.nav_history = nav_history;
24875 });
24876 }
24877 })
24878 .ok();
24879 });
24880 }
24881
24882 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
24883 let snapshot = self.buffer.read(cx).read(cx);
24884 let (_, ranges) = self.text_highlights(HighlightKey::InputComposition, cx)?;
24885 Some(
24886 ranges
24887 .iter()
24888 .map(move |range| {
24889 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
24890 })
24891 .collect(),
24892 )
24893 }
24894
24895 fn selection_replacement_ranges(
24896 &self,
24897 range: Range<MultiBufferOffsetUtf16>,
24898 cx: &mut App,
24899 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
24900 let selections = self
24901 .selections
24902 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24903 let newest_selection = selections
24904 .iter()
24905 .max_by_key(|selection| selection.id)
24906 .unwrap();
24907 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
24908 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
24909 let snapshot = self.buffer.read(cx).read(cx);
24910 selections
24911 .into_iter()
24912 .map(|mut selection| {
24913 selection.start.0.0 =
24914 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
24915 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
24916 snapshot.clip_offset_utf16(selection.start, Bias::Left)
24917 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
24918 })
24919 .collect()
24920 }
24921
24922 fn report_editor_event(
24923 &self,
24924 reported_event: ReportEditorEvent,
24925 file_extension: Option<String>,
24926 cx: &App,
24927 ) {
24928 if cfg!(any(test, feature = "test-support")) {
24929 return;
24930 }
24931
24932 let Some(project) = &self.project else { return };
24933
24934 // If None, we are in a file without an extension
24935 let file = self
24936 .buffer
24937 .read(cx)
24938 .as_singleton()
24939 .and_then(|b| b.read(cx).file());
24940 let file_extension = file_extension.or(file
24941 .as_ref()
24942 .and_then(|file| Path::new(file.file_name(cx)).extension())
24943 .and_then(|e| e.to_str())
24944 .map(|a| a.to_string()));
24945
24946 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
24947 .map(|vim_mode| vim_mode.0)
24948 .unwrap_or(false);
24949
24950 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
24951 let copilot_enabled = edit_predictions_provider
24952 == language::language_settings::EditPredictionProvider::Copilot;
24953 let copilot_enabled_for_language = self
24954 .buffer
24955 .read(cx)
24956 .language_settings(cx)
24957 .show_edit_predictions;
24958
24959 let project = project.read(cx);
24960 let event_type = reported_event.event_type();
24961
24962 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
24963 telemetry::event!(
24964 event_type,
24965 type = if auto_saved {"autosave"} else {"manual"},
24966 file_extension,
24967 vim_mode,
24968 copilot_enabled,
24969 copilot_enabled_for_language,
24970 edit_predictions_provider,
24971 is_via_ssh = project.is_via_remote_server(),
24972 );
24973 } else {
24974 telemetry::event!(
24975 event_type,
24976 file_extension,
24977 vim_mode,
24978 copilot_enabled,
24979 copilot_enabled_for_language,
24980 edit_predictions_provider,
24981 is_via_ssh = project.is_via_remote_server(),
24982 );
24983 };
24984 }
24985
24986 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
24987 /// with each line being an array of {text, highlight} objects.
24988 fn copy_highlight_json(
24989 &mut self,
24990 _: &CopyHighlightJson,
24991 window: &mut Window,
24992 cx: &mut Context<Self>,
24993 ) {
24994 #[derive(Serialize)]
24995 struct Chunk<'a> {
24996 text: String,
24997 highlight: Option<&'a str>,
24998 }
24999
25000 let snapshot = self.buffer.read(cx).snapshot(cx);
25001 let range = self
25002 .selected_text_range(false, window, cx)
25003 .and_then(|selection| {
25004 if selection.range.is_empty() {
25005 None
25006 } else {
25007 Some(
25008 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
25009 selection.range.start,
25010 )))
25011 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
25012 selection.range.end,
25013 ))),
25014 )
25015 }
25016 })
25017 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
25018
25019 let chunks = snapshot.chunks(range, true);
25020 let mut lines = Vec::new();
25021 let mut line: VecDeque<Chunk> = VecDeque::new();
25022
25023 let Some(style) = self.style.as_ref() else {
25024 return;
25025 };
25026
25027 for chunk in chunks {
25028 let highlight = chunk
25029 .syntax_highlight_id
25030 .and_then(|id| id.name(&style.syntax));
25031 let mut chunk_lines = chunk.text.split('\n').peekable();
25032 while let Some(text) = chunk_lines.next() {
25033 let mut merged_with_last_token = false;
25034 if let Some(last_token) = line.back_mut()
25035 && last_token.highlight == highlight
25036 {
25037 last_token.text.push_str(text);
25038 merged_with_last_token = true;
25039 }
25040
25041 if !merged_with_last_token {
25042 line.push_back(Chunk {
25043 text: text.into(),
25044 highlight,
25045 });
25046 }
25047
25048 if chunk_lines.peek().is_some() {
25049 if line.len() > 1 && line.front().unwrap().text.is_empty() {
25050 line.pop_front();
25051 }
25052 if line.len() > 1 && line.back().unwrap().text.is_empty() {
25053 line.pop_back();
25054 }
25055
25056 lines.push(mem::take(&mut line));
25057 }
25058 }
25059 }
25060
25061 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
25062 return;
25063 };
25064 cx.write_to_clipboard(ClipboardItem::new_string(lines));
25065 }
25066
25067 pub fn open_context_menu(
25068 &mut self,
25069 _: &OpenContextMenu,
25070 window: &mut Window,
25071 cx: &mut Context<Self>,
25072 ) {
25073 self.request_autoscroll(Autoscroll::newest(), cx);
25074 let position = self
25075 .selections
25076 .newest_display(&self.display_snapshot(cx))
25077 .start;
25078 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
25079 }
25080
25081 pub fn replay_insert_event(
25082 &mut self,
25083 text: &str,
25084 relative_utf16_range: Option<Range<isize>>,
25085 window: &mut Window,
25086 cx: &mut Context<Self>,
25087 ) {
25088 if !self.input_enabled {
25089 cx.emit(EditorEvent::InputIgnored { text: text.into() });
25090 return;
25091 }
25092 if let Some(relative_utf16_range) = relative_utf16_range {
25093 let selections = self
25094 .selections
25095 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
25096 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25097 let new_ranges = selections.into_iter().map(|range| {
25098 let start = MultiBufferOffsetUtf16(OffsetUtf16(
25099 range
25100 .head()
25101 .0
25102 .0
25103 .saturating_add_signed(relative_utf16_range.start),
25104 ));
25105 let end = MultiBufferOffsetUtf16(OffsetUtf16(
25106 range
25107 .head()
25108 .0
25109 .0
25110 .saturating_add_signed(relative_utf16_range.end),
25111 ));
25112 start..end
25113 });
25114 s.select_ranges(new_ranges);
25115 });
25116 }
25117
25118 self.handle_input(text, window, cx);
25119 }
25120
25121 pub fn is_focused(&self, window: &Window) -> bool {
25122 self.focus_handle.is_focused(window)
25123 }
25124
25125 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25126 cx.emit(EditorEvent::Focused);
25127
25128 if let Some(descendant) = self
25129 .last_focused_descendant
25130 .take()
25131 .and_then(|descendant| descendant.upgrade())
25132 {
25133 window.focus(&descendant, cx);
25134 } else {
25135 if let Some(blame) = self.blame.as_ref() {
25136 blame.update(cx, GitBlame::focus)
25137 }
25138
25139 self.blink_manager.update(cx, BlinkManager::enable);
25140 self.show_cursor_names(window, cx);
25141 self.buffer.update(cx, |buffer, cx| {
25142 buffer.finalize_last_transaction(cx);
25143 if self.leader_id.is_none() {
25144 buffer.set_active_selections(
25145 &self.selections.disjoint_anchors_arc(),
25146 self.selections.line_mode(),
25147 self.cursor_shape,
25148 cx,
25149 );
25150 }
25151 });
25152
25153 if let Some(position_map) = self.last_position_map.clone()
25154 && !self.mouse_cursor_hidden
25155 {
25156 EditorElement::mouse_moved(
25157 self,
25158 &MouseMoveEvent {
25159 position: window.mouse_position(),
25160 pressed_button: None,
25161 modifiers: window.modifiers(),
25162 },
25163 &position_map,
25164 None,
25165 window,
25166 cx,
25167 );
25168 }
25169 }
25170 }
25171
25172 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
25173 cx.emit(EditorEvent::FocusedIn)
25174 }
25175
25176 fn handle_focus_out(
25177 &mut self,
25178 event: FocusOutEvent,
25179 _window: &mut Window,
25180 cx: &mut Context<Self>,
25181 ) {
25182 if event.blurred != self.focus_handle {
25183 self.last_focused_descendant = Some(event.blurred);
25184 }
25185 self.selection_drag_state = SelectionDragState::None;
25186 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
25187 }
25188
25189 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25190 self.blink_manager.update(cx, BlinkManager::disable);
25191 self.buffer
25192 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
25193
25194 if let Some(blame) = self.blame.as_ref() {
25195 blame.update(cx, GitBlame::blur)
25196 }
25197 if !self.hover_state.focused(window, cx) {
25198 hide_hover(self, cx);
25199 }
25200 if !self
25201 .context_menu
25202 .borrow()
25203 .as_ref()
25204 .is_some_and(|context_menu| context_menu.focused(window, cx))
25205 {
25206 self.hide_context_menu(window, cx);
25207 }
25208 self.take_active_edit_prediction(cx);
25209 cx.emit(EditorEvent::Blurred);
25210 cx.notify();
25211 }
25212
25213 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25214 let mut pending: String = window
25215 .pending_input_keystrokes()
25216 .into_iter()
25217 .flatten()
25218 .filter_map(|keystroke| keystroke.key_char.clone())
25219 .collect();
25220
25221 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
25222 pending = "".to_string();
25223 }
25224
25225 let existing_pending = self
25226 .text_highlights(HighlightKey::PendingInput, cx)
25227 .map(|(_, ranges)| ranges.to_vec());
25228 if existing_pending.is_none() && pending.is_empty() {
25229 return;
25230 }
25231 let transaction =
25232 self.transact(window, cx, |this, window, cx| {
25233 let selections = this
25234 .selections
25235 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
25236 let edits = selections
25237 .iter()
25238 .map(|selection| (selection.end..selection.end, pending.clone()));
25239 this.edit(edits, cx);
25240 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25241 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
25242 sel.start + ix * pending.len()..sel.end + ix * pending.len()
25243 }));
25244 });
25245 if let Some(existing_ranges) = existing_pending {
25246 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
25247 this.edit(edits, cx);
25248 }
25249 });
25250
25251 let snapshot = self.snapshot(window, cx);
25252 let ranges = self
25253 .selections
25254 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
25255 .into_iter()
25256 .map(|selection| {
25257 snapshot.buffer_snapshot().anchor_after(selection.end)
25258 ..snapshot
25259 .buffer_snapshot()
25260 .anchor_before(selection.end + pending.len())
25261 })
25262 .collect();
25263
25264 if pending.is_empty() {
25265 self.clear_highlights(HighlightKey::PendingInput, cx);
25266 } else {
25267 self.highlight_text(
25268 HighlightKey::PendingInput,
25269 ranges,
25270 HighlightStyle {
25271 underline: Some(UnderlineStyle {
25272 thickness: px(1.),
25273 color: None,
25274 wavy: false,
25275 }),
25276 ..Default::default()
25277 },
25278 cx,
25279 );
25280 }
25281
25282 self.ime_transaction = self.ime_transaction.or(transaction);
25283 if let Some(transaction) = self.ime_transaction {
25284 self.buffer.update(cx, |buffer, cx| {
25285 buffer.group_until_transaction(transaction, cx);
25286 });
25287 }
25288
25289 if self
25290 .text_highlights(HighlightKey::PendingInput, cx)
25291 .is_none()
25292 {
25293 self.ime_transaction.take();
25294 }
25295 }
25296
25297 pub fn register_action_renderer(
25298 &mut self,
25299 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
25300 ) -> Subscription {
25301 let id = self.next_editor_action_id.post_inc();
25302 self.editor_actions
25303 .borrow_mut()
25304 .insert(id, Box::new(listener));
25305
25306 let editor_actions = self.editor_actions.clone();
25307 Subscription::new(move || {
25308 editor_actions.borrow_mut().remove(&id);
25309 })
25310 }
25311
25312 pub fn register_action<A: Action>(
25313 &mut self,
25314 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
25315 ) -> Subscription {
25316 let id = self.next_editor_action_id.post_inc();
25317 let listener = Arc::new(listener);
25318 self.editor_actions.borrow_mut().insert(
25319 id,
25320 Box::new(move |_, window, _| {
25321 let listener = listener.clone();
25322 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
25323 let action = action.downcast_ref().unwrap();
25324 if phase == DispatchPhase::Bubble {
25325 listener(action, window, cx)
25326 }
25327 })
25328 }),
25329 );
25330
25331 let editor_actions = self.editor_actions.clone();
25332 Subscription::new(move || {
25333 editor_actions.borrow_mut().remove(&id);
25334 })
25335 }
25336
25337 pub fn file_header_size(&self) -> u32 {
25338 FILE_HEADER_HEIGHT
25339 }
25340
25341 pub fn restore(
25342 &mut self,
25343 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
25344 window: &mut Window,
25345 cx: &mut Context<Self>,
25346 ) {
25347 self.buffer().update(cx, |multi_buffer, cx| {
25348 for (buffer_id, changes) in revert_changes {
25349 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
25350 buffer.update(cx, |buffer, cx| {
25351 buffer.edit(
25352 changes
25353 .into_iter()
25354 .map(|(range, text)| (range, text.to_string())),
25355 None,
25356 cx,
25357 );
25358 });
25359 }
25360 }
25361 });
25362 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25363 selections.refresh()
25364 });
25365 }
25366
25367 pub fn to_pixel_point(
25368 &mut self,
25369 source: Anchor,
25370 editor_snapshot: &EditorSnapshot,
25371 window: &mut Window,
25372 cx: &mut App,
25373 ) -> Option<gpui::Point<Pixels>> {
25374 let source_point = source.to_display_point(editor_snapshot);
25375 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
25376 }
25377
25378 pub fn display_to_pixel_point(
25379 &mut self,
25380 source: DisplayPoint,
25381 editor_snapshot: &EditorSnapshot,
25382 window: &mut Window,
25383 cx: &mut App,
25384 ) -> Option<gpui::Point<Pixels>> {
25385 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
25386 let text_layout_details = self.text_layout_details(window, cx);
25387 let scroll_top = text_layout_details
25388 .scroll_anchor
25389 .scroll_position(editor_snapshot)
25390 .y;
25391
25392 if source.row().as_f64() < scroll_top.floor() {
25393 return None;
25394 }
25395 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
25396 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
25397 Some(gpui::Point::new(source_x, source_y))
25398 }
25399
25400 pub fn has_visible_completions_menu(&self) -> bool {
25401 !self.edit_prediction_preview_is_active()
25402 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
25403 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
25404 })
25405 }
25406
25407 pub fn register_addon<T: Addon>(&mut self, instance: T) {
25408 if self.mode.is_minimap() {
25409 return;
25410 }
25411 self.addons
25412 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
25413 }
25414
25415 pub fn unregister_addon<T: Addon>(&mut self) {
25416 self.addons.remove(&std::any::TypeId::of::<T>());
25417 }
25418
25419 pub fn addon<T: Addon>(&self) -> Option<&T> {
25420 let type_id = std::any::TypeId::of::<T>();
25421 self.addons
25422 .get(&type_id)
25423 .and_then(|item| item.to_any().downcast_ref::<T>())
25424 }
25425
25426 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
25427 let type_id = std::any::TypeId::of::<T>();
25428 self.addons
25429 .get_mut(&type_id)
25430 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
25431 }
25432
25433 fn character_dimensions(&self, window: &mut Window, cx: &mut App) -> CharacterDimensions {
25434 let text_layout_details = self.text_layout_details(window, cx);
25435 let style = &text_layout_details.editor_style;
25436 let font_id = window.text_system().resolve_font(&style.text.font());
25437 let font_size = style.text.font_size.to_pixels(window.rem_size());
25438 let line_height = style.text.line_height_in_pixels(window.rem_size());
25439 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
25440 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
25441
25442 CharacterDimensions {
25443 em_width,
25444 em_advance,
25445 line_height,
25446 }
25447 }
25448
25449 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
25450 self.load_diff_task.clone()
25451 }
25452
25453 fn read_metadata_from_db(
25454 &mut self,
25455 item_id: u64,
25456 workspace_id: WorkspaceId,
25457 window: &mut Window,
25458 cx: &mut Context<Editor>,
25459 ) {
25460 if self.buffer_kind(cx) == ItemBufferKind::Singleton
25461 && !self.mode.is_minimap()
25462 && WorkspaceSettings::get(None, cx).restore_on_startup
25463 != RestoreOnStartupBehavior::EmptyTab
25464 {
25465 let buffer_snapshot = OnceCell::new();
25466
25467 // Get file path for path-based fold lookup
25468 let file_path: Option<Arc<Path>> =
25469 self.buffer().read(cx).as_singleton().and_then(|buffer| {
25470 project::File::from_dyn(buffer.read(cx).file())
25471 .map(|file| Arc::from(file.abs_path(cx)))
25472 });
25473
25474 // Try file_folds (path-based) first, fallback to editor_folds (migration)
25475 let db = EditorDb::global(cx);
25476 let (folds, needs_migration) = if let Some(ref path) = file_path {
25477 if let Some(folds) = db.get_file_folds(workspace_id, path).log_err()
25478 && !folds.is_empty()
25479 {
25480 (Some(folds), false)
25481 } else if let Some(folds) = db.get_editor_folds(item_id, workspace_id).log_err()
25482 && !folds.is_empty()
25483 {
25484 // Found old editor_folds data, will migrate to file_folds
25485 (Some(folds), true)
25486 } else {
25487 (None, false)
25488 }
25489 } else {
25490 // No file path, try editor_folds as fallback
25491 let folds = db.get_editor_folds(item_id, workspace_id).log_err();
25492 (folds.filter(|f| !f.is_empty()), false)
25493 };
25494
25495 if let Some(folds) = folds {
25496 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25497 let snapshot_len = snapshot.len().0;
25498
25499 // Helper: search for fingerprint in buffer, return offset if found
25500 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
25501 // Ensure we start at a character boundary (defensive)
25502 let search_start = snapshot
25503 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
25504 .0;
25505 let search_end = snapshot_len.saturating_sub(fingerprint.len());
25506
25507 let mut byte_offset = search_start;
25508 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
25509 if byte_offset > search_end {
25510 break;
25511 }
25512 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
25513 return Some(byte_offset);
25514 }
25515 byte_offset += ch.len_utf8();
25516 }
25517 None
25518 };
25519
25520 // Track search position to handle duplicate fingerprints correctly.
25521 // Folds are stored in document order, so we advance after each match.
25522 let mut search_start = 0usize;
25523
25524 // Collect db_folds for migration (only folds with valid fingerprints)
25525 let mut db_folds_for_migration: Vec<(usize, usize, String, String)> = Vec::new();
25526
25527 let valid_folds: Vec<_> = folds
25528 .into_iter()
25529 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
25530 // Skip folds without fingerprints (old data before migration)
25531 let sfp = start_fp?;
25532 let efp = end_fp?;
25533 let efp_len = efp.len();
25534
25535 // Fast path: check if fingerprints match at stored offsets
25536 // Note: end_fp is content BEFORE fold end, so check at (stored_end - efp_len)
25537 let start_matches = stored_start < snapshot_len
25538 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
25539 let efp_check_pos = stored_end.saturating_sub(efp_len);
25540 let end_matches = efp_check_pos >= stored_start
25541 && stored_end <= snapshot_len
25542 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
25543
25544 let (new_start, new_end) = if start_matches && end_matches {
25545 // Offsets unchanged, use stored values
25546 (stored_start, stored_end)
25547 } else if sfp == efp {
25548 // Short fold: identical fingerprints can only match once per search
25549 // Use stored fold length to compute new_end
25550 let new_start = find_fingerprint(&sfp, search_start)?;
25551 let fold_len = stored_end - stored_start;
25552 let new_end = new_start + fold_len;
25553 (new_start, new_end)
25554 } else {
25555 // Slow path: search for fingerprints in buffer
25556 let new_start = find_fingerprint(&sfp, search_start)?;
25557 // Search for end_fp after start, then add efp_len to get actual fold end
25558 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
25559 let new_end = efp_pos + efp_len;
25560 (new_start, new_end)
25561 };
25562
25563 // Advance search position for next fold
25564 search_start = new_end;
25565
25566 // Validate fold makes sense (end must be after start)
25567 if new_end <= new_start {
25568 return None;
25569 }
25570
25571 // Collect for migration if needed
25572 if needs_migration {
25573 db_folds_for_migration.push((new_start, new_end, sfp, efp));
25574 }
25575
25576 Some(
25577 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
25578 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
25579 )
25580 })
25581 .collect();
25582
25583 if !valid_folds.is_empty() {
25584 self.fold_ranges(valid_folds, false, window, cx);
25585
25586 // Migrate from editor_folds to file_folds if we loaded from old table
25587 if needs_migration {
25588 if let Some(ref path) = file_path {
25589 let path = path.clone();
25590 let db = EditorDb::global(cx);
25591 cx.spawn(async move |_, _| {
25592 db.save_file_folds(workspace_id, path, db_folds_for_migration)
25593 .await
25594 .log_err();
25595 })
25596 .detach();
25597 }
25598 }
25599 }
25600 }
25601
25602 if let Some(selections) = db.get_editor_selections(item_id, workspace_id).log_err()
25603 && !selections.is_empty()
25604 {
25605 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25606 // skip adding the initial selection to selection history
25607 self.selection_history.mode = SelectionHistoryMode::Skipping;
25608 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25609 s.select_ranges(selections.into_iter().map(|(start, end)| {
25610 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
25611 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
25612 }));
25613 });
25614 self.selection_history.mode = SelectionHistoryMode::Normal;
25615 };
25616 }
25617
25618 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
25619 }
25620
25621 /// Load folds from the file_folds database table by file path.
25622 /// Used when manually opening a file that was previously closed.
25623 fn load_folds_from_db(
25624 &mut self,
25625 workspace_id: WorkspaceId,
25626 file_path: PathBuf,
25627 window: &mut Window,
25628 cx: &mut Context<Editor>,
25629 ) {
25630 if self.mode.is_minimap()
25631 || WorkspaceSettings::get(None, cx).restore_on_startup
25632 == RestoreOnStartupBehavior::EmptyTab
25633 {
25634 return;
25635 }
25636
25637 let Some(folds) = EditorDb::global(cx)
25638 .get_file_folds(workspace_id, &file_path)
25639 .log_err()
25640 else {
25641 return;
25642 };
25643 if folds.is_empty() {
25644 return;
25645 }
25646
25647 let snapshot = self.buffer.read(cx).snapshot(cx);
25648 let snapshot_len = snapshot.len().0;
25649
25650 // Helper: search for fingerprint in buffer, return offset if found
25651 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
25652 let search_start = snapshot
25653 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
25654 .0;
25655 let search_end = snapshot_len.saturating_sub(fingerprint.len());
25656
25657 let mut byte_offset = search_start;
25658 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
25659 if byte_offset > search_end {
25660 break;
25661 }
25662 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
25663 return Some(byte_offset);
25664 }
25665 byte_offset += ch.len_utf8();
25666 }
25667 None
25668 };
25669
25670 let mut search_start = 0usize;
25671
25672 let valid_folds: Vec<_> = folds
25673 .into_iter()
25674 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
25675 let sfp = start_fp?;
25676 let efp = end_fp?;
25677 let efp_len = efp.len();
25678
25679 let start_matches = stored_start < snapshot_len
25680 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
25681 let efp_check_pos = stored_end.saturating_sub(efp_len);
25682 let end_matches = efp_check_pos >= stored_start
25683 && stored_end <= snapshot_len
25684 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
25685
25686 let (new_start, new_end) = if start_matches && end_matches {
25687 (stored_start, stored_end)
25688 } else if sfp == efp {
25689 let new_start = find_fingerprint(&sfp, search_start)?;
25690 let fold_len = stored_end - stored_start;
25691 let new_end = new_start + fold_len;
25692 (new_start, new_end)
25693 } else {
25694 let new_start = find_fingerprint(&sfp, search_start)?;
25695 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
25696 let new_end = efp_pos + efp_len;
25697 (new_start, new_end)
25698 };
25699
25700 search_start = new_end;
25701
25702 if new_end <= new_start {
25703 return None;
25704 }
25705
25706 Some(
25707 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
25708 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
25709 )
25710 })
25711 .collect();
25712
25713 if !valid_folds.is_empty() {
25714 self.fold_ranges(valid_folds, false, window, cx);
25715 }
25716 }
25717
25718 fn lsp_data_enabled(&self) -> bool {
25719 self.enable_lsp_data && self.mode().is_full()
25720 }
25721
25722 fn update_lsp_data(
25723 &mut self,
25724 for_buffer: Option<BufferId>,
25725 window: &mut Window,
25726 cx: &mut Context<'_, Self>,
25727 ) {
25728 if !self.lsp_data_enabled() {
25729 return;
25730 }
25731
25732 if let Some(buffer_id) = for_buffer {
25733 self.pull_diagnostics(buffer_id, window, cx);
25734 }
25735 self.refresh_semantic_tokens(for_buffer, None, cx);
25736 self.refresh_document_colors(for_buffer, window, cx);
25737 self.refresh_folding_ranges(for_buffer, window, cx);
25738 self.refresh_document_symbols(for_buffer, cx);
25739 }
25740
25741 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
25742 if !self.lsp_data_enabled() {
25743 return;
25744 }
25745 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
25746 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
25747 }
25748 }
25749
25750 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
25751 if !self.lsp_data_enabled() {
25752 return;
25753 }
25754
25755 if !self.registered_buffers.contains_key(&buffer_id)
25756 && let Some(project) = self.project.as_ref()
25757 {
25758 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
25759 project.update(cx, |project, cx| {
25760 self.registered_buffers.insert(
25761 buffer_id,
25762 project.register_buffer_with_language_servers(&buffer, cx),
25763 );
25764 });
25765 } else {
25766 self.registered_buffers.remove(&buffer_id);
25767 }
25768 }
25769 }
25770
25771 fn create_style(&self, cx: &App) -> EditorStyle {
25772 let settings = ThemeSettings::get_global(cx);
25773
25774 let mut text_style = match self.mode {
25775 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
25776 color: cx.theme().colors().editor_foreground,
25777 font_family: settings.ui_font.family.clone(),
25778 font_features: settings.ui_font.features.clone(),
25779 font_fallbacks: settings.ui_font.fallbacks.clone(),
25780 font_size: rems(0.875).into(),
25781 font_weight: settings.ui_font.weight,
25782 line_height: relative(settings.buffer_line_height.value()),
25783 ..Default::default()
25784 },
25785 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
25786 color: cx.theme().colors().editor_foreground,
25787 font_family: settings.buffer_font.family.clone(),
25788 font_features: settings.buffer_font.features.clone(),
25789 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25790 font_size: settings.buffer_font_size(cx).into(),
25791 font_weight: settings.buffer_font.weight,
25792 line_height: relative(settings.buffer_line_height.value()),
25793 ..Default::default()
25794 },
25795 };
25796 if let Some(text_style_refinement) = &self.text_style_refinement {
25797 text_style.refine(text_style_refinement)
25798 }
25799
25800 let background = match self.mode {
25801 EditorMode::SingleLine => cx.theme().system().transparent,
25802 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
25803 EditorMode::Full { .. } => cx.theme().colors().editor_background,
25804 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
25805 };
25806
25807 EditorStyle {
25808 background,
25809 border: cx.theme().colors().border,
25810 local_player: cx.theme().players().local(),
25811 text: text_style,
25812 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
25813 syntax: cx.theme().syntax().clone(),
25814 status: cx.theme().status().clone(),
25815 inlay_hints_style: make_inlay_hints_style(cx),
25816 edit_prediction_styles: make_suggestion_styles(cx),
25817 unnecessary_code_fade: settings.unnecessary_code_fade,
25818 show_underlines: self.diagnostics_enabled(),
25819 }
25820 }
25821
25822 fn breadcrumbs_inner(&self, cx: &App) -> Option<Vec<HighlightedText>> {
25823 let multibuffer = self.buffer().read(cx);
25824 let is_singleton = multibuffer.is_singleton();
25825 let (buffer_id, symbols) = self.outline_symbols_at_cursor.as_ref()?;
25826 let buffer = multibuffer.buffer(*buffer_id)?;
25827
25828 let buffer = buffer.read(cx);
25829 // In a multi-buffer layout, we don't want to include the filename in the breadcrumbs
25830 let mut breadcrumbs = if is_singleton {
25831 let text = self.breadcrumb_header.clone().unwrap_or_else(|| {
25832 buffer
25833 .snapshot()
25834 .resolve_file_path(
25835 self.project
25836 .as_ref()
25837 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
25838 .unwrap_or_default(),
25839 cx,
25840 )
25841 .unwrap_or_else(|| {
25842 if multibuffer.is_singleton() {
25843 multibuffer.title(cx).to_string()
25844 } else {
25845 "untitled".to_string()
25846 }
25847 })
25848 });
25849 vec![HighlightedText {
25850 text: text.into(),
25851 highlights: vec![],
25852 }]
25853 } else {
25854 vec![]
25855 };
25856
25857 breadcrumbs.extend(symbols.iter().map(|symbol| HighlightedText {
25858 text: symbol.text.clone().into(),
25859 highlights: symbol.highlight_ranges.clone(),
25860 }));
25861 Some(breadcrumbs)
25862 }
25863
25864 fn disable_lsp_data(&mut self) {
25865 self.enable_lsp_data = false;
25866 }
25867
25868 fn disable_runnables(&mut self) {
25869 self.enable_runnables = false;
25870 }
25871
25872 fn update_data_on_scroll(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) {
25873 self.register_visible_buffers(cx);
25874 self.colorize_brackets(false, cx);
25875 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
25876 if !self.buffer().read(cx).is_singleton() {
25877 self.update_lsp_data(None, window, cx);
25878 self.refresh_runnables(None, window, cx);
25879 }
25880 }
25881}
25882
25883fn edit_for_markdown_paste<'a>(
25884 buffer: &MultiBufferSnapshot,
25885 range: Range<MultiBufferOffset>,
25886 to_insert: &'a str,
25887 url: Option<url::Url>,
25888) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
25889 if url.is_none() {
25890 return (range, Cow::Borrowed(to_insert));
25891 };
25892
25893 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
25894
25895 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
25896 Cow::Borrowed(to_insert)
25897 } else {
25898 Cow::Owned(format!("[{old_text}]({to_insert})"))
25899 };
25900 (range, new_text)
25901}
25902
25903fn process_completion_for_edit(
25904 completion: &Completion,
25905 intent: CompletionIntent,
25906 buffer: &Entity<Buffer>,
25907 cursor_position: &text::Anchor,
25908 cx: &mut Context<Editor>,
25909) -> CompletionEdit {
25910 let buffer = buffer.read(cx);
25911 let buffer_snapshot = buffer.snapshot();
25912 let (snippet, new_text) = if completion.is_snippet() {
25913 let mut snippet_source = completion.new_text.clone();
25914 // Workaround for typescript language server issues so that methods don't expand within
25915 // strings and functions with type expressions. The previous point is used because the query
25916 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
25917 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
25918 let previous_point = if previous_point.column > 0 {
25919 cursor_position.to_previous_offset(&buffer_snapshot)
25920 } else {
25921 cursor_position.to_offset(&buffer_snapshot)
25922 };
25923 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
25924 && scope.prefers_label_for_snippet_in_completion()
25925 && let Some(label) = completion.label()
25926 && matches!(
25927 completion.kind(),
25928 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
25929 )
25930 {
25931 snippet_source = label;
25932 }
25933 match Snippet::parse(&snippet_source).log_err() {
25934 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
25935 None => (None, completion.new_text.clone()),
25936 }
25937 } else {
25938 (None, completion.new_text.clone())
25939 };
25940
25941 let mut range_to_replace = {
25942 let replace_range = &completion.replace_range;
25943 if let CompletionSource::Lsp {
25944 insert_range: Some(insert_range),
25945 ..
25946 } = &completion.source
25947 {
25948 debug_assert_eq!(
25949 insert_range.start, replace_range.start,
25950 "insert_range and replace_range should start at the same position"
25951 );
25952 debug_assert!(
25953 insert_range
25954 .start
25955 .cmp(cursor_position, &buffer_snapshot)
25956 .is_le(),
25957 "insert_range should start before or at cursor position"
25958 );
25959 debug_assert!(
25960 replace_range
25961 .start
25962 .cmp(cursor_position, &buffer_snapshot)
25963 .is_le(),
25964 "replace_range should start before or at cursor position"
25965 );
25966
25967 let should_replace = match intent {
25968 CompletionIntent::CompleteWithInsert => false,
25969 CompletionIntent::CompleteWithReplace => true,
25970 CompletionIntent::Complete | CompletionIntent::Compose => {
25971 let insert_mode =
25972 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
25973 .completions
25974 .lsp_insert_mode;
25975 match insert_mode {
25976 LspInsertMode::Insert => false,
25977 LspInsertMode::Replace => true,
25978 LspInsertMode::ReplaceSubsequence => {
25979 let mut text_to_replace = buffer.chars_for_range(
25980 buffer.anchor_before(replace_range.start)
25981 ..buffer.anchor_after(replace_range.end),
25982 );
25983 let mut current_needle = text_to_replace.next();
25984 for haystack_ch in completion.label.text.chars() {
25985 if let Some(needle_ch) = current_needle
25986 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
25987 {
25988 current_needle = text_to_replace.next();
25989 }
25990 }
25991 current_needle.is_none()
25992 }
25993 LspInsertMode::ReplaceSuffix => {
25994 if replace_range
25995 .end
25996 .cmp(cursor_position, &buffer_snapshot)
25997 .is_gt()
25998 {
25999 let range_after_cursor = *cursor_position..replace_range.end;
26000 let text_after_cursor = buffer
26001 .text_for_range(
26002 buffer.anchor_before(range_after_cursor.start)
26003 ..buffer.anchor_after(range_after_cursor.end),
26004 )
26005 .collect::<String>()
26006 .to_ascii_lowercase();
26007 completion
26008 .label
26009 .text
26010 .to_ascii_lowercase()
26011 .ends_with(&text_after_cursor)
26012 } else {
26013 true
26014 }
26015 }
26016 }
26017 }
26018 };
26019
26020 if should_replace {
26021 replace_range.clone()
26022 } else {
26023 insert_range.clone()
26024 }
26025 } else {
26026 replace_range.clone()
26027 }
26028 };
26029
26030 if range_to_replace
26031 .end
26032 .cmp(cursor_position, &buffer_snapshot)
26033 .is_lt()
26034 {
26035 range_to_replace.end = *cursor_position;
26036 }
26037
26038 let replace_range = range_to_replace.to_offset(buffer);
26039 CompletionEdit {
26040 new_text,
26041 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
26042 snippet,
26043 }
26044}
26045
26046struct CompletionEdit {
26047 new_text: String,
26048 replace_range: Range<BufferOffset>,
26049 snippet: Option<Snippet>,
26050}
26051
26052fn comment_delimiter_for_newline(
26053 start_point: &Point,
26054 buffer: &MultiBufferSnapshot,
26055 language: &LanguageScope,
26056) -> Option<Arc<str>> {
26057 let delimiters = language.line_comment_prefixes();
26058 let max_len_of_delimiter = delimiters.iter().map(|delimiter| delimiter.len()).max()?;
26059 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
26060
26061 let num_of_whitespaces = snapshot
26062 .chars_for_range(range.clone())
26063 .take_while(|c| c.is_whitespace())
26064 .count();
26065 let comment_candidate = snapshot
26066 .chars_for_range(range.clone())
26067 .skip(num_of_whitespaces)
26068 .take(max_len_of_delimiter + 2)
26069 .collect::<String>();
26070 let (delimiter, trimmed_len, is_repl) = delimiters
26071 .iter()
26072 .filter_map(|delimiter| {
26073 let prefix = delimiter.trim_end();
26074 if comment_candidate.starts_with(prefix) {
26075 let is_repl = if let Some(stripped_comment) = comment_candidate.strip_prefix(prefix)
26076 {
26077 stripped_comment.starts_with(" %%")
26078 } else {
26079 false
26080 };
26081 Some((delimiter, prefix.len(), is_repl))
26082 } else {
26083 None
26084 }
26085 })
26086 .max_by_key(|(_, len, _)| *len)?;
26087
26088 if let Some(BlockCommentConfig {
26089 start: block_start, ..
26090 }) = language.block_comment()
26091 {
26092 let block_start_trimmed = block_start.trim_end();
26093 if block_start_trimmed.starts_with(delimiter.trim_end()) {
26094 let line_content = snapshot
26095 .chars_for_range(range.clone())
26096 .skip(num_of_whitespaces)
26097 .take(block_start_trimmed.len())
26098 .collect::<String>();
26099
26100 if line_content.starts_with(block_start_trimmed) {
26101 return None;
26102 }
26103 }
26104 }
26105
26106 let cursor_is_placed_after_comment_marker =
26107 num_of_whitespaces + trimmed_len <= start_point.column as usize;
26108 if cursor_is_placed_after_comment_marker {
26109 if !is_repl {
26110 return Some(delimiter.clone());
26111 }
26112
26113 let line_content_after_cursor: String = snapshot
26114 .chars_for_range(range)
26115 .skip(start_point.column as usize)
26116 .collect();
26117
26118 if line_content_after_cursor.trim().is_empty() {
26119 return None;
26120 } else {
26121 return Some(delimiter.clone());
26122 }
26123 } else {
26124 None
26125 }
26126}
26127
26128fn documentation_delimiter_for_newline(
26129 start_point: &Point,
26130 buffer: &MultiBufferSnapshot,
26131 language: &LanguageScope,
26132 newline_config: &mut NewlineConfig,
26133) -> Option<Arc<str>> {
26134 let BlockCommentConfig {
26135 start: start_tag,
26136 end: end_tag,
26137 prefix: delimiter,
26138 tab_size: len,
26139 } = language.documentation_comment()?;
26140 let is_within_block_comment = buffer
26141 .language_scope_at(*start_point)
26142 .is_some_and(|scope| scope.override_name() == Some("comment"));
26143 if !is_within_block_comment {
26144 return None;
26145 }
26146
26147 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
26148
26149 let num_of_whitespaces = snapshot
26150 .chars_for_range(range.clone())
26151 .take_while(|c| c.is_whitespace())
26152 .count();
26153
26154 // It is safe to use a column from MultiBufferPoint in context of a single buffer ranges, because we're only ever looking at a single line at a time.
26155 let column = start_point.column;
26156 let cursor_is_after_start_tag = {
26157 let start_tag_len = start_tag.len();
26158 let start_tag_line = snapshot
26159 .chars_for_range(range.clone())
26160 .skip(num_of_whitespaces)
26161 .take(start_tag_len)
26162 .collect::<String>();
26163 if start_tag_line.starts_with(start_tag.as_ref()) {
26164 num_of_whitespaces + start_tag_len <= column as usize
26165 } else {
26166 false
26167 }
26168 };
26169
26170 let cursor_is_after_delimiter = {
26171 let delimiter_trim = delimiter.trim_end();
26172 let delimiter_line = snapshot
26173 .chars_for_range(range.clone())
26174 .skip(num_of_whitespaces)
26175 .take(delimiter_trim.len())
26176 .collect::<String>();
26177 if delimiter_line.starts_with(delimiter_trim) {
26178 num_of_whitespaces + delimiter_trim.len() <= column as usize
26179 } else {
26180 false
26181 }
26182 };
26183
26184 let mut needs_extra_line = false;
26185 let mut extra_line_additional_indent = IndentSize::spaces(0);
26186
26187 let cursor_is_before_end_tag_if_exists = {
26188 let mut char_position = 0u32;
26189 let mut end_tag_offset = None;
26190
26191 'outer: for chunk in snapshot.text_for_range(range) {
26192 if let Some(byte_pos) = chunk.find(&**end_tag) {
26193 let chars_before_match = chunk[..byte_pos].chars().count() as u32;
26194 end_tag_offset = Some(char_position + chars_before_match);
26195 break 'outer;
26196 }
26197 char_position += chunk.chars().count() as u32;
26198 }
26199
26200 if let Some(end_tag_offset) = end_tag_offset {
26201 let cursor_is_before_end_tag = column <= end_tag_offset;
26202 if cursor_is_after_start_tag {
26203 if cursor_is_before_end_tag {
26204 needs_extra_line = true;
26205 }
26206 let cursor_is_at_start_of_end_tag = column == end_tag_offset;
26207 if cursor_is_at_start_of_end_tag {
26208 extra_line_additional_indent.len = *len;
26209 }
26210 }
26211 cursor_is_before_end_tag
26212 } else {
26213 true
26214 }
26215 };
26216
26217 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
26218 && cursor_is_before_end_tag_if_exists
26219 {
26220 let additional_indent = if cursor_is_after_start_tag {
26221 IndentSize::spaces(*len)
26222 } else {
26223 IndentSize::spaces(0)
26224 };
26225
26226 *newline_config = NewlineConfig::Newline {
26227 additional_indent,
26228 extra_line_additional_indent: if needs_extra_line {
26229 Some(extra_line_additional_indent)
26230 } else {
26231 None
26232 },
26233 prevent_auto_indent: true,
26234 };
26235 Some(delimiter.clone())
26236 } else {
26237 None
26238 }
26239}
26240
26241const ORDERED_LIST_MAX_MARKER_LEN: usize = 16;
26242
26243fn list_delimiter_for_newline(
26244 start_point: &Point,
26245 buffer: &MultiBufferSnapshot,
26246 language: &LanguageScope,
26247 newline_config: &mut NewlineConfig,
26248) -> Option<Arc<str>> {
26249 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
26250
26251 let num_of_whitespaces = snapshot
26252 .chars_for_range(range.clone())
26253 .take_while(|c| c.is_whitespace())
26254 .count();
26255
26256 let task_list_entries: Vec<_> = language
26257 .task_list()
26258 .into_iter()
26259 .flat_map(|config| {
26260 config
26261 .prefixes
26262 .iter()
26263 .map(|prefix| (prefix.as_ref(), config.continuation.as_ref()))
26264 })
26265 .collect();
26266 let unordered_list_entries: Vec<_> = language
26267 .unordered_list()
26268 .iter()
26269 .map(|marker| (marker.as_ref(), marker.as_ref()))
26270 .collect();
26271
26272 let all_entries: Vec<_> = task_list_entries
26273 .into_iter()
26274 .chain(unordered_list_entries)
26275 .collect();
26276
26277 if let Some(max_prefix_len) = all_entries.iter().map(|(p, _)| p.len()).max() {
26278 let candidate: String = snapshot
26279 .chars_for_range(range.clone())
26280 .skip(num_of_whitespaces)
26281 .take(max_prefix_len)
26282 .collect();
26283
26284 if let Some((prefix, continuation)) = all_entries
26285 .iter()
26286 .filter(|(prefix, _)| candidate.starts_with(*prefix))
26287 .max_by_key(|(prefix, _)| prefix.len())
26288 {
26289 let end_of_prefix = num_of_whitespaces + prefix.len();
26290 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
26291 let has_content_after_marker = snapshot
26292 .chars_for_range(range)
26293 .skip(end_of_prefix)
26294 .any(|c| !c.is_whitespace());
26295
26296 if has_content_after_marker && cursor_is_after_prefix {
26297 return Some((*continuation).into());
26298 }
26299
26300 if start_point.column as usize == end_of_prefix {
26301 if num_of_whitespaces == 0 {
26302 *newline_config = NewlineConfig::ClearCurrentLine;
26303 } else {
26304 *newline_config = NewlineConfig::UnindentCurrentLine {
26305 continuation: (*continuation).into(),
26306 };
26307 }
26308 }
26309
26310 return None;
26311 }
26312 }
26313
26314 let candidate: String = snapshot
26315 .chars_for_range(range.clone())
26316 .skip(num_of_whitespaces)
26317 .take(ORDERED_LIST_MAX_MARKER_LEN)
26318 .collect();
26319
26320 for ordered_config in language.ordered_list() {
26321 let regex = match Regex::new(&ordered_config.pattern) {
26322 Ok(r) => r,
26323 Err(_) => continue,
26324 };
26325
26326 if let Some(captures) = regex.captures(&candidate) {
26327 let full_match = captures.get(0)?;
26328 let marker_len = full_match.len();
26329 let end_of_prefix = num_of_whitespaces + marker_len;
26330 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
26331
26332 let has_content_after_marker = snapshot
26333 .chars_for_range(range)
26334 .skip(end_of_prefix)
26335 .any(|c| !c.is_whitespace());
26336
26337 if has_content_after_marker && cursor_is_after_prefix {
26338 let number: u32 = captures.get(1)?.as_str().parse().ok()?;
26339 let continuation = ordered_config
26340 .format
26341 .replace("{1}", &(number + 1).to_string());
26342 return Some(continuation.into());
26343 }
26344
26345 if start_point.column as usize == end_of_prefix {
26346 let continuation = ordered_config.format.replace("{1}", "1");
26347 if num_of_whitespaces == 0 {
26348 *newline_config = NewlineConfig::ClearCurrentLine;
26349 } else {
26350 *newline_config = NewlineConfig::UnindentCurrentLine {
26351 continuation: continuation.into(),
26352 };
26353 }
26354 }
26355
26356 return None;
26357 }
26358 }
26359
26360 None
26361}
26362
26363fn is_list_prefix_row(
26364 row: MultiBufferRow,
26365 buffer: &MultiBufferSnapshot,
26366 language: &LanguageScope,
26367) -> bool {
26368 let Some((snapshot, range)) = buffer.buffer_line_for_row(row) else {
26369 return false;
26370 };
26371
26372 let num_of_whitespaces = snapshot
26373 .chars_for_range(range.clone())
26374 .take_while(|c| c.is_whitespace())
26375 .count();
26376
26377 let task_list_prefixes: Vec<_> = language
26378 .task_list()
26379 .into_iter()
26380 .flat_map(|config| {
26381 config
26382 .prefixes
26383 .iter()
26384 .map(|p| p.as_ref())
26385 .collect::<Vec<_>>()
26386 })
26387 .collect();
26388 let unordered_list_markers: Vec<_> = language
26389 .unordered_list()
26390 .iter()
26391 .map(|marker| marker.as_ref())
26392 .collect();
26393 let all_prefixes: Vec<_> = task_list_prefixes
26394 .into_iter()
26395 .chain(unordered_list_markers)
26396 .collect();
26397 if let Some(max_prefix_len) = all_prefixes.iter().map(|p| p.len()).max() {
26398 let candidate: String = snapshot
26399 .chars_for_range(range.clone())
26400 .skip(num_of_whitespaces)
26401 .take(max_prefix_len)
26402 .collect();
26403 if all_prefixes
26404 .iter()
26405 .any(|prefix| candidate.starts_with(*prefix))
26406 {
26407 return true;
26408 }
26409 }
26410
26411 let ordered_list_candidate: String = snapshot
26412 .chars_for_range(range)
26413 .skip(num_of_whitespaces)
26414 .take(ORDERED_LIST_MAX_MARKER_LEN)
26415 .collect();
26416 for ordered_config in language.ordered_list() {
26417 let regex = match Regex::new(&ordered_config.pattern) {
26418 Ok(r) => r,
26419 Err(_) => continue,
26420 };
26421 if let Some(captures) = regex.captures(&ordered_list_candidate) {
26422 return captures.get(0).is_some();
26423 }
26424 }
26425
26426 false
26427}
26428
26429#[derive(Debug)]
26430enum NewlineConfig {
26431 /// Insert newline with optional additional indent and optional extra blank line
26432 Newline {
26433 additional_indent: IndentSize,
26434 extra_line_additional_indent: Option<IndentSize>,
26435 prevent_auto_indent: bool,
26436 },
26437 /// Clear the current line
26438 ClearCurrentLine,
26439 /// Unindent the current line and add continuation
26440 UnindentCurrentLine { continuation: Arc<str> },
26441}
26442
26443impl NewlineConfig {
26444 fn has_extra_line(&self) -> bool {
26445 matches!(
26446 self,
26447 Self::Newline {
26448 extra_line_additional_indent: Some(_),
26449 ..
26450 }
26451 )
26452 }
26453
26454 fn insert_extra_newline_brackets(
26455 buffer: &MultiBufferSnapshot,
26456 range: Range<MultiBufferOffset>,
26457 language: &language::LanguageScope,
26458 ) -> bool {
26459 let leading_whitespace_len = buffer
26460 .reversed_chars_at(range.start)
26461 .take_while(|c| c.is_whitespace() && *c != '\n')
26462 .map(|c| c.len_utf8())
26463 .sum::<usize>();
26464 let trailing_whitespace_len = buffer
26465 .chars_at(range.end)
26466 .take_while(|c| c.is_whitespace() && *c != '\n')
26467 .map(|c| c.len_utf8())
26468 .sum::<usize>();
26469 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
26470
26471 language.brackets().any(|(pair, enabled)| {
26472 let pair_start = pair.start.trim_end();
26473 let pair_end = pair.end.trim_start();
26474
26475 enabled
26476 && pair.newline
26477 && buffer.contains_str_at(range.end, pair_end)
26478 && buffer.contains_str_at(
26479 range.start.saturating_sub_usize(pair_start.len()),
26480 pair_start,
26481 )
26482 })
26483 }
26484
26485 fn insert_extra_newline_tree_sitter(
26486 buffer: &MultiBufferSnapshot,
26487 range: Range<MultiBufferOffset>,
26488 ) -> bool {
26489 let (buffer, range) = match buffer
26490 .range_to_buffer_ranges(range.start..=range.end)
26491 .as_slice()
26492 {
26493 [(buffer, range, _)] => (*buffer, range.clone()),
26494 _ => return false,
26495 };
26496 let pair = {
26497 let mut result: Option<BracketMatch<usize>> = None;
26498
26499 for pair in buffer
26500 .all_bracket_ranges(range.start.0..range.end.0)
26501 .filter(move |pair| {
26502 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
26503 })
26504 {
26505 let len = pair.close_range.end - pair.open_range.start;
26506
26507 if let Some(existing) = &result {
26508 let existing_len = existing.close_range.end - existing.open_range.start;
26509 if len > existing_len {
26510 continue;
26511 }
26512 }
26513
26514 result = Some(pair);
26515 }
26516
26517 result
26518 };
26519 let Some(pair) = pair else {
26520 return false;
26521 };
26522 pair.newline_only
26523 && buffer
26524 .chars_for_range(pair.open_range.end..range.start.0)
26525 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
26526 .all(|c| c.is_whitespace() && c != '\n')
26527 }
26528}
26529
26530fn update_uncommitted_diff_for_buffer(
26531 editor: Entity<Editor>,
26532 project: &Entity<Project>,
26533 buffers: impl IntoIterator<Item = Entity<Buffer>>,
26534 buffer: Entity<MultiBuffer>,
26535 cx: &mut App,
26536) -> Task<()> {
26537 let mut tasks = Vec::new();
26538 project.update(cx, |project, cx| {
26539 for buffer in buffers {
26540 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
26541 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
26542 }
26543 }
26544 });
26545 cx.spawn(async move |cx| {
26546 let diffs = future::join_all(tasks).await;
26547 if editor.read_with(cx, |editor, _cx| editor.temporary_diff_override) {
26548 return;
26549 }
26550
26551 buffer.update(cx, |buffer, cx| {
26552 for diff in diffs.into_iter().flatten() {
26553 buffer.add_diff(diff, cx);
26554 }
26555 });
26556 })
26557}
26558
26559fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
26560 let tab_size = tab_size.get() as usize;
26561 let mut width = offset;
26562
26563 for ch in text.chars() {
26564 width += if ch == '\t' {
26565 tab_size - (width % tab_size)
26566 } else {
26567 1
26568 };
26569 }
26570
26571 width - offset
26572}
26573
26574#[cfg(test)]
26575mod tests {
26576 use super::*;
26577
26578 #[test]
26579 fn test_string_size_with_expanded_tabs() {
26580 let nz = |val| NonZeroU32::new(val).unwrap();
26581 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
26582 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
26583 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
26584 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
26585 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
26586 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
26587 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
26588 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
26589 }
26590}
26591
26592/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
26593struct WordBreakingTokenizer<'a> {
26594 input: &'a str,
26595}
26596
26597impl<'a> WordBreakingTokenizer<'a> {
26598 fn new(input: &'a str) -> Self {
26599 Self { input }
26600 }
26601}
26602
26603fn is_char_ideographic(ch: char) -> bool {
26604 use unicode_script::Script::*;
26605 use unicode_script::UnicodeScript;
26606 matches!(ch.script(), Han | Tangut | Yi)
26607}
26608
26609fn is_grapheme_ideographic(text: &str) -> bool {
26610 text.chars().any(is_char_ideographic)
26611}
26612
26613fn is_grapheme_whitespace(text: &str) -> bool {
26614 text.chars().any(|x| x.is_whitespace())
26615}
26616
26617fn should_stay_with_preceding_ideograph(text: &str) -> bool {
26618 text.chars()
26619 .next()
26620 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
26621}
26622
26623#[derive(PartialEq, Eq, Debug, Clone, Copy)]
26624enum WordBreakToken<'a> {
26625 Word { token: &'a str, grapheme_len: usize },
26626 InlineWhitespace { token: &'a str, grapheme_len: usize },
26627 Newline,
26628}
26629
26630impl<'a> Iterator for WordBreakingTokenizer<'a> {
26631 /// Yields a span, the count of graphemes in the token, and whether it was
26632 /// whitespace. Note that it also breaks at word boundaries.
26633 type Item = WordBreakToken<'a>;
26634
26635 fn next(&mut self) -> Option<Self::Item> {
26636 use unicode_segmentation::UnicodeSegmentation;
26637 if self.input.is_empty() {
26638 return None;
26639 }
26640
26641 let mut iter = self.input.graphemes(true).peekable();
26642 let mut offset = 0;
26643 let mut grapheme_len = 0;
26644 if let Some(first_grapheme) = iter.next() {
26645 let is_newline = first_grapheme == "\n";
26646 let is_whitespace = is_grapheme_whitespace(first_grapheme);
26647 offset += first_grapheme.len();
26648 grapheme_len += 1;
26649 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
26650 if let Some(grapheme) = iter.peek().copied()
26651 && should_stay_with_preceding_ideograph(grapheme)
26652 {
26653 offset += grapheme.len();
26654 grapheme_len += 1;
26655 }
26656 } else {
26657 let mut words = self.input[offset..].split_word_bound_indices().peekable();
26658 let mut next_word_bound = words.peek().copied();
26659 if next_word_bound.is_some_and(|(i, _)| i == 0) {
26660 next_word_bound = words.next();
26661 }
26662 while let Some(grapheme) = iter.peek().copied() {
26663 if next_word_bound.is_some_and(|(i, _)| i == offset) {
26664 break;
26665 };
26666 if is_grapheme_whitespace(grapheme) != is_whitespace
26667 || (grapheme == "\n") != is_newline
26668 {
26669 break;
26670 };
26671 offset += grapheme.len();
26672 grapheme_len += 1;
26673 iter.next();
26674 }
26675 }
26676 let token = &self.input[..offset];
26677 self.input = &self.input[offset..];
26678 if token == "\n" {
26679 Some(WordBreakToken::Newline)
26680 } else if is_whitespace {
26681 Some(WordBreakToken::InlineWhitespace {
26682 token,
26683 grapheme_len,
26684 })
26685 } else {
26686 Some(WordBreakToken::Word {
26687 token,
26688 grapheme_len,
26689 })
26690 }
26691 } else {
26692 None
26693 }
26694 }
26695}
26696
26697#[test]
26698fn test_word_breaking_tokenizer() {
26699 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
26700 ("", &[]),
26701 (" ", &[whitespace(" ", 2)]),
26702 ("Ʒ", &[word("Ʒ", 1)]),
26703 ("Ǽ", &[word("Ǽ", 1)]),
26704 ("⋑", &[word("⋑", 1)]),
26705 ("⋑⋑", &[word("⋑⋑", 2)]),
26706 (
26707 "原理,进而",
26708 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
26709 ),
26710 (
26711 "hello world",
26712 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
26713 ),
26714 (
26715 "hello, world",
26716 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
26717 ),
26718 (
26719 " hello world",
26720 &[
26721 whitespace(" ", 2),
26722 word("hello", 5),
26723 whitespace(" ", 1),
26724 word("world", 5),
26725 ],
26726 ),
26727 (
26728 "这是什么 \n 钢笔",
26729 &[
26730 word("这", 1),
26731 word("是", 1),
26732 word("什", 1),
26733 word("么", 1),
26734 whitespace(" ", 1),
26735 newline(),
26736 whitespace(" ", 1),
26737 word("钢", 1),
26738 word("笔", 1),
26739 ],
26740 ),
26741 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
26742 ];
26743
26744 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26745 WordBreakToken::Word {
26746 token,
26747 grapheme_len,
26748 }
26749 }
26750
26751 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26752 WordBreakToken::InlineWhitespace {
26753 token,
26754 grapheme_len,
26755 }
26756 }
26757
26758 fn newline() -> WordBreakToken<'static> {
26759 WordBreakToken::Newline
26760 }
26761
26762 for (input, result) in tests {
26763 assert_eq!(
26764 WordBreakingTokenizer::new(input)
26765 .collect::<Vec<_>>()
26766 .as_slice(),
26767 *result,
26768 );
26769 }
26770}
26771
26772fn wrap_with_prefix(
26773 first_line_prefix: String,
26774 subsequent_lines_prefix: String,
26775 unwrapped_text: String,
26776 wrap_column: usize,
26777 tab_size: NonZeroU32,
26778 preserve_existing_whitespace: bool,
26779) -> String {
26780 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
26781 let subsequent_lines_prefix_len =
26782 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
26783 let mut wrapped_text = String::new();
26784 let mut current_line = first_line_prefix;
26785 let mut is_first_line = true;
26786
26787 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
26788 let mut current_line_len = first_line_prefix_len;
26789 let mut in_whitespace = false;
26790 for token in tokenizer {
26791 let have_preceding_whitespace = in_whitespace;
26792 match token {
26793 WordBreakToken::Word {
26794 token,
26795 grapheme_len,
26796 } => {
26797 in_whitespace = false;
26798 let current_prefix_len = if is_first_line {
26799 first_line_prefix_len
26800 } else {
26801 subsequent_lines_prefix_len
26802 };
26803 if current_line_len + grapheme_len > wrap_column
26804 && current_line_len != current_prefix_len
26805 {
26806 wrapped_text.push_str(current_line.trim_end());
26807 wrapped_text.push('\n');
26808 is_first_line = false;
26809 current_line = subsequent_lines_prefix.clone();
26810 current_line_len = subsequent_lines_prefix_len;
26811 }
26812 current_line.push_str(token);
26813 current_line_len += grapheme_len;
26814 }
26815 WordBreakToken::InlineWhitespace {
26816 mut token,
26817 mut grapheme_len,
26818 } => {
26819 in_whitespace = true;
26820 if have_preceding_whitespace && !preserve_existing_whitespace {
26821 continue;
26822 }
26823 if !preserve_existing_whitespace {
26824 // Keep a single whitespace grapheme as-is
26825 if let Some(first) =
26826 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
26827 {
26828 token = first;
26829 } else {
26830 token = " ";
26831 }
26832 grapheme_len = 1;
26833 }
26834 let current_prefix_len = if is_first_line {
26835 first_line_prefix_len
26836 } else {
26837 subsequent_lines_prefix_len
26838 };
26839 if current_line_len + grapheme_len > wrap_column {
26840 wrapped_text.push_str(current_line.trim_end());
26841 wrapped_text.push('\n');
26842 is_first_line = false;
26843 current_line = subsequent_lines_prefix.clone();
26844 current_line_len = subsequent_lines_prefix_len;
26845 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
26846 current_line.push_str(token);
26847 current_line_len += grapheme_len;
26848 }
26849 }
26850 WordBreakToken::Newline => {
26851 in_whitespace = true;
26852 let current_prefix_len = if is_first_line {
26853 first_line_prefix_len
26854 } else {
26855 subsequent_lines_prefix_len
26856 };
26857 if preserve_existing_whitespace {
26858 wrapped_text.push_str(current_line.trim_end());
26859 wrapped_text.push('\n');
26860 is_first_line = false;
26861 current_line = subsequent_lines_prefix.clone();
26862 current_line_len = subsequent_lines_prefix_len;
26863 } else if have_preceding_whitespace {
26864 continue;
26865 } else if current_line_len + 1 > wrap_column
26866 && current_line_len != current_prefix_len
26867 {
26868 wrapped_text.push_str(current_line.trim_end());
26869 wrapped_text.push('\n');
26870 is_first_line = false;
26871 current_line = subsequent_lines_prefix.clone();
26872 current_line_len = subsequent_lines_prefix_len;
26873 } else if current_line_len != current_prefix_len {
26874 current_line.push(' ');
26875 current_line_len += 1;
26876 }
26877 }
26878 }
26879 }
26880
26881 if !current_line.is_empty() {
26882 wrapped_text.push_str(¤t_line);
26883 }
26884 wrapped_text
26885}
26886
26887#[test]
26888fn test_wrap_with_prefix() {
26889 assert_eq!(
26890 wrap_with_prefix(
26891 "# ".to_string(),
26892 "# ".to_string(),
26893 "abcdefg".to_string(),
26894 4,
26895 NonZeroU32::new(4).unwrap(),
26896 false,
26897 ),
26898 "# abcdefg"
26899 );
26900 assert_eq!(
26901 wrap_with_prefix(
26902 "".to_string(),
26903 "".to_string(),
26904 "\thello world".to_string(),
26905 8,
26906 NonZeroU32::new(4).unwrap(),
26907 false,
26908 ),
26909 "hello\nworld"
26910 );
26911 assert_eq!(
26912 wrap_with_prefix(
26913 "// ".to_string(),
26914 "// ".to_string(),
26915 "xx \nyy zz aa bb cc".to_string(),
26916 12,
26917 NonZeroU32::new(4).unwrap(),
26918 false,
26919 ),
26920 "// xx yy zz\n// aa bb cc"
26921 );
26922 assert_eq!(
26923 wrap_with_prefix(
26924 String::new(),
26925 String::new(),
26926 "这是什么 \n 钢笔".to_string(),
26927 3,
26928 NonZeroU32::new(4).unwrap(),
26929 false,
26930 ),
26931 "这是什\n么 钢\n笔"
26932 );
26933 assert_eq!(
26934 wrap_with_prefix(
26935 String::new(),
26936 String::new(),
26937 format!("foo{}bar", '\u{2009}'), // thin space
26938 80,
26939 NonZeroU32::new(4).unwrap(),
26940 false,
26941 ),
26942 format!("foo{}bar", '\u{2009}')
26943 );
26944}
26945
26946pub trait CollaborationHub {
26947 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
26948 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
26949 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
26950}
26951
26952impl CollaborationHub for Entity<Project> {
26953 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
26954 self.read(cx).collaborators()
26955 }
26956
26957 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
26958 self.read(cx).user_store().read(cx).participant_indices()
26959 }
26960
26961 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
26962 let this = self.read(cx);
26963 let user_ids = this.collaborators().values().map(|c| c.user_id);
26964 this.user_store().read(cx).participant_names(user_ids, cx)
26965 }
26966}
26967
26968pub trait SemanticsProvider {
26969 fn hover(
26970 &self,
26971 buffer: &Entity<Buffer>,
26972 position: text::Anchor,
26973 cx: &mut App,
26974 ) -> Option<Task<Option<Vec<project::Hover>>>>;
26975
26976 fn inline_values(
26977 &self,
26978 buffer_handle: Entity<Buffer>,
26979 range: Range<text::Anchor>,
26980 cx: &mut App,
26981 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
26982
26983 fn applicable_inlay_chunks(
26984 &self,
26985 buffer: &Entity<Buffer>,
26986 ranges: &[Range<text::Anchor>],
26987 cx: &mut App,
26988 ) -> Vec<Range<BufferRow>>;
26989
26990 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
26991
26992 fn inlay_hints(
26993 &self,
26994 invalidate: InvalidationStrategy,
26995 buffer: Entity<Buffer>,
26996 ranges: Vec<Range<text::Anchor>>,
26997 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
26998 cx: &mut App,
26999 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
27000
27001 fn semantic_tokens(
27002 &self,
27003 buffer: Entity<Buffer>,
27004 refresh: Option<RefreshForServer>,
27005 cx: &mut App,
27006 ) -> Option<Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>>>;
27007
27008 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
27009
27010 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
27011
27012 fn document_highlights(
27013 &self,
27014 buffer: &Entity<Buffer>,
27015 position: text::Anchor,
27016 cx: &mut App,
27017 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
27018
27019 fn definitions(
27020 &self,
27021 buffer: &Entity<Buffer>,
27022 position: text::Anchor,
27023 kind: GotoDefinitionKind,
27024 cx: &mut App,
27025 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
27026
27027 fn range_for_rename(
27028 &self,
27029 buffer: &Entity<Buffer>,
27030 position: text::Anchor,
27031 cx: &mut App,
27032 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
27033
27034 fn perform_rename(
27035 &self,
27036 buffer: &Entity<Buffer>,
27037 position: text::Anchor,
27038 new_name: String,
27039 cx: &mut App,
27040 ) -> Option<Task<Result<ProjectTransaction>>>;
27041}
27042
27043pub trait CompletionProvider {
27044 fn completions(
27045 &self,
27046 excerpt_id: ExcerptId,
27047 buffer: &Entity<Buffer>,
27048 buffer_position: text::Anchor,
27049 trigger: CompletionContext,
27050 window: &mut Window,
27051 cx: &mut Context<Editor>,
27052 ) -> Task<Result<Vec<CompletionResponse>>>;
27053
27054 fn resolve_completions(
27055 &self,
27056 _buffer: Entity<Buffer>,
27057 _completion_indices: Vec<usize>,
27058 _completions: Rc<RefCell<Box<[Completion]>>>,
27059 _cx: &mut Context<Editor>,
27060 ) -> Task<Result<bool>> {
27061 Task::ready(Ok(false))
27062 }
27063
27064 fn apply_additional_edits_for_completion(
27065 &self,
27066 _buffer: Entity<Buffer>,
27067 _completions: Rc<RefCell<Box<[Completion]>>>,
27068 _completion_index: usize,
27069 _push_to_history: bool,
27070 _all_commit_ranges: Vec<Range<language::Anchor>>,
27071 _cx: &mut Context<Editor>,
27072 ) -> Task<Result<Option<language::Transaction>>> {
27073 Task::ready(Ok(None))
27074 }
27075
27076 fn is_completion_trigger(
27077 &self,
27078 buffer: &Entity<Buffer>,
27079 position: language::Anchor,
27080 text: &str,
27081 trigger_in_words: bool,
27082 cx: &mut Context<Editor>,
27083 ) -> bool;
27084
27085 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
27086
27087 fn sort_completions(&self) -> bool {
27088 true
27089 }
27090
27091 fn filter_completions(&self) -> bool {
27092 true
27093 }
27094
27095 fn show_snippets(&self) -> bool {
27096 false
27097 }
27098}
27099
27100pub trait CodeActionProvider {
27101 fn id(&self) -> Arc<str>;
27102
27103 fn code_actions(
27104 &self,
27105 buffer: &Entity<Buffer>,
27106 range: Range<text::Anchor>,
27107 window: &mut Window,
27108 cx: &mut App,
27109 ) -> Task<Result<Vec<CodeAction>>>;
27110
27111 fn apply_code_action(
27112 &self,
27113 buffer_handle: Entity<Buffer>,
27114 action: CodeAction,
27115 excerpt_id: ExcerptId,
27116 push_to_history: bool,
27117 window: &mut Window,
27118 cx: &mut App,
27119 ) -> Task<Result<ProjectTransaction>>;
27120}
27121
27122impl CodeActionProvider for Entity<Project> {
27123 fn id(&self) -> Arc<str> {
27124 "project".into()
27125 }
27126
27127 fn code_actions(
27128 &self,
27129 buffer: &Entity<Buffer>,
27130 range: Range<text::Anchor>,
27131 _window: &mut Window,
27132 cx: &mut App,
27133 ) -> Task<Result<Vec<CodeAction>>> {
27134 self.update(cx, |project, cx| {
27135 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
27136 let code_actions = project.code_actions(buffer, range, None, cx);
27137 cx.background_spawn(async move {
27138 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
27139 Ok(code_lens_actions
27140 .context("code lens fetch")?
27141 .into_iter()
27142 .flatten()
27143 .chain(
27144 code_actions
27145 .context("code action fetch")?
27146 .into_iter()
27147 .flatten(),
27148 )
27149 .collect())
27150 })
27151 })
27152 }
27153
27154 fn apply_code_action(
27155 &self,
27156 buffer_handle: Entity<Buffer>,
27157 action: CodeAction,
27158 _excerpt_id: ExcerptId,
27159 push_to_history: bool,
27160 _window: &mut Window,
27161 cx: &mut App,
27162 ) -> Task<Result<ProjectTransaction>> {
27163 self.update(cx, |project, cx| {
27164 project.apply_code_action(buffer_handle, action, push_to_history, cx)
27165 })
27166 }
27167}
27168
27169fn snippet_completions(
27170 project: &Project,
27171 buffer: &Entity<Buffer>,
27172 buffer_anchor: text::Anchor,
27173 classifier: CharClassifier,
27174 cx: &mut App,
27175) -> Task<Result<CompletionResponse>> {
27176 let languages = buffer.read(cx).languages_at(buffer_anchor);
27177 let snippet_store = project.snippets().read(cx);
27178
27179 let scopes: Vec<_> = languages
27180 .iter()
27181 .filter_map(|language| {
27182 let language_name = language.lsp_id();
27183 let snippets = snippet_store.snippets_for(Some(language_name), cx);
27184
27185 if snippets.is_empty() {
27186 None
27187 } else {
27188 Some((language.default_scope(), snippets))
27189 }
27190 })
27191 .collect();
27192
27193 if scopes.is_empty() {
27194 return Task::ready(Ok(CompletionResponse {
27195 completions: vec![],
27196 display_options: CompletionDisplayOptions::default(),
27197 is_incomplete: false,
27198 }));
27199 }
27200
27201 let snapshot = buffer.read(cx).text_snapshot();
27202 let executor = cx.background_executor().clone();
27203
27204 cx.background_spawn(async move {
27205 let is_word_char = |c| classifier.is_word(c);
27206
27207 let mut is_incomplete = false;
27208 let mut completions: Vec<Completion> = Vec::new();
27209
27210 const MAX_PREFIX_LEN: usize = 128;
27211 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
27212 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
27213 let window_start = snapshot.clip_offset(window_start, Bias::Left);
27214
27215 let max_buffer_window: String = snapshot
27216 .text_for_range(window_start..buffer_offset)
27217 .collect();
27218
27219 if max_buffer_window.is_empty() {
27220 return Ok(CompletionResponse {
27221 completions: vec![],
27222 display_options: CompletionDisplayOptions::default(),
27223 is_incomplete: true,
27224 });
27225 }
27226
27227 for (_scope, snippets) in scopes.into_iter() {
27228 // Sort snippets by word count to match longer snippet prefixes first.
27229 let mut sorted_snippet_candidates = snippets
27230 .iter()
27231 .enumerate()
27232 .flat_map(|(snippet_ix, snippet)| {
27233 snippet
27234 .prefix
27235 .iter()
27236 .enumerate()
27237 .map(move |(prefix_ix, prefix)| {
27238 let word_count =
27239 snippet_candidate_suffixes(prefix, &is_word_char).count();
27240 ((snippet_ix, prefix_ix), prefix, word_count)
27241 })
27242 })
27243 .collect_vec();
27244 sorted_snippet_candidates
27245 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
27246
27247 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
27248
27249 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, &is_word_char)
27250 .take(
27251 sorted_snippet_candidates
27252 .first()
27253 .map(|(_, _, word_count)| *word_count)
27254 .unwrap_or_default(),
27255 )
27256 .collect_vec();
27257
27258 const MAX_RESULTS: usize = 100;
27259 // Each match also remembers how many characters from the buffer it consumed
27260 let mut matches: Vec<(StringMatch, usize)> = vec![];
27261
27262 let mut snippet_list_cutoff_index = 0;
27263 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
27264 let word_count = buffer_index + 1;
27265 // Increase `snippet_list_cutoff_index` until we have all of the
27266 // snippets with sufficiently many words.
27267 while sorted_snippet_candidates
27268 .get(snippet_list_cutoff_index)
27269 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
27270 *snippet_word_count >= word_count
27271 })
27272 {
27273 snippet_list_cutoff_index += 1;
27274 }
27275
27276 // Take only the candidates with at least `word_count` many words
27277 let snippet_candidates_at_word_len =
27278 &sorted_snippet_candidates[..snippet_list_cutoff_index];
27279
27280 let candidates = snippet_candidates_at_word_len
27281 .iter()
27282 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
27283 .enumerate() // index in `sorted_snippet_candidates`
27284 // First char must match
27285 .filter(|(_ix, prefix)| {
27286 itertools::equal(
27287 prefix
27288 .chars()
27289 .next()
27290 .into_iter()
27291 .flat_map(|c| c.to_lowercase()),
27292 buffer_window
27293 .chars()
27294 .next()
27295 .into_iter()
27296 .flat_map(|c| c.to_lowercase()),
27297 )
27298 })
27299 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
27300 .collect::<Vec<StringMatchCandidate>>();
27301
27302 matches.extend(
27303 fuzzy::match_strings(
27304 &candidates,
27305 &buffer_window,
27306 buffer_window.chars().any(|c| c.is_uppercase()),
27307 true,
27308 MAX_RESULTS - matches.len(), // always prioritize longer snippets
27309 &Default::default(),
27310 executor.clone(),
27311 )
27312 .await
27313 .into_iter()
27314 .map(|string_match| (string_match, buffer_window.len())),
27315 );
27316
27317 if matches.len() >= MAX_RESULTS {
27318 break;
27319 }
27320 }
27321
27322 let to_lsp = |point: &text::Anchor| {
27323 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
27324 point_to_lsp(end)
27325 };
27326 let lsp_end = to_lsp(&buffer_anchor);
27327
27328 if matches.len() >= MAX_RESULTS {
27329 is_incomplete = true;
27330 }
27331
27332 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
27333 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
27334 sorted_snippet_candidates[string_match.candidate_id];
27335 let snippet = &snippets[snippet_index];
27336 let start = buffer_offset - buffer_window_len;
27337 let start = snapshot.anchor_before(start);
27338 let range = start..buffer_anchor;
27339 let lsp_start = to_lsp(&start);
27340 let lsp_range = lsp::Range {
27341 start: lsp_start,
27342 end: lsp_end,
27343 };
27344 Completion {
27345 replace_range: range,
27346 new_text: snippet.body.clone(),
27347 source: CompletionSource::Lsp {
27348 insert_range: None,
27349 server_id: LanguageServerId(usize::MAX),
27350 resolved: true,
27351 lsp_completion: Box::new(lsp::CompletionItem {
27352 label: snippet.prefix.first().unwrap().clone(),
27353 kind: Some(CompletionItemKind::SNIPPET),
27354 label_details: snippet.description.as_ref().map(|description| {
27355 lsp::CompletionItemLabelDetails {
27356 detail: Some(description.clone()),
27357 description: None,
27358 }
27359 }),
27360 insert_text_format: Some(InsertTextFormat::SNIPPET),
27361 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
27362 lsp::InsertReplaceEdit {
27363 new_text: snippet.body.clone(),
27364 insert: lsp_range,
27365 replace: lsp_range,
27366 },
27367 )),
27368 filter_text: Some(snippet.body.clone()),
27369 sort_text: Some(char::MAX.to_string()),
27370 ..lsp::CompletionItem::default()
27371 }),
27372 lsp_defaults: None,
27373 },
27374 label: CodeLabel {
27375 text: matching_prefix.clone(),
27376 runs: Vec::new(),
27377 filter_range: 0..matching_prefix.len(),
27378 },
27379 icon_path: None,
27380 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
27381 single_line: snippet.name.clone().into(),
27382 plain_text: snippet
27383 .description
27384 .clone()
27385 .map(|description| description.into()),
27386 }),
27387 insert_text_mode: None,
27388 confirm: None,
27389 match_start: Some(start),
27390 snippet_deduplication_key: Some((snippet_index, prefix_index)),
27391 }
27392 }));
27393 }
27394
27395 Ok(CompletionResponse {
27396 completions,
27397 display_options: CompletionDisplayOptions::default(),
27398 is_incomplete,
27399 })
27400 })
27401}
27402
27403impl CompletionProvider for Entity<Project> {
27404 fn completions(
27405 &self,
27406 _excerpt_id: ExcerptId,
27407 buffer: &Entity<Buffer>,
27408 buffer_position: text::Anchor,
27409 options: CompletionContext,
27410 _window: &mut Window,
27411 cx: &mut Context<Editor>,
27412 ) -> Task<Result<Vec<CompletionResponse>>> {
27413 self.update(cx, |project, cx| {
27414 let task = project.completions(buffer, buffer_position, options, cx);
27415 cx.background_spawn(task)
27416 })
27417 }
27418
27419 fn resolve_completions(
27420 &self,
27421 buffer: Entity<Buffer>,
27422 completion_indices: Vec<usize>,
27423 completions: Rc<RefCell<Box<[Completion]>>>,
27424 cx: &mut Context<Editor>,
27425 ) -> Task<Result<bool>> {
27426 self.update(cx, |project, cx| {
27427 project.lsp_store().update(cx, |lsp_store, cx| {
27428 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
27429 })
27430 })
27431 }
27432
27433 fn apply_additional_edits_for_completion(
27434 &self,
27435 buffer: Entity<Buffer>,
27436 completions: Rc<RefCell<Box<[Completion]>>>,
27437 completion_index: usize,
27438 push_to_history: bool,
27439 all_commit_ranges: Vec<Range<language::Anchor>>,
27440 cx: &mut Context<Editor>,
27441 ) -> Task<Result<Option<language::Transaction>>> {
27442 self.update(cx, |project, cx| {
27443 project.lsp_store().update(cx, |lsp_store, cx| {
27444 lsp_store.apply_additional_edits_for_completion(
27445 buffer,
27446 completions,
27447 completion_index,
27448 push_to_history,
27449 all_commit_ranges,
27450 cx,
27451 )
27452 })
27453 })
27454 }
27455
27456 fn is_completion_trigger(
27457 &self,
27458 buffer: &Entity<Buffer>,
27459 position: language::Anchor,
27460 text: &str,
27461 trigger_in_words: bool,
27462 cx: &mut Context<Editor>,
27463 ) -> bool {
27464 let mut chars = text.chars();
27465 let char = if let Some(char) = chars.next() {
27466 char
27467 } else {
27468 return false;
27469 };
27470 if chars.next().is_some() {
27471 return false;
27472 }
27473
27474 let buffer = buffer.read(cx);
27475 let snapshot = buffer.snapshot();
27476 let classifier = snapshot
27477 .char_classifier_at(position)
27478 .scope_context(Some(CharScopeContext::Completion));
27479 if trigger_in_words && classifier.is_word(char) {
27480 return true;
27481 }
27482
27483 buffer.completion_triggers().contains(text)
27484 }
27485
27486 fn show_snippets(&self) -> bool {
27487 true
27488 }
27489}
27490
27491impl SemanticsProvider for WeakEntity<Project> {
27492 fn hover(
27493 &self,
27494 buffer: &Entity<Buffer>,
27495 position: text::Anchor,
27496 cx: &mut App,
27497 ) -> Option<Task<Option<Vec<project::Hover>>>> {
27498 self.update(cx, |project, cx| project.hover(buffer, position, cx))
27499 .ok()
27500 }
27501
27502 fn document_highlights(
27503 &self,
27504 buffer: &Entity<Buffer>,
27505 position: text::Anchor,
27506 cx: &mut App,
27507 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
27508 self.update(cx, |project, cx| {
27509 project.document_highlights(buffer, position, cx)
27510 })
27511 .ok()
27512 }
27513
27514 fn definitions(
27515 &self,
27516 buffer: &Entity<Buffer>,
27517 position: text::Anchor,
27518 kind: GotoDefinitionKind,
27519 cx: &mut App,
27520 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
27521 self.update(cx, |project, cx| match kind {
27522 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
27523 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
27524 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
27525 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
27526 })
27527 .ok()
27528 }
27529
27530 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27531 self.update(cx, |project, cx| {
27532 if project
27533 .active_debug_session(cx)
27534 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
27535 {
27536 return true;
27537 }
27538
27539 buffer.update(cx, |buffer, cx| {
27540 project.any_language_server_supports_inlay_hints(buffer, cx)
27541 })
27542 })
27543 .unwrap_or(false)
27544 }
27545
27546 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27547 self.update(cx, |project, cx| {
27548 buffer.update(cx, |buffer, cx| {
27549 project.any_language_server_supports_semantic_tokens(buffer, cx)
27550 })
27551 })
27552 .unwrap_or(false)
27553 }
27554
27555 fn inline_values(
27556 &self,
27557 buffer_handle: Entity<Buffer>,
27558 range: Range<text::Anchor>,
27559 cx: &mut App,
27560 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
27561 self.update(cx, |project, cx| {
27562 let (session, active_stack_frame) = project.active_debug_session(cx)?;
27563
27564 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
27565 })
27566 .ok()
27567 .flatten()
27568 }
27569
27570 fn applicable_inlay_chunks(
27571 &self,
27572 buffer: &Entity<Buffer>,
27573 ranges: &[Range<text::Anchor>],
27574 cx: &mut App,
27575 ) -> Vec<Range<BufferRow>> {
27576 self.update(cx, |project, cx| {
27577 project.lsp_store().update(cx, |lsp_store, cx| {
27578 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
27579 })
27580 })
27581 .unwrap_or_default()
27582 }
27583
27584 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
27585 self.update(cx, |project, cx| {
27586 project.lsp_store().update(cx, |lsp_store, _| {
27587 lsp_store.invalidate_inlay_hints(for_buffers)
27588 })
27589 })
27590 .ok();
27591 }
27592
27593 fn inlay_hints(
27594 &self,
27595 invalidate: InvalidationStrategy,
27596 buffer: Entity<Buffer>,
27597 ranges: Vec<Range<text::Anchor>>,
27598 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
27599 cx: &mut App,
27600 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
27601 self.update(cx, |project, cx| {
27602 project.lsp_store().update(cx, |lsp_store, cx| {
27603 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
27604 })
27605 })
27606 .ok()
27607 }
27608
27609 fn semantic_tokens(
27610 &self,
27611 buffer: Entity<Buffer>,
27612 refresh: Option<RefreshForServer>,
27613 cx: &mut App,
27614 ) -> Option<Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>>> {
27615 self.update(cx, |this, cx| {
27616 this.lsp_store().update(cx, |lsp_store, cx| {
27617 lsp_store.semantic_tokens(buffer, refresh, cx)
27618 })
27619 })
27620 .ok()
27621 }
27622
27623 fn range_for_rename(
27624 &self,
27625 buffer: &Entity<Buffer>,
27626 position: text::Anchor,
27627 cx: &mut App,
27628 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
27629 self.update(cx, |project, cx| {
27630 let buffer = buffer.clone();
27631 let task = project.prepare_rename(buffer.clone(), position, cx);
27632 cx.spawn(async move |_, cx| {
27633 Ok(match task.await? {
27634 PrepareRenameResponse::Success(range) => Some(range),
27635 PrepareRenameResponse::InvalidPosition => None,
27636 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
27637 // Fallback on using TreeSitter info to determine identifier range
27638 buffer.read_with(cx, |buffer, _| {
27639 let snapshot = buffer.snapshot();
27640 let (range, kind) = snapshot.surrounding_word(position, None);
27641 if kind != Some(CharKind::Word) {
27642 return None;
27643 }
27644 Some(
27645 snapshot.anchor_before(range.start)
27646 ..snapshot.anchor_after(range.end),
27647 )
27648 })
27649 }
27650 })
27651 })
27652 })
27653 .ok()
27654 }
27655
27656 fn perform_rename(
27657 &self,
27658 buffer: &Entity<Buffer>,
27659 position: text::Anchor,
27660 new_name: String,
27661 cx: &mut App,
27662 ) -> Option<Task<Result<ProjectTransaction>>> {
27663 self.update(cx, |project, cx| {
27664 project.perform_rename(buffer.clone(), position, new_name, cx)
27665 })
27666 .ok()
27667 }
27668}
27669
27670fn consume_contiguous_rows(
27671 contiguous_row_selections: &mut Vec<Selection<Point>>,
27672 selection: &Selection<Point>,
27673 display_map: &DisplaySnapshot,
27674 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
27675) -> (MultiBufferRow, MultiBufferRow) {
27676 contiguous_row_selections.push(selection.clone());
27677 let start_row = starting_row(selection, display_map);
27678 let mut end_row = ending_row(selection, display_map);
27679
27680 while let Some(next_selection) = selections.peek() {
27681 if next_selection.start.row <= end_row.0 {
27682 end_row = ending_row(next_selection, display_map);
27683 contiguous_row_selections.push(selections.next().unwrap().clone());
27684 } else {
27685 break;
27686 }
27687 }
27688 (start_row, end_row)
27689}
27690
27691fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27692 if selection.start.column > 0 {
27693 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
27694 } else {
27695 MultiBufferRow(selection.start.row)
27696 }
27697}
27698
27699fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27700 if next_selection.end.column > 0 || next_selection.is_empty() {
27701 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
27702 } else {
27703 MultiBufferRow(next_selection.end.row)
27704 }
27705}
27706
27707impl EditorSnapshot {
27708 pub fn remote_selections_in_range<'a>(
27709 &'a self,
27710 range: &'a Range<Anchor>,
27711 collaboration_hub: &dyn CollaborationHub,
27712 cx: &'a App,
27713 ) -> impl 'a + Iterator<Item = RemoteSelection> {
27714 let participant_names = collaboration_hub.user_names(cx);
27715 let participant_indices = collaboration_hub.user_participant_indices(cx);
27716 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
27717 let collaborators_by_replica_id = collaborators_by_peer_id
27718 .values()
27719 .map(|collaborator| (collaborator.replica_id, collaborator))
27720 .collect::<HashMap<_, _>>();
27721 self.buffer_snapshot()
27722 .selections_in_range(range, false)
27723 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
27724 if replica_id == ReplicaId::AGENT {
27725 Some(RemoteSelection {
27726 replica_id,
27727 selection,
27728 cursor_shape,
27729 line_mode,
27730 collaborator_id: CollaboratorId::Agent,
27731 user_name: Some("Agent".into()),
27732 color: cx.theme().players().agent(),
27733 })
27734 } else {
27735 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
27736 let participant_index = participant_indices.get(&collaborator.user_id).copied();
27737 let user_name = participant_names.get(&collaborator.user_id).cloned();
27738 Some(RemoteSelection {
27739 replica_id,
27740 selection,
27741 cursor_shape,
27742 line_mode,
27743 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
27744 user_name,
27745 color: if let Some(index) = participant_index {
27746 cx.theme().players().color_for_participant(index.0)
27747 } else {
27748 cx.theme().players().absent()
27749 },
27750 })
27751 }
27752 })
27753 }
27754
27755 pub fn hunks_for_ranges(
27756 &self,
27757 ranges: impl IntoIterator<Item = Range<Point>>,
27758 ) -> Vec<MultiBufferDiffHunk> {
27759 let mut hunks = Vec::new();
27760 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
27761 HashMap::default();
27762 for query_range in ranges {
27763 let query_rows =
27764 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
27765 for hunk in self.buffer_snapshot().diff_hunks_in_range(
27766 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
27767 ) {
27768 // Include deleted hunks that are adjacent to the query range, because
27769 // otherwise they would be missed.
27770 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
27771 if hunk.status().is_deleted() {
27772 intersects_range |= hunk.row_range.start == query_rows.end;
27773 intersects_range |= hunk.row_range.end == query_rows.start;
27774 }
27775 if intersects_range {
27776 if !processed_buffer_rows
27777 .entry(hunk.buffer_id)
27778 .or_default()
27779 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
27780 {
27781 continue;
27782 }
27783 hunks.push(hunk);
27784 }
27785 }
27786 }
27787
27788 hunks
27789 }
27790
27791 fn display_diff_hunks_for_rows<'a>(
27792 &'a self,
27793 display_rows: Range<DisplayRow>,
27794 folded_buffers: &'a HashSet<BufferId>,
27795 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
27796 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
27797 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
27798
27799 self.buffer_snapshot()
27800 .diff_hunks_in_range(buffer_start..buffer_end)
27801 .filter_map(|hunk| {
27802 if folded_buffers.contains(&hunk.buffer_id)
27803 || (hunk.row_range.is_empty() && self.buffer.all_diff_hunks_expanded())
27804 {
27805 return None;
27806 }
27807
27808 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
27809 let hunk_end_point = if hunk.row_range.end > hunk.row_range.start {
27810 let last_row = MultiBufferRow(hunk.row_range.end.0 - 1);
27811 let line_len = self.buffer_snapshot().line_len(last_row);
27812 Point::new(last_row.0, line_len)
27813 } else {
27814 Point::new(hunk.row_range.end.0, 0)
27815 };
27816
27817 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
27818 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
27819
27820 let display_hunk = if hunk_display_start.column() != 0 {
27821 DisplayDiffHunk::Folded {
27822 display_row: hunk_display_start.row(),
27823 }
27824 } else {
27825 let mut end_row = hunk_display_end.row();
27826 if hunk.row_range.end > hunk.row_range.start || hunk_display_end.column() > 0 {
27827 end_row.0 += 1;
27828 }
27829 let is_created_file = hunk.is_created_file();
27830
27831 DisplayDiffHunk::Unfolded {
27832 status: hunk.status(),
27833 diff_base_byte_range: hunk.diff_base_byte_range.start.0
27834 ..hunk.diff_base_byte_range.end.0,
27835 word_diffs: hunk.word_diffs,
27836 display_row_range: hunk_display_start.row()..end_row,
27837 multi_buffer_range: Anchor::range_in_buffer(
27838 hunk.excerpt_id,
27839 hunk.buffer_range,
27840 ),
27841 is_created_file,
27842 }
27843 };
27844
27845 Some(display_hunk)
27846 })
27847 }
27848
27849 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
27850 self.display_snapshot
27851 .buffer_snapshot()
27852 .language_at(position)
27853 }
27854
27855 pub fn is_focused(&self) -> bool {
27856 self.is_focused
27857 }
27858
27859 pub fn placeholder_text(&self) -> Option<String> {
27860 self.placeholder_display_snapshot
27861 .as_ref()
27862 .map(|display_map| display_map.text())
27863 }
27864
27865 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
27866 self.scroll_anchor.scroll_position(&self.display_snapshot)
27867 }
27868
27869 pub fn gutter_dimensions(
27870 &self,
27871 font_id: FontId,
27872 font_size: Pixels,
27873 style: &EditorStyle,
27874 window: &mut Window,
27875 cx: &App,
27876 ) -> GutterDimensions {
27877 if self.show_gutter
27878 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
27879 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
27880 {
27881 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
27882 matches!(
27883 ProjectSettings::get_global(cx).git.git_gutter,
27884 GitGutterSetting::TrackedFiles
27885 )
27886 });
27887 let gutter_settings = EditorSettings::get_global(cx).gutter;
27888 let show_line_numbers = self
27889 .show_line_numbers
27890 .unwrap_or(gutter_settings.line_numbers);
27891 let line_gutter_width = if show_line_numbers {
27892 // Avoid flicker-like gutter resizes when the line number gains another digit by
27893 // only resizing the gutter on files with > 10**min_line_number_digits lines.
27894 let min_width_for_number_on_gutter =
27895 ch_advance * gutter_settings.min_line_number_digits as f32;
27896 self.max_line_number_width(style, window)
27897 .max(min_width_for_number_on_gutter)
27898 } else {
27899 0.0.into()
27900 };
27901
27902 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
27903 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
27904
27905 let git_blame_entries_width =
27906 self.git_blame_gutter_max_author_length
27907 .map(|max_author_length| {
27908 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
27909 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
27910
27911 /// The number of characters to dedicate to gaps and margins.
27912 const SPACING_WIDTH: usize = 4;
27913
27914 let max_char_count = max_author_length.min(renderer.max_author_length())
27915 + ::git::SHORT_SHA_LENGTH
27916 + MAX_RELATIVE_TIMESTAMP.len()
27917 + SPACING_WIDTH;
27918
27919 ch_advance * max_char_count
27920 });
27921
27922 let is_singleton = self.buffer_snapshot().is_singleton();
27923
27924 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
27925 left_padding += if !is_singleton {
27926 ch_width * 4.0
27927 } else if show_runnables || show_breakpoints {
27928 ch_width * 3.0
27929 } else if show_git_gutter && show_line_numbers {
27930 ch_width * 2.0
27931 } else if show_git_gutter || show_line_numbers {
27932 ch_width
27933 } else {
27934 px(0.)
27935 };
27936
27937 let shows_folds = is_singleton && gutter_settings.folds;
27938
27939 let right_padding = if shows_folds && show_line_numbers {
27940 ch_width * 4.0
27941 } else if shows_folds || (!is_singleton && show_line_numbers) {
27942 ch_width * 3.0
27943 } else if show_line_numbers {
27944 ch_width
27945 } else {
27946 px(0.)
27947 };
27948
27949 GutterDimensions {
27950 left_padding,
27951 right_padding,
27952 width: line_gutter_width + left_padding + right_padding,
27953 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
27954 git_blame_entries_width,
27955 }
27956 } else if self.offset_content {
27957 GutterDimensions::default_with_margin(font_id, font_size, cx)
27958 } else {
27959 GutterDimensions::default()
27960 }
27961 }
27962
27963 pub fn render_crease_toggle(
27964 &self,
27965 buffer_row: MultiBufferRow,
27966 row_contains_cursor: bool,
27967 editor: Entity<Editor>,
27968 window: &mut Window,
27969 cx: &mut App,
27970 ) -> Option<AnyElement> {
27971 let folded = self.is_line_folded(buffer_row);
27972 let mut is_foldable = false;
27973
27974 if let Some(crease) = self
27975 .crease_snapshot
27976 .query_row(buffer_row, self.buffer_snapshot())
27977 {
27978 is_foldable = true;
27979 match crease {
27980 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
27981 if let Some(render_toggle) = render_toggle {
27982 let toggle_callback =
27983 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
27984 if folded {
27985 editor.update(cx, |editor, cx| {
27986 editor.fold_at(buffer_row, window, cx)
27987 });
27988 } else {
27989 editor.update(cx, |editor, cx| {
27990 editor.unfold_at(buffer_row, window, cx)
27991 });
27992 }
27993 });
27994 return Some((render_toggle)(
27995 buffer_row,
27996 folded,
27997 toggle_callback,
27998 window,
27999 cx,
28000 ));
28001 }
28002 }
28003 }
28004 }
28005
28006 is_foldable |= !self.use_lsp_folding_ranges && self.starts_indent(buffer_row);
28007
28008 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
28009 Some(
28010 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
28011 .toggle_state(folded)
28012 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
28013 if folded {
28014 this.unfold_at(buffer_row, window, cx);
28015 } else {
28016 this.fold_at(buffer_row, window, cx);
28017 }
28018 }))
28019 .into_any_element(),
28020 )
28021 } else {
28022 None
28023 }
28024 }
28025
28026 pub fn render_crease_trailer(
28027 &self,
28028 buffer_row: MultiBufferRow,
28029 window: &mut Window,
28030 cx: &mut App,
28031 ) -> Option<AnyElement> {
28032 let folded = self.is_line_folded(buffer_row);
28033 if let Crease::Inline { render_trailer, .. } = self
28034 .crease_snapshot
28035 .query_row(buffer_row, self.buffer_snapshot())?
28036 {
28037 let render_trailer = render_trailer.as_ref()?;
28038 Some(render_trailer(buffer_row, folded, window, cx))
28039 } else {
28040 None
28041 }
28042 }
28043
28044 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
28045 let digit_count = self.widest_line_number().ilog10() + 1;
28046 column_pixels(style, digit_count as usize, window)
28047 }
28048
28049 /// Returns the line delta from `base` to `line` in the multibuffer, ignoring wrapped lines.
28050 ///
28051 /// This is positive if `base` is before `line`.
28052 fn relative_line_delta(
28053 &self,
28054 current_selection_head: DisplayRow,
28055 first_visible_row: DisplayRow,
28056 consider_wrapped_lines: bool,
28057 ) -> i64 {
28058 let current_selection_head = current_selection_head.as_display_point().to_point(self);
28059 let first_visible_row = first_visible_row.as_display_point().to_point(self);
28060
28061 if consider_wrapped_lines {
28062 let wrap_snapshot = self.wrap_snapshot();
28063 let base_wrap_row = wrap_snapshot
28064 .make_wrap_point(current_selection_head, Bias::Left)
28065 .row();
28066 let wrap_row = wrap_snapshot
28067 .make_wrap_point(first_visible_row, Bias::Left)
28068 .row();
28069
28070 wrap_row.0 as i64 - base_wrap_row.0 as i64
28071 } else {
28072 let fold_snapshot = self.fold_snapshot();
28073 let base_fold_row = fold_snapshot
28074 .to_fold_point(self.to_inlay_point(current_selection_head), Bias::Left)
28075 .row();
28076 let fold_row = fold_snapshot
28077 .to_fold_point(self.to_inlay_point(first_visible_row), Bias::Left)
28078 .row();
28079
28080 fold_row as i64 - base_fold_row as i64
28081 }
28082 }
28083
28084 /// Returns the unsigned relative line number to display for each row in `rows`.
28085 ///
28086 /// Wrapped rows are excluded from the hashmap if `count_relative_lines` is `false`.
28087 pub fn calculate_relative_line_numbers(
28088 &self,
28089 rows: &Range<DisplayRow>,
28090 current_selection_head: DisplayRow,
28091 count_wrapped_lines: bool,
28092 ) -> HashMap<DisplayRow, u32> {
28093 let initial_offset =
28094 self.relative_line_delta(current_selection_head, rows.start, count_wrapped_lines);
28095
28096 self.row_infos(rows.start)
28097 .take(rows.len())
28098 .enumerate()
28099 .map(|(i, row_info)| (DisplayRow(rows.start.0 + i as u32), row_info))
28100 .filter(|(_row, row_info)| {
28101 row_info.buffer_row.is_some()
28102 || (count_wrapped_lines && row_info.wrapped_buffer_row.is_some())
28103 })
28104 .enumerate()
28105 .filter_map(|(i, (row, row_info))| {
28106 // We want to ensure here that the current line has absolute
28107 // numbering, even if we are in a soft-wrapped line. With the
28108 // exception that if we are in a deleted line, we should number this
28109 // relative with 0, as otherwise it would have no line number at all
28110 let relative_line_number = (initial_offset + i as i64).unsigned_abs() as u32;
28111
28112 (relative_line_number != 0
28113 || row_info
28114 .diff_status
28115 .is_some_and(|status| status.is_deleted()))
28116 .then_some((row, relative_line_number))
28117 })
28118 .collect()
28119 }
28120}
28121
28122pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
28123 let font_size = style.text.font_size.to_pixels(window.rem_size());
28124 let layout = window.text_system().shape_line(
28125 SharedString::from(" ".repeat(column)),
28126 font_size,
28127 &[TextRun {
28128 len: column,
28129 font: style.text.font(),
28130 color: Hsla::default(),
28131 ..Default::default()
28132 }],
28133 None,
28134 );
28135
28136 layout.width
28137}
28138
28139impl Deref for EditorSnapshot {
28140 type Target = DisplaySnapshot;
28141
28142 fn deref(&self) -> &Self::Target {
28143 &self.display_snapshot
28144 }
28145}
28146
28147#[derive(Clone, Debug, PartialEq, Eq)]
28148pub enum EditorEvent {
28149 /// Emitted when the stored review comments change (added, removed, or updated).
28150 ReviewCommentsChanged {
28151 /// The new total count of review comments.
28152 total_count: usize,
28153 },
28154 InputIgnored {
28155 text: Arc<str>,
28156 },
28157 InputHandled {
28158 utf16_range_to_replace: Option<Range<isize>>,
28159 text: Arc<str>,
28160 },
28161 ExcerptsAdded {
28162 buffer: Entity<Buffer>,
28163 predecessor: ExcerptId,
28164 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
28165 },
28166 ExcerptsRemoved {
28167 ids: Vec<ExcerptId>,
28168 removed_buffer_ids: Vec<BufferId>,
28169 },
28170 BufferFoldToggled {
28171 ids: Vec<ExcerptId>,
28172 folded: bool,
28173 },
28174 ExcerptsEdited {
28175 ids: Vec<ExcerptId>,
28176 },
28177 ExcerptsExpanded {
28178 ids: Vec<ExcerptId>,
28179 },
28180 ExpandExcerptsRequested {
28181 excerpt_ids: Vec<ExcerptId>,
28182 lines: u32,
28183 direction: ExpandExcerptDirection,
28184 },
28185 StageOrUnstageRequested {
28186 stage: bool,
28187 hunks: Vec<MultiBufferDiffHunk>,
28188 },
28189 OpenExcerptsRequested {
28190 selections_by_buffer: HashMap<BufferId, (Vec<Range<BufferOffset>>, Option<u32>)>,
28191 split: bool,
28192 },
28193 RestoreRequested {
28194 hunks: Vec<MultiBufferDiffHunk>,
28195 },
28196 BufferEdited,
28197 Edited {
28198 transaction_id: clock::Lamport,
28199 },
28200 Reparsed(BufferId),
28201 Focused,
28202 FocusedIn,
28203 Blurred,
28204 DirtyChanged,
28205 Saved,
28206 TitleChanged,
28207 SelectionsChanged {
28208 local: bool,
28209 },
28210 ScrollPositionChanged {
28211 local: bool,
28212 autoscroll: bool,
28213 },
28214 TransactionUndone {
28215 transaction_id: clock::Lamport,
28216 },
28217 TransactionBegun {
28218 transaction_id: clock::Lamport,
28219 },
28220 CursorShapeChanged,
28221 BreadcrumbsChanged,
28222 OutlineSymbolsChanged,
28223 PushedToNavHistory {
28224 anchor: Anchor,
28225 is_deactivate: bool,
28226 },
28227}
28228
28229impl EventEmitter<EditorEvent> for Editor {}
28230
28231impl Focusable for Editor {
28232 fn focus_handle(&self, _cx: &App) -> FocusHandle {
28233 self.focus_handle.clone()
28234 }
28235}
28236
28237impl Render for Editor {
28238 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28239 EditorElement::new(&cx.entity(), self.create_style(cx))
28240 }
28241}
28242
28243impl EntityInputHandler for Editor {
28244 fn text_for_range(
28245 &mut self,
28246 range_utf16: Range<usize>,
28247 adjusted_range: &mut Option<Range<usize>>,
28248 _: &mut Window,
28249 cx: &mut Context<Self>,
28250 ) -> Option<String> {
28251 let snapshot = self.buffer.read(cx).read(cx);
28252 let start = snapshot.clip_offset_utf16(
28253 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
28254 Bias::Left,
28255 );
28256 let end = snapshot.clip_offset_utf16(
28257 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
28258 Bias::Right,
28259 );
28260 if (start.0.0..end.0.0) != range_utf16 {
28261 adjusted_range.replace(start.0.0..end.0.0);
28262 }
28263 Some(snapshot.text_for_range(start..end).collect())
28264 }
28265
28266 fn selected_text_range(
28267 &mut self,
28268 ignore_disabled_input: bool,
28269 _: &mut Window,
28270 cx: &mut Context<Self>,
28271 ) -> Option<UTF16Selection> {
28272 // Prevent the IME menu from appearing when holding down an alphabetic key
28273 // while input is disabled.
28274 if !ignore_disabled_input && !self.input_enabled {
28275 return None;
28276 }
28277
28278 let selection = self
28279 .selections
28280 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
28281 let range = selection.range();
28282
28283 Some(UTF16Selection {
28284 range: range.start.0.0..range.end.0.0,
28285 reversed: selection.reversed,
28286 })
28287 }
28288
28289 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
28290 let snapshot = self.buffer.read(cx).read(cx);
28291 let range = self
28292 .text_highlights(HighlightKey::InputComposition, cx)?
28293 .1
28294 .first()?;
28295 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
28296 }
28297
28298 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
28299 self.clear_highlights(HighlightKey::InputComposition, cx);
28300 self.ime_transaction.take();
28301 }
28302
28303 fn replace_text_in_range(
28304 &mut self,
28305 range_utf16: Option<Range<usize>>,
28306 text: &str,
28307 window: &mut Window,
28308 cx: &mut Context<Self>,
28309 ) {
28310 if !self.input_enabled {
28311 cx.emit(EditorEvent::InputIgnored { text: text.into() });
28312 return;
28313 }
28314
28315 self.transact(window, cx, |this, window, cx| {
28316 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
28317 if let Some(marked_ranges) = this.marked_text_ranges(cx) {
28318 // During IME composition, macOS reports the replacement range
28319 // relative to the first marked region (the only one visible via
28320 // marked_text_range). The correct targets for replacement are the
28321 // marked ranges themselves — one per cursor — so use them directly.
28322 Some(marked_ranges)
28323 } else if range_utf16.start == range_utf16.end {
28324 // An empty replacement range means "insert at cursor" with no text
28325 // to replace. macOS reports the cursor position from its own
28326 // (single-cursor) view of the buffer, which diverges from our actual
28327 // cursor positions after multi-cursor edits have shifted offsets.
28328 // Treating this as range_utf16=None lets each cursor insert in place.
28329 None
28330 } else {
28331 // Outside of IME composition (e.g. Accessibility Keyboard word
28332 // completion), the range is an absolute document offset for the
28333 // newest cursor. Fan it out to all cursors via
28334 // selection_replacement_ranges, which applies the delta relative
28335 // to the newest selection to every cursor.
28336 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
28337 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
28338 Some(this.selection_replacement_ranges(range_utf16, cx))
28339 }
28340 } else {
28341 this.marked_text_ranges(cx)
28342 };
28343
28344 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
28345 let newest_selection_id = this.selections.newest_anchor().id;
28346 this.selections
28347 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
28348 .iter()
28349 .zip(ranges_to_replace.iter())
28350 .find_map(|(selection, range)| {
28351 if selection.id == newest_selection_id {
28352 Some(
28353 (range.start.0.0 as isize - selection.head().0.0 as isize)
28354 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
28355 )
28356 } else {
28357 None
28358 }
28359 })
28360 });
28361
28362 cx.emit(EditorEvent::InputHandled {
28363 utf16_range_to_replace: range_to_replace,
28364 text: text.into(),
28365 });
28366
28367 if let Some(new_selected_ranges) = new_selected_ranges {
28368 // Only backspace if at least one range covers actual text. When all
28369 // ranges are empty (e.g. a trailing-space insertion from Accessibility
28370 // Keyboard sends replacementRange=cursor..cursor), backspace would
28371 // incorrectly delete the character just before the cursor.
28372 let should_backspace = new_selected_ranges.iter().any(|r| r.start != r.end);
28373 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
28374 selections.select_ranges(new_selected_ranges)
28375 });
28376 if should_backspace {
28377 this.backspace(&Default::default(), window, cx);
28378 }
28379 }
28380
28381 this.handle_input(text, window, cx);
28382 });
28383
28384 if let Some(transaction) = self.ime_transaction {
28385 self.buffer.update(cx, |buffer, cx| {
28386 buffer.group_until_transaction(transaction, cx);
28387 });
28388 }
28389
28390 self.unmark_text(window, cx);
28391 }
28392
28393 fn replace_and_mark_text_in_range(
28394 &mut self,
28395 range_utf16: Option<Range<usize>>,
28396 text: &str,
28397 new_selected_range_utf16: Option<Range<usize>>,
28398 window: &mut Window,
28399 cx: &mut Context<Self>,
28400 ) {
28401 if !self.input_enabled {
28402 return;
28403 }
28404
28405 let transaction = self.transact(window, cx, |this, window, cx| {
28406 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
28407 let snapshot = this.buffer.read(cx).read(cx);
28408 if let Some(relative_range_utf16) = range_utf16.as_ref() {
28409 for marked_range in &mut marked_ranges {
28410 marked_range.end = marked_range.start + relative_range_utf16.end;
28411 marked_range.start += relative_range_utf16.start;
28412 marked_range.start =
28413 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
28414 marked_range.end =
28415 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
28416 }
28417 }
28418 Some(marked_ranges)
28419 } else if let Some(range_utf16) = range_utf16 {
28420 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
28421 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
28422 Some(this.selection_replacement_ranges(range_utf16, cx))
28423 } else {
28424 None
28425 };
28426
28427 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
28428 let newest_selection_id = this.selections.newest_anchor().id;
28429 this.selections
28430 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
28431 .iter()
28432 .zip(ranges_to_replace.iter())
28433 .find_map(|(selection, range)| {
28434 if selection.id == newest_selection_id {
28435 Some(
28436 (range.start.0.0 as isize - selection.head().0.0 as isize)
28437 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
28438 )
28439 } else {
28440 None
28441 }
28442 })
28443 });
28444
28445 cx.emit(EditorEvent::InputHandled {
28446 utf16_range_to_replace: range_to_replace,
28447 text: text.into(),
28448 });
28449
28450 if let Some(ranges) = ranges_to_replace {
28451 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28452 s.select_ranges(ranges)
28453 });
28454 }
28455
28456 let marked_ranges = {
28457 let snapshot = this.buffer.read(cx).read(cx);
28458 this.selections
28459 .disjoint_anchors_arc()
28460 .iter()
28461 .map(|selection| {
28462 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
28463 })
28464 .collect::<Vec<_>>()
28465 };
28466
28467 if text.is_empty() {
28468 this.unmark_text(window, cx);
28469 } else {
28470 this.highlight_text(
28471 HighlightKey::InputComposition,
28472 marked_ranges.clone(),
28473 HighlightStyle {
28474 underline: Some(UnderlineStyle {
28475 thickness: px(1.),
28476 color: None,
28477 wavy: false,
28478 }),
28479 ..Default::default()
28480 },
28481 cx,
28482 );
28483 }
28484
28485 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
28486 let use_autoclose = this.use_autoclose;
28487 let use_auto_surround = this.use_auto_surround;
28488 this.set_use_autoclose(false);
28489 this.set_use_auto_surround(false);
28490 this.handle_input(text, window, cx);
28491 this.set_use_autoclose(use_autoclose);
28492 this.set_use_auto_surround(use_auto_surround);
28493
28494 if let Some(new_selected_range) = new_selected_range_utf16 {
28495 let snapshot = this.buffer.read(cx).read(cx);
28496 let new_selected_ranges = marked_ranges
28497 .into_iter()
28498 .map(|marked_range| {
28499 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
28500 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
28501 insertion_start.0 + new_selected_range.start,
28502 ));
28503 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
28504 insertion_start.0 + new_selected_range.end,
28505 ));
28506 snapshot.clip_offset_utf16(new_start, Bias::Left)
28507 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
28508 })
28509 .collect::<Vec<_>>();
28510
28511 drop(snapshot);
28512 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
28513 selections.select_ranges(new_selected_ranges)
28514 });
28515 }
28516 });
28517
28518 self.ime_transaction = self.ime_transaction.or(transaction);
28519 if let Some(transaction) = self.ime_transaction {
28520 self.buffer.update(cx, |buffer, cx| {
28521 buffer.group_until_transaction(transaction, cx);
28522 });
28523 }
28524
28525 if self
28526 .text_highlights(HighlightKey::InputComposition, cx)
28527 .is_none()
28528 {
28529 self.ime_transaction.take();
28530 }
28531 }
28532
28533 fn bounds_for_range(
28534 &mut self,
28535 range_utf16: Range<usize>,
28536 element_bounds: gpui::Bounds<Pixels>,
28537 window: &mut Window,
28538 cx: &mut Context<Self>,
28539 ) -> Option<gpui::Bounds<Pixels>> {
28540 let text_layout_details = self.text_layout_details(window, cx);
28541 let CharacterDimensions {
28542 em_width,
28543 em_advance,
28544 line_height,
28545 } = self.character_dimensions(window, cx);
28546
28547 let snapshot = self.snapshot(window, cx);
28548 let scroll_position = snapshot.scroll_position();
28549 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
28550
28551 let start =
28552 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
28553 let x = Pixels::from(
28554 ScrollOffset::from(
28555 snapshot.x_for_display_point(start, &text_layout_details)
28556 + self.gutter_dimensions.full_width(),
28557 ) - scroll_left,
28558 );
28559 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
28560
28561 Some(Bounds {
28562 origin: element_bounds.origin + point(x, y),
28563 size: size(em_width, line_height),
28564 })
28565 }
28566
28567 fn character_index_for_point(
28568 &mut self,
28569 point: gpui::Point<Pixels>,
28570 _window: &mut Window,
28571 _cx: &mut Context<Self>,
28572 ) -> Option<usize> {
28573 let position_map = self.last_position_map.as_ref()?;
28574 if !position_map.text_hitbox.contains(&point) {
28575 return None;
28576 }
28577 let display_point = position_map.point_for_position(point).previous_valid;
28578 let anchor = position_map
28579 .snapshot
28580 .display_point_to_anchor(display_point, Bias::Left);
28581 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
28582 Some(utf16_offset.0.0)
28583 }
28584
28585 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
28586 self.expects_character_input
28587 }
28588}
28589
28590trait SelectionExt {
28591 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
28592 fn spanned_rows(
28593 &self,
28594 include_end_if_at_line_start: bool,
28595 map: &DisplaySnapshot,
28596 ) -> Range<MultiBufferRow>;
28597}
28598
28599impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
28600 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
28601 let start = self
28602 .start
28603 .to_point(map.buffer_snapshot())
28604 .to_display_point(map);
28605 let end = self
28606 .end
28607 .to_point(map.buffer_snapshot())
28608 .to_display_point(map);
28609 if self.reversed {
28610 end..start
28611 } else {
28612 start..end
28613 }
28614 }
28615
28616 fn spanned_rows(
28617 &self,
28618 include_end_if_at_line_start: bool,
28619 map: &DisplaySnapshot,
28620 ) -> Range<MultiBufferRow> {
28621 let start = self.start.to_point(map.buffer_snapshot());
28622 let mut end = self.end.to_point(map.buffer_snapshot());
28623 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
28624 end.row -= 1;
28625 }
28626
28627 let buffer_start = map.prev_line_boundary(start).0;
28628 let buffer_end = map.next_line_boundary(end).0;
28629 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
28630 }
28631}
28632
28633impl<T: InvalidationRegion> InvalidationStack<T> {
28634 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
28635 where
28636 S: Clone + ToOffset,
28637 {
28638 while let Some(region) = self.last() {
28639 let all_selections_inside_invalidation_ranges =
28640 if selections.len() == region.ranges().len() {
28641 selections
28642 .iter()
28643 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
28644 .all(|(selection, invalidation_range)| {
28645 let head = selection.head().to_offset(buffer);
28646 invalidation_range.start <= head && invalidation_range.end >= head
28647 })
28648 } else {
28649 false
28650 };
28651
28652 if all_selections_inside_invalidation_ranges {
28653 break;
28654 } else {
28655 self.pop();
28656 }
28657 }
28658 }
28659}
28660
28661#[derive(Clone)]
28662struct ErasedEditorImpl(Entity<Editor>);
28663
28664impl ui_input::ErasedEditor for ErasedEditorImpl {
28665 fn text(&self, cx: &App) -> String {
28666 self.0.read(cx).text(cx)
28667 }
28668
28669 fn set_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28670 self.0.update(cx, |this, cx| {
28671 this.set_text(text, window, cx);
28672 })
28673 }
28674
28675 fn clear(&self, window: &mut Window, cx: &mut App) {
28676 self.0.update(cx, |this, cx| this.clear(window, cx));
28677 }
28678
28679 fn set_placeholder_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28680 self.0.update(cx, |this, cx| {
28681 this.set_placeholder_text(text, window, cx);
28682 });
28683 }
28684
28685 fn focus_handle(&self, cx: &App) -> FocusHandle {
28686 self.0.read(cx).focus_handle(cx)
28687 }
28688
28689 fn render(&self, _: &mut Window, cx: &App) -> AnyElement {
28690 let settings = ThemeSettings::get_global(cx);
28691 let theme_color = cx.theme().colors();
28692
28693 let text_style = TextStyle {
28694 font_family: settings.ui_font.family.clone(),
28695 font_features: settings.ui_font.features.clone(),
28696 font_size: rems(0.875).into(),
28697 font_weight: settings.ui_font.weight,
28698 font_style: FontStyle::Normal,
28699 line_height: relative(1.2),
28700 color: theme_color.text,
28701 ..Default::default()
28702 };
28703 let editor_style = EditorStyle {
28704 background: theme_color.ghost_element_background,
28705 local_player: cx.theme().players().local(),
28706 syntax: cx.theme().syntax().clone(),
28707 text: text_style,
28708 ..Default::default()
28709 };
28710 EditorElement::new(&self.0, editor_style).into_any()
28711 }
28712
28713 fn as_any(&self) -> &dyn Any {
28714 &self.0
28715 }
28716
28717 fn move_selection_to_end(&self, window: &mut Window, cx: &mut App) {
28718 self.0.update(cx, |editor, cx| {
28719 let editor_offset = editor.buffer().read(cx).len(cx);
28720 editor.change_selections(
28721 SelectionEffects::scroll(Autoscroll::Next),
28722 window,
28723 cx,
28724 |s| s.select_ranges(Some(editor_offset..editor_offset)),
28725 );
28726 });
28727 }
28728
28729 fn subscribe(
28730 &self,
28731 mut callback: Box<dyn FnMut(ui_input::ErasedEditorEvent, &mut Window, &mut App) + 'static>,
28732 window: &mut Window,
28733 cx: &mut App,
28734 ) -> Subscription {
28735 window.subscribe(&self.0, cx, move |_, event: &EditorEvent, window, cx| {
28736 let event = match event {
28737 EditorEvent::BufferEdited => ui_input::ErasedEditorEvent::BufferEdited,
28738 EditorEvent::Blurred => ui_input::ErasedEditorEvent::Blurred,
28739 _ => return,
28740 };
28741 (callback)(event, window, cx);
28742 })
28743 }
28744
28745 fn set_masked(&self, masked: bool, _window: &mut Window, cx: &mut App) {
28746 self.0.update(cx, |editor, cx| {
28747 editor.set_masked(masked, cx);
28748 });
28749 }
28750}
28751impl<T> Default for InvalidationStack<T> {
28752 fn default() -> Self {
28753 Self(Default::default())
28754 }
28755}
28756
28757impl<T> Deref for InvalidationStack<T> {
28758 type Target = Vec<T>;
28759
28760 fn deref(&self) -> &Self::Target {
28761 &self.0
28762 }
28763}
28764
28765impl<T> DerefMut for InvalidationStack<T> {
28766 fn deref_mut(&mut self) -> &mut Self::Target {
28767 &mut self.0
28768 }
28769}
28770
28771impl InvalidationRegion for SnippetState {
28772 fn ranges(&self) -> &[Range<Anchor>] {
28773 &self.ranges[self.active_index]
28774 }
28775}
28776
28777fn edit_prediction_edit_text(
28778 current_snapshot: &BufferSnapshot,
28779 edits: &[(Range<Anchor>, impl AsRef<str>)],
28780 edit_preview: &EditPreview,
28781 include_deletions: bool,
28782 cx: &App,
28783) -> HighlightedText {
28784 let edits = edits
28785 .iter()
28786 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
28787 .collect::<Vec<_>>();
28788
28789 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
28790}
28791
28792fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
28793 // Fallback for providers that don't provide edit_preview (like Copilot)
28794 // Just show the raw edit text with basic styling
28795 let mut text = String::new();
28796 let mut highlights = Vec::new();
28797
28798 let insertion_highlight_style = HighlightStyle {
28799 color: Some(cx.theme().colors().text),
28800 ..Default::default()
28801 };
28802
28803 for (_, edit_text) in edits {
28804 let start_offset = text.len();
28805 text.push_str(edit_text);
28806 let end_offset = text.len();
28807
28808 if start_offset < end_offset {
28809 highlights.push((start_offset..end_offset, insertion_highlight_style));
28810 }
28811 }
28812
28813 HighlightedText {
28814 text: text.into(),
28815 highlights,
28816 }
28817}
28818
28819pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
28820 match severity {
28821 lsp::DiagnosticSeverity::ERROR => colors.error,
28822 lsp::DiagnosticSeverity::WARNING => colors.warning,
28823 lsp::DiagnosticSeverity::INFORMATION => colors.info,
28824 lsp::DiagnosticSeverity::HINT => colors.info,
28825 _ => colors.ignored,
28826 }
28827}
28828
28829pub fn styled_runs_for_code_label<'a>(
28830 label: &'a CodeLabel,
28831 syntax_theme: &'a theme::SyntaxTheme,
28832 local_player: &'a theme::PlayerColor,
28833) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
28834 let fade_out = HighlightStyle {
28835 fade_out: Some(0.35),
28836 ..Default::default()
28837 };
28838
28839 let mut prev_end = label.filter_range.end;
28840 label
28841 .runs
28842 .iter()
28843 .enumerate()
28844 .flat_map(move |(ix, (range, highlight_id))| {
28845 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
28846 HighlightStyle {
28847 color: Some(local_player.cursor),
28848 ..Default::default()
28849 }
28850 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
28851 HighlightStyle {
28852 background_color: Some(local_player.selection),
28853 ..Default::default()
28854 }
28855 } else if let Some(style) = highlight_id.style(syntax_theme) {
28856 style
28857 } else {
28858 return Default::default();
28859 };
28860 let muted_style = style.highlight(fade_out);
28861
28862 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
28863 if range.start >= label.filter_range.end {
28864 if range.start > prev_end {
28865 runs.push((prev_end..range.start, fade_out));
28866 }
28867 runs.push((range.clone(), muted_style));
28868 } else if range.end <= label.filter_range.end {
28869 runs.push((range.clone(), style));
28870 } else {
28871 runs.push((range.start..label.filter_range.end, style));
28872 runs.push((label.filter_range.end..range.end, muted_style));
28873 }
28874 prev_end = cmp::max(prev_end, range.end);
28875
28876 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
28877 runs.push((prev_end..label.text.len(), fade_out));
28878 }
28879
28880 runs
28881 })
28882}
28883
28884pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
28885 let mut prev_index = 0;
28886 let mut prev_codepoint: Option<char> = None;
28887 text.char_indices()
28888 .chain([(text.len(), '\0')])
28889 .filter_map(move |(index, codepoint)| {
28890 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28891 let is_boundary = index == text.len()
28892 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
28893 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
28894 if is_boundary {
28895 let chunk = &text[prev_index..index];
28896 prev_index = index;
28897 Some(chunk)
28898 } else {
28899 None
28900 }
28901 })
28902}
28903
28904/// Given a string of text immediately before the cursor, iterates over possible
28905/// strings a snippet could match to. More precisely: returns an iterator over
28906/// suffixes of `text` created by splitting at word boundaries (before & after
28907/// every non-word character).
28908///
28909/// Shorter suffixes are returned first.
28910pub(crate) fn snippet_candidate_suffixes<'a>(
28911 text: &'a str,
28912 is_word_char: &'a dyn Fn(char) -> bool,
28913) -> impl std::iter::Iterator<Item = &'a str> + 'a {
28914 let mut prev_index = text.len();
28915 let mut prev_codepoint = None;
28916 text.char_indices()
28917 .rev()
28918 .chain([(0, '\0')])
28919 .filter_map(move |(index, codepoint)| {
28920 let prev_index = std::mem::replace(&mut prev_index, index);
28921 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28922 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
28923 None
28924 } else {
28925 let chunk = &text[prev_index..]; // go to end of string
28926 Some(chunk)
28927 }
28928 })
28929}
28930
28931pub trait RangeToAnchorExt: Sized {
28932 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
28933
28934 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
28935 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
28936 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
28937 }
28938}
28939
28940impl<T: ToOffset> RangeToAnchorExt for Range<T> {
28941 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
28942 let start_offset = self.start.to_offset(snapshot);
28943 let end_offset = self.end.to_offset(snapshot);
28944 if start_offset == end_offset {
28945 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
28946 } else {
28947 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
28948 }
28949 }
28950}
28951
28952pub trait RowExt {
28953 fn as_f64(&self) -> f64;
28954
28955 fn next_row(&self) -> Self;
28956
28957 fn previous_row(&self) -> Self;
28958
28959 fn minus(&self, other: Self) -> u32;
28960}
28961
28962impl RowExt for DisplayRow {
28963 fn as_f64(&self) -> f64 {
28964 self.0 as _
28965 }
28966
28967 fn next_row(&self) -> Self {
28968 Self(self.0 + 1)
28969 }
28970
28971 fn previous_row(&self) -> Self {
28972 Self(self.0.saturating_sub(1))
28973 }
28974
28975 fn minus(&self, other: Self) -> u32 {
28976 self.0 - other.0
28977 }
28978}
28979
28980impl RowExt for MultiBufferRow {
28981 fn as_f64(&self) -> f64 {
28982 self.0 as _
28983 }
28984
28985 fn next_row(&self) -> Self {
28986 Self(self.0 + 1)
28987 }
28988
28989 fn previous_row(&self) -> Self {
28990 Self(self.0.saturating_sub(1))
28991 }
28992
28993 fn minus(&self, other: Self) -> u32 {
28994 self.0 - other.0
28995 }
28996}
28997
28998trait RowRangeExt {
28999 type Row;
29000
29001 fn len(&self) -> usize;
29002
29003 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
29004}
29005
29006impl RowRangeExt for Range<MultiBufferRow> {
29007 type Row = MultiBufferRow;
29008
29009 fn len(&self) -> usize {
29010 (self.end.0 - self.start.0) as usize
29011 }
29012
29013 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
29014 (self.start.0..self.end.0).map(MultiBufferRow)
29015 }
29016}
29017
29018impl RowRangeExt for Range<DisplayRow> {
29019 type Row = DisplayRow;
29020
29021 fn len(&self) -> usize {
29022 (self.end.0 - self.start.0) as usize
29023 }
29024
29025 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
29026 (self.start.0..self.end.0).map(DisplayRow)
29027 }
29028}
29029
29030/// If select range has more than one line, we
29031/// just point the cursor to range.start.
29032fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
29033 if range.start.row == range.end.row {
29034 range
29035 } else {
29036 range.start..range.start
29037 }
29038}
29039pub struct KillRing(ClipboardItem);
29040impl Global for KillRing {}
29041
29042const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
29043
29044enum BreakpointPromptEditAction {
29045 Log,
29046 Condition,
29047 HitCondition,
29048}
29049
29050struct BreakpointPromptEditor {
29051 pub(crate) prompt: Entity<Editor>,
29052 editor: WeakEntity<Editor>,
29053 breakpoint_anchor: Anchor,
29054 breakpoint: Breakpoint,
29055 edit_action: BreakpointPromptEditAction,
29056 block_ids: HashSet<CustomBlockId>,
29057 editor_margins: Arc<Mutex<EditorMargins>>,
29058 _subscriptions: Vec<Subscription>,
29059}
29060
29061impl BreakpointPromptEditor {
29062 const MAX_LINES: u8 = 4;
29063
29064 fn new(
29065 editor: WeakEntity<Editor>,
29066 breakpoint_anchor: Anchor,
29067 breakpoint: Breakpoint,
29068 edit_action: BreakpointPromptEditAction,
29069 window: &mut Window,
29070 cx: &mut Context<Self>,
29071 ) -> Self {
29072 let base_text = match edit_action {
29073 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
29074 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
29075 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
29076 }
29077 .map(|msg| msg.to_string())
29078 .unwrap_or_default();
29079
29080 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
29081 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
29082
29083 let prompt = cx.new(|cx| {
29084 let mut prompt = Editor::new(
29085 EditorMode::AutoHeight {
29086 min_lines: 1,
29087 max_lines: Some(Self::MAX_LINES as usize),
29088 },
29089 buffer,
29090 None,
29091 window,
29092 cx,
29093 );
29094 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
29095 prompt.set_show_cursor_when_unfocused(false, cx);
29096 prompt.set_placeholder_text(
29097 match edit_action {
29098 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
29099 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
29100 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
29101 },
29102 window,
29103 cx,
29104 );
29105
29106 prompt
29107 });
29108
29109 Self {
29110 prompt,
29111 editor,
29112 breakpoint_anchor,
29113 breakpoint,
29114 edit_action,
29115 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
29116 block_ids: Default::default(),
29117 _subscriptions: vec![],
29118 }
29119 }
29120
29121 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
29122 self.block_ids.extend(block_ids)
29123 }
29124
29125 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
29126 if let Some(editor) = self.editor.upgrade() {
29127 let message = self
29128 .prompt
29129 .read(cx)
29130 .buffer
29131 .read(cx)
29132 .as_singleton()
29133 .expect("A multi buffer in breakpoint prompt isn't possible")
29134 .read(cx)
29135 .as_rope()
29136 .to_string();
29137
29138 editor.update(cx, |editor, cx| {
29139 editor.edit_breakpoint_at_anchor(
29140 self.breakpoint_anchor,
29141 self.breakpoint.clone(),
29142 match self.edit_action {
29143 BreakpointPromptEditAction::Log => {
29144 BreakpointEditAction::EditLogMessage(message.into())
29145 }
29146 BreakpointPromptEditAction::Condition => {
29147 BreakpointEditAction::EditCondition(message.into())
29148 }
29149 BreakpointPromptEditAction::HitCondition => {
29150 BreakpointEditAction::EditHitCondition(message.into())
29151 }
29152 },
29153 cx,
29154 );
29155
29156 editor.remove_blocks(self.block_ids.clone(), None, cx);
29157 cx.focus_self(window);
29158 });
29159 }
29160 }
29161
29162 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
29163 self.editor
29164 .update(cx, |editor, cx| {
29165 editor.remove_blocks(self.block_ids.clone(), None, cx);
29166 window.focus(&editor.focus_handle, cx);
29167 })
29168 .log_err();
29169 }
29170
29171 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
29172 let settings = ThemeSettings::get_global(cx);
29173 let text_style = TextStyle {
29174 color: if self.prompt.read(cx).read_only(cx) {
29175 cx.theme().colors().text_disabled
29176 } else {
29177 cx.theme().colors().text
29178 },
29179 font_family: settings.buffer_font.family.clone(),
29180 font_fallbacks: settings.buffer_font.fallbacks.clone(),
29181 font_size: settings.buffer_font_size(cx).into(),
29182 font_weight: settings.buffer_font.weight,
29183 line_height: relative(settings.buffer_line_height.value()),
29184 ..Default::default()
29185 };
29186 EditorElement::new(
29187 &self.prompt,
29188 EditorStyle {
29189 background: cx.theme().colors().editor_background,
29190 local_player: cx.theme().players().local(),
29191 text: text_style,
29192 ..Default::default()
29193 },
29194 )
29195 }
29196
29197 fn render_close_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
29198 let focus_handle = self.prompt.focus_handle(cx);
29199 IconButton::new("cancel", IconName::Close)
29200 .icon_color(Color::Muted)
29201 .shape(IconButtonShape::Square)
29202 .tooltip(move |_window, cx| {
29203 Tooltip::for_action_in("Cancel", &menu::Cancel, &focus_handle, cx)
29204 })
29205 .on_click(cx.listener(|this, _, window, cx| {
29206 this.cancel(&menu::Cancel, window, cx);
29207 }))
29208 }
29209
29210 fn render_confirm_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
29211 let focus_handle = self.prompt.focus_handle(cx);
29212 IconButton::new("confirm", IconName::Return)
29213 .icon_color(Color::Muted)
29214 .shape(IconButtonShape::Square)
29215 .tooltip(move |_window, cx| {
29216 Tooltip::for_action_in("Confirm", &menu::Confirm, &focus_handle, cx)
29217 })
29218 .on_click(cx.listener(|this, _, window, cx| {
29219 this.confirm(&menu::Confirm, window, cx);
29220 }))
29221 }
29222}
29223
29224impl Render for BreakpointPromptEditor {
29225 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
29226 let ui_font_size = ThemeSettings::get_global(cx).ui_font_size(cx);
29227 let editor_margins = *self.editor_margins.lock();
29228 let gutter_dimensions = editor_margins.gutter;
29229 let left_gutter_width = gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0);
29230 let right_padding = editor_margins.right + px(9.);
29231 h_flex()
29232 .key_context("Editor")
29233 .bg(cx.theme().colors().editor_background)
29234 .border_y_1()
29235 .border_color(cx.theme().status().info_border)
29236 .size_full()
29237 .py(window.line_height() / 2.5)
29238 .pr(right_padding)
29239 .on_action(cx.listener(Self::confirm))
29240 .on_action(cx.listener(Self::cancel))
29241 .child(
29242 WithRemSize::new(ui_font_size)
29243 .h_full()
29244 .w(left_gutter_width)
29245 .flex()
29246 .flex_row()
29247 .flex_shrink_0()
29248 .items_center()
29249 .justify_center()
29250 .gap_1()
29251 .child(self.render_close_button(cx)),
29252 )
29253 .child(
29254 h_flex()
29255 .w_full()
29256 .justify_between()
29257 .child(div().flex_1().child(self.render_prompt_editor(cx)))
29258 .child(
29259 WithRemSize::new(ui_font_size)
29260 .flex()
29261 .flex_row()
29262 .items_center()
29263 .child(self.render_confirm_button(cx)),
29264 ),
29265 )
29266 }
29267}
29268
29269impl Focusable for BreakpointPromptEditor {
29270 fn focus_handle(&self, cx: &App) -> FocusHandle {
29271 self.prompt.focus_handle(cx)
29272 }
29273}
29274
29275fn all_edits_insertions_or_deletions(
29276 edits: &Vec<(Range<Anchor>, Arc<str>)>,
29277 snapshot: &MultiBufferSnapshot,
29278) -> bool {
29279 let mut all_insertions = true;
29280 let mut all_deletions = true;
29281
29282 for (range, new_text) in edits.iter() {
29283 let range_is_empty = range.to_offset(snapshot).is_empty();
29284 let text_is_empty = new_text.is_empty();
29285
29286 if range_is_empty != text_is_empty {
29287 if range_is_empty {
29288 all_deletions = false;
29289 } else {
29290 all_insertions = false;
29291 }
29292 } else {
29293 return false;
29294 }
29295
29296 if !all_insertions && !all_deletions {
29297 return false;
29298 }
29299 }
29300 all_insertions || all_deletions
29301}
29302
29303struct MissingEditPredictionKeybindingTooltip;
29304
29305impl Render for MissingEditPredictionKeybindingTooltip {
29306 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
29307 ui::tooltip_container(cx, |container, cx| {
29308 container
29309 .flex_shrink_0()
29310 .max_w_80()
29311 .min_h(rems_from_px(124.))
29312 .justify_between()
29313 .child(
29314 v_flex()
29315 .flex_1()
29316 .text_ui_sm(cx)
29317 .child(Label::new("Conflict with Accept Keybinding"))
29318 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
29319 )
29320 .child(
29321 h_flex()
29322 .pb_1()
29323 .gap_1()
29324 .items_end()
29325 .w_full()
29326 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
29327 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
29328 }))
29329 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
29330 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
29331 })),
29332 )
29333 })
29334 }
29335}
29336
29337#[derive(Debug, Clone, Copy, PartialEq)]
29338pub struct LineHighlight {
29339 pub background: Background,
29340 pub border: Option<gpui::Hsla>,
29341 pub include_gutter: bool,
29342 pub type_id: Option<TypeId>,
29343}
29344
29345struct LineManipulationResult {
29346 pub new_text: String,
29347 pub line_count_before: usize,
29348 pub line_count_after: usize,
29349}
29350
29351fn render_diff_hunk_controls(
29352 row: u32,
29353 status: &DiffHunkStatus,
29354 hunk_range: Range<Anchor>,
29355 is_created_file: bool,
29356 line_height: Pixels,
29357 editor: &Entity<Editor>,
29358 _window: &mut Window,
29359 cx: &mut App,
29360) -> AnyElement {
29361 h_flex()
29362 .h(line_height)
29363 .mr_1()
29364 .gap_1()
29365 .px_0p5()
29366 .pb_1()
29367 .border_x_1()
29368 .border_b_1()
29369 .border_color(cx.theme().colors().border_variant)
29370 .rounded_b_lg()
29371 .bg(cx.theme().colors().editor_background)
29372 .gap_1()
29373 .block_mouse_except_scroll()
29374 .shadow_md()
29375 .child(if status.has_secondary_hunk() {
29376 Button::new(("stage", row as u64), "Stage")
29377 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
29378 .tooltip({
29379 let focus_handle = editor.focus_handle(cx);
29380 move |_window, cx| {
29381 Tooltip::for_action_in(
29382 "Stage Hunk",
29383 &::git::ToggleStaged,
29384 &focus_handle,
29385 cx,
29386 )
29387 }
29388 })
29389 .on_click({
29390 let editor = editor.clone();
29391 move |_event, _window, cx| {
29392 editor.update(cx, |editor, cx| {
29393 editor.stage_or_unstage_diff_hunks(
29394 true,
29395 vec![hunk_range.start..hunk_range.start],
29396 cx,
29397 );
29398 });
29399 }
29400 })
29401 } else {
29402 Button::new(("unstage", row as u64), "Unstage")
29403 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
29404 .tooltip({
29405 let focus_handle = editor.focus_handle(cx);
29406 move |_window, cx| {
29407 Tooltip::for_action_in(
29408 "Unstage Hunk",
29409 &::git::ToggleStaged,
29410 &focus_handle,
29411 cx,
29412 )
29413 }
29414 })
29415 .on_click({
29416 let editor = editor.clone();
29417 move |_event, _window, cx| {
29418 editor.update(cx, |editor, cx| {
29419 editor.stage_or_unstage_diff_hunks(
29420 false,
29421 vec![hunk_range.start..hunk_range.start],
29422 cx,
29423 );
29424 });
29425 }
29426 })
29427 })
29428 .child(
29429 Button::new(("restore", row as u64), "Restore")
29430 .tooltip({
29431 let focus_handle = editor.focus_handle(cx);
29432 move |_window, cx| {
29433 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
29434 }
29435 })
29436 .on_click({
29437 let editor = editor.clone();
29438 move |_event, window, cx| {
29439 editor.update(cx, |editor, cx| {
29440 let snapshot = editor.snapshot(window, cx);
29441 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
29442 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
29443 });
29444 }
29445 })
29446 .disabled(is_created_file),
29447 )
29448 .when(
29449 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
29450 |el| {
29451 el.child(
29452 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
29453 .shape(IconButtonShape::Square)
29454 .icon_size(IconSize::Small)
29455 // .disabled(!has_multiple_hunks)
29456 .tooltip({
29457 let focus_handle = editor.focus_handle(cx);
29458 move |_window, cx| {
29459 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
29460 }
29461 })
29462 .on_click({
29463 let editor = editor.clone();
29464 move |_event, window, cx| {
29465 editor.update(cx, |editor, cx| {
29466 let snapshot = editor.snapshot(window, cx);
29467 let position =
29468 hunk_range.end.to_point(&snapshot.buffer_snapshot());
29469 editor.go_to_hunk_before_or_after_position(
29470 &snapshot,
29471 position,
29472 Direction::Next,
29473 true,
29474 window,
29475 cx,
29476 );
29477 editor.expand_selected_diff_hunks(cx);
29478 });
29479 }
29480 }),
29481 )
29482 .child(
29483 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
29484 .shape(IconButtonShape::Square)
29485 .icon_size(IconSize::Small)
29486 // .disabled(!has_multiple_hunks)
29487 .tooltip({
29488 let focus_handle = editor.focus_handle(cx);
29489 move |_window, cx| {
29490 Tooltip::for_action_in(
29491 "Previous Hunk",
29492 &GoToPreviousHunk,
29493 &focus_handle,
29494 cx,
29495 )
29496 }
29497 })
29498 .on_click({
29499 let editor = editor.clone();
29500 move |_event, window, cx| {
29501 editor.update(cx, |editor, cx| {
29502 let snapshot = editor.snapshot(window, cx);
29503 let point =
29504 hunk_range.start.to_point(&snapshot.buffer_snapshot());
29505 editor.go_to_hunk_before_or_after_position(
29506 &snapshot,
29507 point,
29508 Direction::Prev,
29509 true,
29510 window,
29511 cx,
29512 );
29513 editor.expand_selected_diff_hunks(cx);
29514 });
29515 }
29516 }),
29517 )
29518 },
29519 )
29520 .into_any_element()
29521}
29522
29523pub fn multibuffer_context_lines(cx: &App) -> u32 {
29524 EditorSettings::try_get(cx)
29525 .map(|settings| settings.excerpt_context_lines)
29526 .unwrap_or(2)
29527 .min(32)
29528}