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}
1873
1874impl Editor {
1875 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1876 let buffer = cx.new(|cx| Buffer::local("", cx));
1877 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1878 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1879 }
1880
1881 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1882 let buffer = cx.new(|cx| Buffer::local("", cx));
1883 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1884 Self::new(EditorMode::full(), buffer, None, window, cx)
1885 }
1886
1887 pub fn auto_height(
1888 min_lines: usize,
1889 max_lines: usize,
1890 window: &mut Window,
1891 cx: &mut Context<Self>,
1892 ) -> Self {
1893 let buffer = cx.new(|cx| Buffer::local("", cx));
1894 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1895 Self::new(
1896 EditorMode::AutoHeight {
1897 min_lines,
1898 max_lines: Some(max_lines),
1899 },
1900 buffer,
1901 None,
1902 window,
1903 cx,
1904 )
1905 }
1906
1907 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1908 /// The editor grows as tall as needed to fit its content.
1909 pub fn auto_height_unbounded(
1910 min_lines: usize,
1911 window: &mut Window,
1912 cx: &mut Context<Self>,
1913 ) -> Self {
1914 let buffer = cx.new(|cx| Buffer::local("", cx));
1915 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1916 Self::new(
1917 EditorMode::AutoHeight {
1918 min_lines,
1919 max_lines: None,
1920 },
1921 buffer,
1922 None,
1923 window,
1924 cx,
1925 )
1926 }
1927
1928 pub fn for_buffer(
1929 buffer: Entity<Buffer>,
1930 project: Option<Entity<Project>>,
1931 window: &mut Window,
1932 cx: &mut Context<Self>,
1933 ) -> Self {
1934 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1935 Self::new(EditorMode::full(), buffer, project, window, cx)
1936 }
1937
1938 pub fn for_multibuffer(
1939 buffer: Entity<MultiBuffer>,
1940 project: Option<Entity<Project>>,
1941 window: &mut Window,
1942 cx: &mut Context<Self>,
1943 ) -> Self {
1944 Self::new(EditorMode::full(), buffer, project, window, cx)
1945 }
1946
1947 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1948 let mut clone = Self::new(
1949 self.mode.clone(),
1950 self.buffer.clone(),
1951 self.project.clone(),
1952 window,
1953 cx,
1954 );
1955 let my_snapshot = self.display_map.update(cx, |display_map, cx| {
1956 let snapshot = display_map.snapshot(cx);
1957 clone.display_map.update(cx, |display_map, cx| {
1958 display_map.set_state(&snapshot, cx);
1959 });
1960 snapshot
1961 });
1962 let clone_snapshot = clone.display_map.update(cx, |map, cx| map.snapshot(cx));
1963 clone.folds_did_change(cx);
1964 clone.selections.clone_state(&self.selections);
1965 clone
1966 .scroll_manager
1967 .clone_state(&self.scroll_manager, &my_snapshot, &clone_snapshot, cx);
1968 clone.searchable = self.searchable;
1969 clone.read_only = self.read_only;
1970 clone.buffers_with_disabled_indent_guides =
1971 self.buffers_with_disabled_indent_guides.clone();
1972 clone
1973 }
1974
1975 pub fn new(
1976 mode: EditorMode,
1977 buffer: Entity<MultiBuffer>,
1978 project: Option<Entity<Project>>,
1979 window: &mut Window,
1980 cx: &mut Context<Self>,
1981 ) -> Self {
1982 Editor::new_internal(mode, buffer, project, None, window, cx)
1983 }
1984
1985 pub fn refresh_sticky_headers(
1986 &mut self,
1987 display_snapshot: &DisplaySnapshot,
1988 cx: &mut Context<Editor>,
1989 ) {
1990 if !self.mode.is_full() {
1991 return;
1992 }
1993 let multi_buffer = display_snapshot.buffer_snapshot();
1994 let scroll_anchor = self
1995 .scroll_manager
1996 .native_anchor(display_snapshot, cx)
1997 .anchor;
1998 let Some((excerpt_id, _, buffer)) = multi_buffer.as_singleton() else {
1999 return;
2000 };
2001 let buffer = buffer.clone();
2002
2003 let buffer_visible_start = scroll_anchor.text_anchor.to_point(&buffer);
2004 let max_row = buffer.max_point().row;
2005 let start_row = buffer_visible_start.row.min(max_row);
2006 let end_row = (buffer_visible_start.row + 10).min(max_row);
2007
2008 let syntax = self.style(cx).syntax.clone();
2009 let background_task = cx.background_spawn(async move {
2010 buffer
2011 .outline_items_containing(
2012 Point::new(start_row, 0)..Point::new(end_row, 0),
2013 true,
2014 Some(syntax.as_ref()),
2015 )
2016 .into_iter()
2017 .map(|outline_item| OutlineItem {
2018 depth: outline_item.depth,
2019 range: Anchor::range_in_buffer(excerpt_id, outline_item.range),
2020 source_range_for_text: Anchor::range_in_buffer(
2021 excerpt_id,
2022 outline_item.source_range_for_text,
2023 ),
2024 text: outline_item.text,
2025 highlight_ranges: outline_item.highlight_ranges,
2026 name_ranges: outline_item.name_ranges,
2027 body_range: outline_item
2028 .body_range
2029 .map(|range| Anchor::range_in_buffer(excerpt_id, range)),
2030 annotation_range: outline_item
2031 .annotation_range
2032 .map(|range| Anchor::range_in_buffer(excerpt_id, range)),
2033 })
2034 .collect()
2035 });
2036 self.sticky_headers_task = cx.spawn(async move |this, cx| {
2037 let sticky_headers = background_task.await;
2038 this.update(cx, |this, cx| {
2039 this.sticky_headers = Some(sticky_headers);
2040 cx.notify();
2041 })
2042 .ok();
2043 });
2044 }
2045
2046 fn new_internal(
2047 mode: EditorMode,
2048 multi_buffer: Entity<MultiBuffer>,
2049 project: Option<Entity<Project>>,
2050 display_map: Option<Entity<DisplayMap>>,
2051 window: &mut Window,
2052 cx: &mut Context<Self>,
2053 ) -> Self {
2054 debug_assert!(
2055 display_map.is_none() || mode.is_minimap(),
2056 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
2057 );
2058
2059 let full_mode = mode.is_full();
2060 let is_minimap = mode.is_minimap();
2061 let diagnostics_max_severity = if full_mode {
2062 EditorSettings::get_global(cx)
2063 .diagnostics_max_severity
2064 .unwrap_or(DiagnosticSeverity::Hint)
2065 } else {
2066 DiagnosticSeverity::Off
2067 };
2068 let style = window.text_style();
2069 let font_size = style.font_size.to_pixels(window.rem_size());
2070 let editor = cx.entity().downgrade();
2071 let fold_placeholder = FoldPlaceholder {
2072 constrain_width: false,
2073 render: Arc::new(move |fold_id, fold_range, cx| {
2074 let editor = editor.clone();
2075 FoldPlaceholder::fold_element(fold_id, cx)
2076 .cursor_pointer()
2077 .child("⋯")
2078 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
2079 .on_click(move |_, _window, cx| {
2080 editor
2081 .update(cx, |editor, cx| {
2082 editor.unfold_ranges(
2083 &[fold_range.start..fold_range.end],
2084 true,
2085 false,
2086 cx,
2087 );
2088 cx.stop_propagation();
2089 })
2090 .ok();
2091 })
2092 .into_any()
2093 }),
2094 merge_adjacent: true,
2095 ..FoldPlaceholder::default()
2096 };
2097 let display_map = display_map.unwrap_or_else(|| {
2098 cx.new(|cx| {
2099 DisplayMap::new(
2100 multi_buffer.clone(),
2101 style.font(),
2102 font_size,
2103 None,
2104 FILE_HEADER_HEIGHT,
2105 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2106 fold_placeholder,
2107 diagnostics_max_severity,
2108 cx,
2109 )
2110 })
2111 });
2112
2113 let selections = SelectionsCollection::new();
2114
2115 let blink_manager = cx.new(|cx| {
2116 let mut blink_manager = BlinkManager::new(
2117 CURSOR_BLINK_INTERVAL,
2118 |cx| EditorSettings::get_global(cx).cursor_blink,
2119 cx,
2120 );
2121 if is_minimap {
2122 blink_manager.disable(cx);
2123 }
2124 blink_manager
2125 });
2126
2127 let soft_wrap_mode_override =
2128 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
2129
2130 let mut project_subscriptions = Vec::new();
2131 if full_mode && let Some(project) = project.as_ref() {
2132 project_subscriptions.push(cx.subscribe_in(
2133 project,
2134 window,
2135 |editor, _, event, window, cx| match event {
2136 project::Event::RefreshCodeLens => {
2137 // we always query lens with actions, without storing them, always refreshing them
2138 }
2139 project::Event::RefreshInlayHints {
2140 server_id,
2141 request_id,
2142 } => {
2143 editor.refresh_inlay_hints(
2144 InlayHintRefreshReason::RefreshRequested {
2145 server_id: *server_id,
2146 request_id: *request_id,
2147 },
2148 cx,
2149 );
2150 }
2151 project::Event::RefreshSemanticTokens {
2152 server_id,
2153 request_id,
2154 } => {
2155 editor.refresh_semantic_tokens(
2156 None,
2157 Some(RefreshForServer {
2158 server_id: *server_id,
2159 request_id: *request_id,
2160 }),
2161 cx,
2162 );
2163 }
2164 project::Event::LanguageServerRemoved(_) => {
2165 editor.registered_buffers.clear();
2166 editor.register_visible_buffers(cx);
2167 editor.invalidate_semantic_tokens(None);
2168 editor.refresh_runnables(None, window, cx);
2169 editor.update_lsp_data(None, window, cx);
2170 editor.refresh_inlay_hints(InlayHintRefreshReason::ServerRemoved, cx);
2171 }
2172 project::Event::SnippetEdit(id, snippet_edits) => {
2173 // todo(lw): Non singletons
2174 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
2175 let snapshot = buffer.read(cx).snapshot();
2176 let focus_handle = editor.focus_handle(cx);
2177 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
2178 for (range, snippet) in snippet_edits {
2179 let buffer_range =
2180 language::range_from_lsp(*range).to_offset(&snapshot);
2181 editor
2182 .insert_snippet(
2183 &[MultiBufferOffset(buffer_range.start)
2184 ..MultiBufferOffset(buffer_range.end)],
2185 snippet.clone(),
2186 window,
2187 cx,
2188 )
2189 .ok();
2190 }
2191 }
2192 }
2193 }
2194 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
2195 let buffer_id = *buffer_id;
2196 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
2197 editor.register_buffer(buffer_id, cx);
2198 editor.refresh_runnables(Some(buffer_id), window, cx);
2199 editor.update_lsp_data(Some(buffer_id), window, cx);
2200 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
2201 refresh_linked_ranges(editor, window, cx);
2202 editor.refresh_code_actions(window, cx);
2203 editor.refresh_document_highlights(cx);
2204 }
2205 }
2206
2207 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
2208 let Some(workspace) = editor.workspace() else {
2209 return;
2210 };
2211 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2212 else {
2213 return;
2214 };
2215
2216 if active_editor.entity_id() == cx.entity_id() {
2217 let entity_id = cx.entity_id();
2218 workspace.update(cx, |this, cx| {
2219 this.panes_mut()
2220 .iter_mut()
2221 .filter(|pane| pane.entity_id() != entity_id)
2222 .for_each(|p| {
2223 p.update(cx, |pane, _| {
2224 pane.nav_history_mut().rename_item(
2225 entity_id,
2226 project_path.clone(),
2227 abs_path.clone().into(),
2228 );
2229 })
2230 });
2231 });
2232
2233 Self::open_transaction_for_hidden_buffers(
2234 workspace,
2235 transaction.clone(),
2236 "Rename".to_string(),
2237 window,
2238 cx,
2239 );
2240 }
2241 }
2242
2243 project::Event::WorkspaceEditApplied(transaction) => {
2244 let Some(workspace) = editor.workspace() else {
2245 return;
2246 };
2247 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2248 else {
2249 return;
2250 };
2251
2252 if active_editor.entity_id() == cx.entity_id() {
2253 Self::open_transaction_for_hidden_buffers(
2254 workspace,
2255 transaction.clone(),
2256 "LSP Edit".to_string(),
2257 window,
2258 cx,
2259 );
2260 }
2261 }
2262
2263 _ => {}
2264 },
2265 ));
2266 if let Some(task_inventory) = project
2267 .read(cx)
2268 .task_store()
2269 .read(cx)
2270 .task_inventory()
2271 .cloned()
2272 {
2273 project_subscriptions.push(cx.observe_in(
2274 &task_inventory,
2275 window,
2276 |editor, _, window, cx| {
2277 editor.refresh_runnables(None, window, cx);
2278 },
2279 ));
2280 };
2281
2282 project_subscriptions.push(cx.subscribe_in(
2283 &project.read(cx).breakpoint_store(),
2284 window,
2285 |editor, _, event, window, cx| match event {
2286 BreakpointStoreEvent::ClearDebugLines => {
2287 editor.clear_row_highlights::<ActiveDebugLine>();
2288 editor.refresh_inline_values(cx);
2289 }
2290 BreakpointStoreEvent::SetDebugLine => {
2291 if editor.go_to_active_debug_line(window, cx) {
2292 cx.stop_propagation();
2293 }
2294
2295 editor.refresh_inline_values(cx);
2296 }
2297 _ => {}
2298 },
2299 ));
2300 let git_store = project.read(cx).git_store().clone();
2301 let project = project.clone();
2302 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2303 if let GitStoreEvent::RepositoryAdded = event {
2304 this.load_diff_task = Some(
2305 update_uncommitted_diff_for_buffer(
2306 cx.entity(),
2307 &project,
2308 this.buffer.read(cx).all_buffers(),
2309 this.buffer.clone(),
2310 cx,
2311 )
2312 .shared(),
2313 );
2314 }
2315 }));
2316 }
2317
2318 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2319
2320 let inlay_hint_settings =
2321 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2322 let focus_handle = cx.focus_handle();
2323 if !is_minimap {
2324 cx.on_focus(&focus_handle, window, Self::handle_focus)
2325 .detach();
2326 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2327 .detach();
2328 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2329 .detach();
2330 cx.on_blur(&focus_handle, window, Self::handle_blur)
2331 .detach();
2332 cx.observe_pending_input(window, Self::observe_pending_input)
2333 .detach();
2334 }
2335
2336 let show_indent_guides =
2337 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2338 Some(false)
2339 } else {
2340 None
2341 };
2342
2343 let breakpoint_store = match (&mode, project.as_ref()) {
2344 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2345 _ => None,
2346 };
2347
2348 let mut code_action_providers = Vec::new();
2349 let mut load_uncommitted_diff = None;
2350 if let Some(project) = project.clone() {
2351 load_uncommitted_diff = Some(
2352 update_uncommitted_diff_for_buffer(
2353 cx.entity(),
2354 &project,
2355 multi_buffer.read(cx).all_buffers(),
2356 multi_buffer.clone(),
2357 cx,
2358 )
2359 .shared(),
2360 );
2361 code_action_providers.push(Rc::new(project) as Rc<_>);
2362 }
2363
2364 let mut editor = Self {
2365 focus_handle,
2366 show_cursor_when_unfocused: false,
2367 last_focused_descendant: None,
2368 buffer: multi_buffer.clone(),
2369 display_map: display_map.clone(),
2370 placeholder_display_map: None,
2371 selections,
2372 scroll_manager: ScrollManager::new(cx),
2373 columnar_selection_state: None,
2374 add_selections_state: None,
2375 select_next_state: None,
2376 select_prev_state: None,
2377 selection_history: SelectionHistory::default(),
2378 defer_selection_effects: false,
2379 deferred_selection_effects_state: None,
2380 autoclose_regions: Vec::new(),
2381 snippet_stack: InvalidationStack::default(),
2382 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2383 ime_transaction: None,
2384 active_diagnostics: ActiveDiagnostic::None,
2385 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2386 inline_diagnostics_update: Task::ready(()),
2387 inline_diagnostics: Vec::new(),
2388 soft_wrap_mode_override,
2389 diagnostics_max_severity,
2390 hard_wrap: None,
2391 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2392 semantics_provider: project
2393 .as_ref()
2394 .map(|project| Rc::new(project.downgrade()) as _),
2395 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2396 project,
2397 blink_manager: blink_manager.clone(),
2398 show_local_selections: true,
2399 show_scrollbars: ScrollbarAxes {
2400 horizontal: full_mode,
2401 vertical: full_mode,
2402 },
2403 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2404 offset_content: !matches!(mode, EditorMode::SingleLine),
2405 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2406 show_gutter: full_mode,
2407 show_line_numbers: (!full_mode).then_some(false),
2408 use_relative_line_numbers: None,
2409 disable_expand_excerpt_buttons: !full_mode,
2410 delegate_expand_excerpts: false,
2411 delegate_stage_and_restore: false,
2412 delegate_open_excerpts: false,
2413 enable_lsp_data: true,
2414 enable_runnables: true,
2415 show_git_diff_gutter: None,
2416 show_code_actions: None,
2417 show_runnables: None,
2418 show_breakpoints: None,
2419 show_diff_review_button: false,
2420 show_wrap_guides: None,
2421 show_indent_guides,
2422 buffers_with_disabled_indent_guides: HashSet::default(),
2423 highlight_order: 0,
2424 highlighted_rows: HashMap::default(),
2425 background_highlights: HashMap::default(),
2426 gutter_highlights: HashMap::default(),
2427 scrollbar_marker_state: ScrollbarMarkerState::default(),
2428 active_indent_guides_state: ActiveIndentGuidesState::default(),
2429 nav_history: None,
2430 context_menu: RefCell::new(None),
2431 context_menu_options: None,
2432 mouse_context_menu: None,
2433 completion_tasks: Vec::new(),
2434 inline_blame_popover: None,
2435 inline_blame_popover_show_task: None,
2436 signature_help_state: SignatureHelpState::default(),
2437 auto_signature_help: None,
2438 find_all_references_task_sources: Vec::new(),
2439 next_completion_id: 0,
2440 next_inlay_id: 0,
2441 code_action_providers,
2442 available_code_actions: None,
2443 code_actions_task: None,
2444 quick_selection_highlight_task: None,
2445 debounced_selection_highlight_task: None,
2446 debounced_selection_highlight_complete: false,
2447 document_highlights_task: None,
2448 linked_editing_range_task: None,
2449 pending_rename: None,
2450 searchable: !is_minimap,
2451 cursor_shape: EditorSettings::get_global(cx)
2452 .cursor_shape
2453 .unwrap_or_default(),
2454 cursor_offset_on_selection: false,
2455 current_line_highlight: None,
2456 autoindent_mode: Some(AutoindentMode::EachLine),
2457 collapse_matches: false,
2458 workspace: None,
2459 input_enabled: !is_minimap,
2460 expects_character_input: !is_minimap,
2461 use_modal_editing: full_mode,
2462 read_only: is_minimap,
2463 use_autoclose: true,
2464 use_auto_surround: true,
2465 auto_replace_emoji_shortcode: false,
2466 jsx_tag_auto_close_enabled_in_any_buffer: false,
2467 leader_id: None,
2468 remote_id: None,
2469 hover_state: HoverState::default(),
2470 pending_mouse_down: None,
2471 prev_pressure_stage: None,
2472 hovered_link_state: None,
2473 edit_prediction_provider: None,
2474 active_edit_prediction: None,
2475 stale_edit_prediction_in_menu: None,
2476 edit_prediction_preview: EditPredictionPreview::Inactive {
2477 released_too_fast: false,
2478 },
2479 inline_diagnostics_enabled: full_mode,
2480 diagnostics_enabled: full_mode,
2481 word_completions_enabled: full_mode,
2482 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2483 gutter_hovered: false,
2484 pixel_position_of_newest_cursor: None,
2485 last_bounds: None,
2486 last_position_map: None,
2487 expect_bounds_change: None,
2488 gutter_dimensions: GutterDimensions::default(),
2489 style: None,
2490 show_cursor_names: false,
2491 hovered_cursors: HashMap::default(),
2492 next_editor_action_id: EditorActionId::default(),
2493 editor_actions: Rc::default(),
2494 edit_predictions_hidden_for_vim_mode: false,
2495 show_edit_predictions_override: None,
2496 show_completions_on_input_override: None,
2497 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2498 edit_prediction_settings: EditPredictionSettings::Disabled,
2499 in_leading_whitespace: false,
2500 custom_context_menu: None,
2501 show_git_blame_gutter: false,
2502 show_git_blame_inline: false,
2503 show_selection_menu: None,
2504 show_git_blame_inline_delay_task: None,
2505 git_blame_inline_enabled: full_mode
2506 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2507 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2508 buffer_serialization: is_minimap.not().then(|| {
2509 BufferSerialization::new(
2510 ProjectSettings::get_global(cx)
2511 .session
2512 .restore_unsaved_buffers,
2513 )
2514 }),
2515 blame: None,
2516 blame_subscription: None,
2517
2518 breakpoint_store,
2519 gutter_breakpoint_indicator: (None, None),
2520 gutter_diff_review_indicator: (None, None),
2521 diff_review_drag_state: None,
2522 diff_review_overlays: Vec::new(),
2523 stored_review_comments: Vec::new(),
2524 next_review_comment_id: 0,
2525 hovered_diff_hunk_row: None,
2526 _subscriptions: (!is_minimap)
2527 .then(|| {
2528 vec![
2529 cx.observe(&multi_buffer, Self::on_buffer_changed),
2530 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2531 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2532 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2533 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2534 cx.observe_global_in::<GlobalTheme>(window, Self::theme_changed),
2535 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2536 cx.observe_window_activation(window, |editor, window, cx| {
2537 let active = window.is_window_active();
2538 editor.blink_manager.update(cx, |blink_manager, cx| {
2539 if active {
2540 blink_manager.enable(cx);
2541 } else {
2542 blink_manager.disable(cx);
2543 }
2544 });
2545 if active {
2546 editor.show_mouse_cursor(cx);
2547 }
2548 }),
2549 ]
2550 })
2551 .unwrap_or_default(),
2552 runnables: RunnableData::new(),
2553 pull_diagnostics_task: Task::ready(()),
2554 colors: None,
2555 refresh_colors_task: Task::ready(()),
2556 use_document_folding_ranges: false,
2557 refresh_folding_ranges_task: Task::ready(()),
2558 inlay_hints: None,
2559 next_color_inlay_id: 0,
2560 post_scroll_update: Task::ready(()),
2561 linked_edit_ranges: Default::default(),
2562 in_project_search: false,
2563 previous_search_ranges: None,
2564 breadcrumb_header: None,
2565 focused_block: None,
2566 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2567 addons: HashMap::default(),
2568 registered_buffers: HashMap::default(),
2569 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2570 selection_mark_mode: false,
2571 toggle_fold_multiple_buffers: Task::ready(()),
2572 serialize_selections: Task::ready(()),
2573 serialize_folds: Task::ready(()),
2574 text_style_refinement: None,
2575 load_diff_task: load_uncommitted_diff,
2576 temporary_diff_override: false,
2577 mouse_cursor_hidden: false,
2578 minimap: None,
2579 hide_mouse_mode: EditorSettings::get_global(cx)
2580 .hide_mouse
2581 .unwrap_or_default(),
2582 change_list: ChangeList::new(),
2583 mode,
2584 selection_drag_state: SelectionDragState::None,
2585 folding_newlines: Task::ready(()),
2586 lookup_key: None,
2587 select_next_is_case_sensitive: None,
2588 on_local_selections_changed: None,
2589 suppress_selection_callback: false,
2590 applicable_language_settings: HashMap::default(),
2591 semantic_token_state: SemanticTokenState::new(cx, full_mode),
2592 accent_data: None,
2593 bracket_fetched_tree_sitter_chunks: HashMap::default(),
2594 number_deleted_lines: false,
2595 refresh_matching_bracket_highlights_task: Task::ready(()),
2596 refresh_document_symbols_task: Task::ready(()).shared(),
2597 lsp_document_symbols: HashMap::default(),
2598 refresh_outline_symbols_at_cursor_at_cursor_task: Task::ready(()),
2599 outline_symbols_at_cursor: None,
2600 sticky_headers_task: Task::ready(()),
2601 sticky_headers: None,
2602 colorize_brackets_task: Task::ready(()),
2603 };
2604
2605 if is_minimap {
2606 return editor;
2607 }
2608
2609 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2610 editor.accent_data = editor.fetch_accent_data(cx);
2611
2612 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2613 editor
2614 ._subscriptions
2615 .push(cx.observe(breakpoints, |_, _, cx| {
2616 cx.notify();
2617 }));
2618 }
2619 editor._subscriptions.extend(project_subscriptions);
2620
2621 editor._subscriptions.push(cx.subscribe_in(
2622 &cx.entity(),
2623 window,
2624 |editor, _, e: &EditorEvent, window, cx| match e {
2625 EditorEvent::ScrollPositionChanged { local, .. } => {
2626 if *local {
2627 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2628 editor.inline_blame_popover.take();
2629 let snapshot = editor.snapshot(window, cx);
2630 let new_anchor = editor
2631 .scroll_manager
2632 .native_anchor(&snapshot.display_snapshot, cx);
2633 editor.update_restoration_data(cx, move |data| {
2634 data.scroll_position = (
2635 new_anchor.top_row(snapshot.buffer_snapshot()),
2636 new_anchor.offset,
2637 );
2638 });
2639
2640 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2641 cx.background_executor()
2642 .timer(Duration::from_millis(50))
2643 .await;
2644 editor
2645 .update_in(cx, |editor, window, cx| {
2646 editor.update_data_on_scroll(window, cx)
2647 })
2648 .ok();
2649 });
2650 }
2651 editor.refresh_sticky_headers(&editor.snapshot(window, cx), cx);
2652 }
2653 EditorEvent::Edited { .. } => {
2654 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2655 .map(|vim_mode| vim_mode.0)
2656 .unwrap_or(false);
2657 if !vim_mode {
2658 let display_map = editor.display_snapshot(cx);
2659 let selections = editor.selections.all_adjusted_display(&display_map);
2660 let pop_state = editor
2661 .change_list
2662 .last()
2663 .map(|previous| {
2664 previous.len() == selections.len()
2665 && previous.iter().enumerate().all(|(ix, p)| {
2666 p.to_display_point(&display_map).row()
2667 == selections[ix].head().row()
2668 })
2669 })
2670 .unwrap_or(false);
2671 let new_positions = selections
2672 .into_iter()
2673 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2674 .collect();
2675 editor
2676 .change_list
2677 .push_to_change_list(pop_state, new_positions);
2678 }
2679 }
2680 _ => (),
2681 },
2682 ));
2683
2684 if let Some(dap_store) = editor
2685 .project
2686 .as_ref()
2687 .map(|project| project.read(cx).dap_store())
2688 {
2689 let weak_editor = cx.weak_entity();
2690
2691 editor
2692 ._subscriptions
2693 .push(
2694 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2695 let session_entity = cx.entity();
2696 weak_editor
2697 .update(cx, |editor, cx| {
2698 editor._subscriptions.push(
2699 cx.subscribe(&session_entity, Self::on_debug_session_event),
2700 );
2701 })
2702 .ok();
2703 }),
2704 );
2705
2706 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2707 editor
2708 ._subscriptions
2709 .push(cx.subscribe(&session, Self::on_debug_session_event));
2710 }
2711 }
2712
2713 // skip adding the initial selection to selection history
2714 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2715 editor.end_selection(window, cx);
2716 editor.selection_history.mode = SelectionHistoryMode::Normal;
2717
2718 editor.scroll_manager.show_scrollbars(window, cx);
2719 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2720
2721 if full_mode {
2722 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2723 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2724
2725 if editor.git_blame_inline_enabled {
2726 editor.start_git_blame_inline(false, window, cx);
2727 }
2728
2729 editor.go_to_active_debug_line(window, cx);
2730
2731 editor.minimap =
2732 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2733 editor.colors = Some(LspColorData::new(cx));
2734 editor.use_document_folding_ranges = true;
2735 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2736
2737 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2738 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2739 }
2740 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2741 }
2742
2743 editor
2744 }
2745
2746 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2747 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2748 }
2749
2750 pub fn deploy_mouse_context_menu(
2751 &mut self,
2752 position: gpui::Point<Pixels>,
2753 context_menu: Entity<ContextMenu>,
2754 window: &mut Window,
2755 cx: &mut Context<Self>,
2756 ) {
2757 self.mouse_context_menu = Some(MouseContextMenu::new(
2758 self,
2759 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2760 context_menu,
2761 window,
2762 cx,
2763 ));
2764 }
2765
2766 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2767 self.mouse_context_menu
2768 .as_ref()
2769 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2770 }
2771
2772 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2773 if self
2774 .selections
2775 .pending_anchor()
2776 .is_some_and(|pending_selection| {
2777 let snapshot = self.buffer().read(cx).snapshot(cx);
2778 pending_selection.range().includes(range, &snapshot)
2779 })
2780 {
2781 return true;
2782 }
2783
2784 self.selections
2785 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2786 .into_iter()
2787 .any(|selection| {
2788 // This is needed to cover a corner case, if we just check for an existing
2789 // selection in the fold range, having a cursor at the start of the fold
2790 // marks it as selected. Non-empty selections don't cause this.
2791 let length = selection.end - selection.start;
2792 length > 0
2793 })
2794 }
2795
2796 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2797 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2798 }
2799
2800 fn key_context_internal(
2801 &self,
2802 has_active_edit_prediction: bool,
2803 window: &mut Window,
2804 cx: &mut App,
2805 ) -> KeyContext {
2806 let mut key_context = KeyContext::new_with_defaults();
2807 key_context.add("Editor");
2808 let mode = match self.mode {
2809 EditorMode::SingleLine => "single_line",
2810 EditorMode::AutoHeight { .. } => "auto_height",
2811 EditorMode::Minimap { .. } => "minimap",
2812 EditorMode::Full { .. } => "full",
2813 };
2814
2815 if EditorSettings::jupyter_enabled(cx) {
2816 key_context.add("jupyter");
2817 }
2818
2819 key_context.set("mode", mode);
2820 if self.pending_rename.is_some() {
2821 key_context.add("renaming");
2822 }
2823
2824 if let Some(snippet_stack) = self.snippet_stack.last() {
2825 key_context.add("in_snippet");
2826
2827 if snippet_stack.active_index > 0 {
2828 key_context.add("has_previous_tabstop");
2829 }
2830
2831 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2832 key_context.add("has_next_tabstop");
2833 }
2834 }
2835
2836 match self.context_menu.borrow().as_ref() {
2837 Some(CodeContextMenu::Completions(menu)) => {
2838 if menu.visible() {
2839 key_context.add("menu");
2840 key_context.add("showing_completions");
2841 }
2842 }
2843 Some(CodeContextMenu::CodeActions(menu)) => {
2844 if menu.visible() {
2845 key_context.add("menu");
2846 key_context.add("showing_code_actions")
2847 }
2848 }
2849 None => {}
2850 }
2851
2852 if self.signature_help_state.has_multiple_signatures() {
2853 key_context.add("showing_signature_help");
2854 }
2855
2856 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2857 if !self.focus_handle(cx).contains_focused(window, cx)
2858 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2859 {
2860 for addon in self.addons.values() {
2861 addon.extend_key_context(&mut key_context, cx)
2862 }
2863 }
2864
2865 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2866 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2867 Some(
2868 file.full_path(cx)
2869 .extension()?
2870 .to_string_lossy()
2871 .to_lowercase(),
2872 )
2873 }) {
2874 key_context.set("extension", extension);
2875 }
2876 } else {
2877 key_context.add("multibuffer");
2878 }
2879
2880 if has_active_edit_prediction {
2881 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2882 key_context.add("copilot_suggestion");
2883 }
2884
2885 if self.in_leading_whitespace {
2886 key_context.add("in_leading_whitespace");
2887 }
2888 if self.edit_prediction_requires_modifier() {
2889 key_context.set("edit_prediction_mode", "subtle")
2890 } else {
2891 key_context.set("edit_prediction_mode", "eager");
2892 }
2893
2894 if self.selection_mark_mode {
2895 key_context.add("selection_mode");
2896 }
2897
2898 let disjoint = self.selections.disjoint_anchors();
2899 if matches!(
2900 &self.mode,
2901 EditorMode::SingleLine | EditorMode::AutoHeight { .. }
2902 ) && let [selection] = disjoint
2903 && selection.start == selection.end
2904 {
2905 let snapshot = self.snapshot(window, cx);
2906 let snapshot = snapshot.buffer_snapshot();
2907 let caret_offset = selection.end.to_offset(snapshot);
2908
2909 if caret_offset == MultiBufferOffset(0) {
2910 key_context.add("start_of_input");
2911 }
2912
2913 if caret_offset == snapshot.len() {
2914 key_context.add("end_of_input");
2915 }
2916 }
2917
2918 if self.has_any_expanded_diff_hunks(cx) {
2919 key_context.add("diffs_expanded");
2920 }
2921
2922 key_context
2923 }
2924
2925 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2926 self.last_bounds.as_ref()
2927 }
2928
2929 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2930 if self.mouse_cursor_hidden {
2931 self.mouse_cursor_hidden = false;
2932 cx.notify();
2933 }
2934 }
2935
2936 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2937 let hide_mouse_cursor = match origin {
2938 HideMouseCursorOrigin::TypingAction => {
2939 matches!(
2940 self.hide_mouse_mode,
2941 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2942 )
2943 }
2944 HideMouseCursorOrigin::MovementAction => {
2945 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2946 }
2947 };
2948 if self.mouse_cursor_hidden != hide_mouse_cursor {
2949 self.mouse_cursor_hidden = hide_mouse_cursor;
2950 cx.notify();
2951 }
2952 }
2953
2954 fn accept_edit_prediction_keystroke(
2955 &self,
2956 granularity: EditPredictionGranularity,
2957 window: &mut Window,
2958 cx: &mut App,
2959 ) -> Option<gpui::KeybindingKeystroke> {
2960 let key_context = self.key_context_internal(true, window, cx);
2961
2962 let bindings =
2963 match granularity {
2964 EditPredictionGranularity::Word => window
2965 .bindings_for_action_in_context(&AcceptNextWordEditPrediction, key_context),
2966 EditPredictionGranularity::Line => window
2967 .bindings_for_action_in_context(&AcceptNextLineEditPrediction, key_context),
2968 EditPredictionGranularity::Full => {
2969 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2970 }
2971 };
2972
2973 bindings
2974 .into_iter()
2975 .rev()
2976 .find_map(|binding| match binding.keystrokes() {
2977 [keystroke, ..] => Some(keystroke.clone()),
2978 _ => None,
2979 })
2980 }
2981
2982 fn preview_edit_prediction_keystroke(
2983 &self,
2984 window: &mut Window,
2985 cx: &mut App,
2986 ) -> Option<gpui::KeybindingKeystroke> {
2987 let key_context = self.key_context_internal(true, window, cx);
2988 let bindings = window.bindings_for_action_in_context(&AcceptEditPrediction, key_context);
2989 bindings
2990 .into_iter()
2991 .rev()
2992 .find_map(|binding| match binding.keystrokes() {
2993 [keystroke, ..] if keystroke.modifiers().modified() => Some(keystroke.clone()),
2994 _ => None,
2995 })
2996 }
2997
2998 fn edit_prediction_preview_modifiers_held(
2999 &self,
3000 modifiers: &Modifiers,
3001 window: &mut Window,
3002 cx: &mut App,
3003 ) -> bool {
3004 let key_context = self.key_context_internal(true, window, cx);
3005 let actions: [&dyn Action; 3] = [
3006 &AcceptEditPrediction,
3007 &AcceptNextWordEditPrediction,
3008 &AcceptNextLineEditPrediction,
3009 ];
3010
3011 actions.into_iter().any(|action| {
3012 window
3013 .bindings_for_action_in_context(action, key_context.clone())
3014 .into_iter()
3015 .rev()
3016 .any(|binding| {
3017 binding.keystrokes().first().is_some_and(|keystroke| {
3018 keystroke.modifiers().modified() && keystroke.modifiers() == modifiers
3019 })
3020 })
3021 })
3022 }
3023
3024 fn edit_prediction_cursor_popover_prefers_preview(
3025 &self,
3026 completion: &EditPredictionState,
3027 ) -> bool {
3028 match &completion.completion {
3029 EditPrediction::Edit {
3030 edits, snapshot, ..
3031 } => {
3032 let mut start_row: Option<u32> = None;
3033 let mut end_row: Option<u32> = None;
3034
3035 for (range, text) in edits {
3036 let edit_start_row = range.start.text_anchor.to_point(snapshot).row;
3037 let old_end_row = range.end.text_anchor.to_point(snapshot).row;
3038 let inserted_newline_count = text
3039 .as_ref()
3040 .chars()
3041 .filter(|character| *character == '\n')
3042 .count() as u32;
3043 let deleted_newline_count = old_end_row - edit_start_row;
3044 let preview_end_row = edit_start_row + inserted_newline_count;
3045
3046 start_row =
3047 Some(start_row.map_or(edit_start_row, |row| row.min(edit_start_row)));
3048 end_row = Some(end_row.map_or(preview_end_row, |row| row.max(preview_end_row)));
3049
3050 if deleted_newline_count > 1 {
3051 end_row = Some(end_row.map_or(old_end_row, |row| row.max(old_end_row)));
3052 }
3053 }
3054
3055 start_row
3056 .zip(end_row)
3057 .is_some_and(|(start_row, end_row)| end_row > start_row)
3058 }
3059 EditPrediction::MoveWithin { .. } | EditPrediction::MoveOutside { .. } => false,
3060 }
3061 }
3062
3063 fn edit_prediction_keybind_display(
3064 &self,
3065 surface: EditPredictionKeybindSurface,
3066 window: &mut Window,
3067 cx: &mut App,
3068 ) -> EditPredictionKeybindDisplay {
3069 let accept_keystroke =
3070 self.accept_edit_prediction_keystroke(EditPredictionGranularity::Full, window, cx);
3071 let preview_keystroke = self.preview_edit_prediction_keystroke(window, cx);
3072
3073 let action = match surface {
3074 EditPredictionKeybindSurface::Inline
3075 | EditPredictionKeybindSurface::CursorPopoverCompact => {
3076 if self.edit_prediction_requires_modifier() {
3077 EditPredictionKeybindAction::Preview
3078 } else {
3079 EditPredictionKeybindAction::Accept
3080 }
3081 }
3082 EditPredictionKeybindSurface::CursorPopoverExpanded => self
3083 .active_edit_prediction
3084 .as_ref()
3085 .filter(|completion| {
3086 self.edit_prediction_cursor_popover_prefers_preview(completion)
3087 })
3088 .map_or(EditPredictionKeybindAction::Accept, |_| {
3089 EditPredictionKeybindAction::Preview
3090 }),
3091 };
3092 #[cfg(test)]
3093 let preview_copy = preview_keystroke.clone();
3094 #[cfg(test)]
3095 let accept_copy = accept_keystroke.clone();
3096
3097 let displayed_keystroke = match surface {
3098 EditPredictionKeybindSurface::Inline => match action {
3099 EditPredictionKeybindAction::Accept => accept_keystroke,
3100 EditPredictionKeybindAction::Preview => preview_keystroke,
3101 },
3102 EditPredictionKeybindSurface::CursorPopoverCompact
3103 | EditPredictionKeybindSurface::CursorPopoverExpanded => match action {
3104 EditPredictionKeybindAction::Accept => accept_keystroke,
3105 EditPredictionKeybindAction::Preview => {
3106 preview_keystroke.or_else(|| accept_keystroke.clone())
3107 }
3108 },
3109 };
3110
3111 let missing_accept_keystroke = displayed_keystroke.is_none();
3112
3113 EditPredictionKeybindDisplay {
3114 #[cfg(test)]
3115 accept_keystroke: accept_copy,
3116 #[cfg(test)]
3117 preview_keystroke: preview_copy,
3118 displayed_keystroke,
3119 action,
3120 missing_accept_keystroke,
3121 show_hold_label: matches!(surface, EditPredictionKeybindSurface::CursorPopoverCompact)
3122 && self.edit_prediction_preview.released_too_fast(),
3123 }
3124 }
3125
3126 pub fn new_file(
3127 workspace: &mut Workspace,
3128 _: &workspace::NewFile,
3129 window: &mut Window,
3130 cx: &mut Context<Workspace>,
3131 ) {
3132 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
3133 "Failed to create buffer",
3134 window,
3135 cx,
3136 |e, _, _| match e.error_code() {
3137 ErrorCode::RemoteUpgradeRequired => Some(format!(
3138 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
3139 e.error_tag("required").unwrap_or("the latest version")
3140 )),
3141 _ => None,
3142 },
3143 );
3144 }
3145
3146 pub fn new_in_workspace(
3147 workspace: &mut Workspace,
3148 window: &mut Window,
3149 cx: &mut Context<Workspace>,
3150 ) -> Task<Result<Entity<Editor>>> {
3151 let project = workspace.project().clone();
3152 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
3153
3154 cx.spawn_in(window, async move |workspace, cx| {
3155 let buffer = create.await?;
3156 workspace.update_in(cx, |workspace, window, cx| {
3157 let editor =
3158 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
3159 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
3160 editor
3161 })
3162 })
3163 }
3164
3165 fn new_file_vertical(
3166 workspace: &mut Workspace,
3167 _: &workspace::NewFileSplitVertical,
3168 window: &mut Window,
3169 cx: &mut Context<Workspace>,
3170 ) {
3171 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
3172 }
3173
3174 fn new_file_horizontal(
3175 workspace: &mut Workspace,
3176 _: &workspace::NewFileSplitHorizontal,
3177 window: &mut Window,
3178 cx: &mut Context<Workspace>,
3179 ) {
3180 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
3181 }
3182
3183 fn new_file_split(
3184 workspace: &mut Workspace,
3185 action: &workspace::NewFileSplit,
3186 window: &mut Window,
3187 cx: &mut Context<Workspace>,
3188 ) {
3189 Self::new_file_in_direction(workspace, action.0, window, cx)
3190 }
3191
3192 fn new_file_in_direction(
3193 workspace: &mut Workspace,
3194 direction: SplitDirection,
3195 window: &mut Window,
3196 cx: &mut Context<Workspace>,
3197 ) {
3198 let project = workspace.project().clone();
3199 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
3200
3201 cx.spawn_in(window, async move |workspace, cx| {
3202 let buffer = create.await?;
3203 workspace.update_in(cx, move |workspace, window, cx| {
3204 workspace.split_item(
3205 direction,
3206 Box::new(
3207 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
3208 ),
3209 window,
3210 cx,
3211 )
3212 })?;
3213 anyhow::Ok(())
3214 })
3215 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
3216 match e.error_code() {
3217 ErrorCode::RemoteUpgradeRequired => Some(format!(
3218 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
3219 e.error_tag("required").unwrap_or("the latest version")
3220 )),
3221 _ => None,
3222 }
3223 });
3224 }
3225
3226 pub fn leader_id(&self) -> Option<CollaboratorId> {
3227 self.leader_id
3228 }
3229
3230 pub fn buffer(&self) -> &Entity<MultiBuffer> {
3231 &self.buffer
3232 }
3233
3234 pub fn project(&self) -> Option<&Entity<Project>> {
3235 self.project.as_ref()
3236 }
3237
3238 pub fn workspace(&self) -> Option<Entity<Workspace>> {
3239 self.workspace.as_ref()?.0.upgrade()
3240 }
3241
3242 /// Detaches a task and shows an error notification in the workspace if available,
3243 /// otherwise just logs the error.
3244 pub fn detach_and_notify_err<R, E>(
3245 &self,
3246 task: Task<Result<R, E>>,
3247 window: &mut Window,
3248 cx: &mut App,
3249 ) where
3250 E: std::fmt::Debug + std::fmt::Display + 'static,
3251 R: 'static,
3252 {
3253 if let Some(workspace) = self.workspace() {
3254 task.detach_and_notify_err(workspace.downgrade(), window, cx);
3255 } else {
3256 task.detach_and_log_err(cx);
3257 }
3258 }
3259
3260 /// Returns the workspace serialization ID if this editor should be serialized.
3261 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
3262 self.workspace
3263 .as_ref()
3264 .filter(|_| self.should_serialize_buffer())
3265 .and_then(|workspace| workspace.1)
3266 }
3267
3268 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
3269 self.buffer().read(cx).title(cx)
3270 }
3271
3272 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
3273 let git_blame_gutter_max_author_length = self
3274 .render_git_blame_gutter(cx)
3275 .then(|| {
3276 if let Some(blame) = self.blame.as_ref() {
3277 let max_author_length =
3278 blame.update(cx, |blame, cx| blame.max_author_length(cx));
3279 Some(max_author_length)
3280 } else {
3281 None
3282 }
3283 })
3284 .flatten();
3285
3286 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3287
3288 EditorSnapshot {
3289 mode: self.mode.clone(),
3290 show_gutter: self.show_gutter,
3291 offset_content: self.offset_content,
3292 show_line_numbers: self.show_line_numbers,
3293 number_deleted_lines: self.number_deleted_lines,
3294 show_git_diff_gutter: self.show_git_diff_gutter,
3295 semantic_tokens_enabled: self.semantic_token_state.enabled(),
3296 show_code_actions: self.show_code_actions,
3297 show_runnables: self.show_runnables,
3298 show_breakpoints: self.show_breakpoints,
3299 git_blame_gutter_max_author_length,
3300 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
3301 display_snapshot,
3302 placeholder_display_snapshot: self
3303 .placeholder_display_map
3304 .as_ref()
3305 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
3306 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
3307 is_focused: self.focus_handle.is_focused(window),
3308 current_line_highlight: self
3309 .current_line_highlight
3310 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
3311 gutter_hovered: self.gutter_hovered,
3312 }
3313 }
3314
3315 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
3316 self.buffer.read(cx).language_at(point, cx)
3317 }
3318
3319 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
3320 self.buffer.read(cx).read(cx).file_at(point).cloned()
3321 }
3322
3323 pub fn active_excerpt(
3324 &self,
3325 cx: &App,
3326 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
3327 self.buffer
3328 .read(cx)
3329 .excerpt_containing(self.selections.newest_anchor().head(), cx)
3330 }
3331
3332 pub fn mode(&self) -> &EditorMode {
3333 &self.mode
3334 }
3335
3336 pub fn set_mode(&mut self, mode: EditorMode) {
3337 self.mode = mode;
3338 }
3339
3340 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
3341 self.collaboration_hub.as_deref()
3342 }
3343
3344 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
3345 self.collaboration_hub = Some(hub);
3346 }
3347
3348 pub fn set_in_project_search(&mut self, in_project_search: bool) {
3349 self.in_project_search = in_project_search;
3350 }
3351
3352 pub fn set_custom_context_menu(
3353 &mut self,
3354 f: impl 'static
3355 + Fn(
3356 &mut Self,
3357 DisplayPoint,
3358 &mut Window,
3359 &mut Context<Self>,
3360 ) -> Option<Entity<ui::ContextMenu>>,
3361 ) {
3362 self.custom_context_menu = Some(Box::new(f))
3363 }
3364
3365 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
3366 self.completion_provider = provider;
3367 }
3368
3369 #[cfg(any(test, feature = "test-support"))]
3370 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
3371 self.completion_provider.clone()
3372 }
3373
3374 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
3375 self.semantics_provider.clone()
3376 }
3377
3378 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
3379 self.semantics_provider = provider;
3380 }
3381
3382 pub fn set_edit_prediction_provider<T>(
3383 &mut self,
3384 provider: Option<Entity<T>>,
3385 window: &mut Window,
3386 cx: &mut Context<Self>,
3387 ) where
3388 T: EditPredictionDelegate,
3389 {
3390 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionDelegate {
3391 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
3392 if this.focus_handle.is_focused(window) {
3393 this.update_visible_edit_prediction(window, cx);
3394 }
3395 }),
3396 provider: Arc::new(provider),
3397 });
3398 self.update_edit_prediction_settings(cx);
3399 self.refresh_edit_prediction(false, false, window, cx);
3400 }
3401
3402 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
3403 self.placeholder_display_map
3404 .as_ref()
3405 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
3406 }
3407
3408 pub fn set_placeholder_text(
3409 &mut self,
3410 placeholder_text: &str,
3411 window: &mut Window,
3412 cx: &mut Context<Self>,
3413 ) {
3414 let multibuffer = cx
3415 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3416
3417 let style = window.text_style();
3418
3419 self.placeholder_display_map = Some(cx.new(|cx| {
3420 DisplayMap::new(
3421 multibuffer,
3422 style.font(),
3423 style.font_size.to_pixels(window.rem_size()),
3424 None,
3425 FILE_HEADER_HEIGHT,
3426 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3427 Default::default(),
3428 DiagnosticSeverity::Off,
3429 cx,
3430 )
3431 }));
3432 cx.notify();
3433 }
3434
3435 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3436 self.cursor_shape = cursor_shape;
3437
3438 // Disrupt blink for immediate user feedback that the cursor shape has changed
3439 self.blink_manager.update(cx, BlinkManager::show_cursor);
3440
3441 cx.notify();
3442 }
3443
3444 pub fn cursor_shape(&self) -> CursorShape {
3445 self.cursor_shape
3446 }
3447
3448 pub fn set_cursor_offset_on_selection(&mut self, set_cursor_offset_on_selection: bool) {
3449 self.cursor_offset_on_selection = set_cursor_offset_on_selection;
3450 }
3451
3452 pub fn set_current_line_highlight(
3453 &mut self,
3454 current_line_highlight: Option<CurrentLineHighlight>,
3455 ) {
3456 self.current_line_highlight = current_line_highlight;
3457 }
3458
3459 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3460 self.collapse_matches = collapse_matches;
3461 }
3462
3463 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3464 if self.collapse_matches {
3465 return range.start..range.start;
3466 }
3467 range.clone()
3468 }
3469
3470 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3471 self.display_map.read(cx).clip_at_line_ends
3472 }
3473
3474 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3475 if self.display_map.read(cx).clip_at_line_ends != clip {
3476 self.display_map
3477 .update(cx, |map, _| map.clip_at_line_ends = clip);
3478 }
3479 }
3480
3481 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3482 self.input_enabled = input_enabled;
3483 }
3484
3485 pub fn set_expects_character_input(&mut self, expects_character_input: bool) {
3486 self.expects_character_input = expects_character_input;
3487 }
3488
3489 pub fn set_edit_predictions_hidden_for_vim_mode(
3490 &mut self,
3491 hidden: bool,
3492 window: &mut Window,
3493 cx: &mut Context<Self>,
3494 ) {
3495 if hidden != self.edit_predictions_hidden_for_vim_mode {
3496 self.edit_predictions_hidden_for_vim_mode = hidden;
3497 if hidden {
3498 self.update_visible_edit_prediction(window, cx);
3499 } else {
3500 self.refresh_edit_prediction(true, false, window, cx);
3501 }
3502 }
3503 }
3504
3505 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3506 self.menu_edit_predictions_policy = value;
3507 }
3508
3509 pub fn set_autoindent(&mut self, autoindent: bool) {
3510 if autoindent {
3511 self.autoindent_mode = Some(AutoindentMode::EachLine);
3512 } else {
3513 self.autoindent_mode = None;
3514 }
3515 }
3516
3517 pub fn capability(&self, cx: &App) -> Capability {
3518 if self.read_only {
3519 Capability::ReadOnly
3520 } else {
3521 self.buffer.read(cx).capability()
3522 }
3523 }
3524
3525 pub fn read_only(&self, cx: &App) -> bool {
3526 self.read_only || self.buffer.read(cx).read_only()
3527 }
3528
3529 pub fn set_read_only(&mut self, read_only: bool) {
3530 self.read_only = read_only;
3531 }
3532
3533 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3534 self.use_autoclose = autoclose;
3535 }
3536
3537 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3538 self.use_auto_surround = auto_surround;
3539 }
3540
3541 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3542 self.auto_replace_emoji_shortcode = auto_replace;
3543 }
3544
3545 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3546 self.buffer_serialization = should_serialize.then(|| {
3547 BufferSerialization::new(
3548 ProjectSettings::get_global(cx)
3549 .session
3550 .restore_unsaved_buffers,
3551 )
3552 })
3553 }
3554
3555 fn should_serialize_buffer(&self) -> bool {
3556 self.buffer_serialization.is_some()
3557 }
3558
3559 pub fn toggle_edit_predictions(
3560 &mut self,
3561 _: &ToggleEditPrediction,
3562 window: &mut Window,
3563 cx: &mut Context<Self>,
3564 ) {
3565 if self.show_edit_predictions_override.is_some() {
3566 self.set_show_edit_predictions(None, window, cx);
3567 } else {
3568 let show_edit_predictions = !self.edit_predictions_enabled();
3569 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3570 }
3571 }
3572
3573 pub fn set_show_completions_on_input(&mut self, show_completions_on_input: Option<bool>) {
3574 self.show_completions_on_input_override = show_completions_on_input;
3575 }
3576
3577 pub fn set_show_edit_predictions(
3578 &mut self,
3579 show_edit_predictions: Option<bool>,
3580 window: &mut Window,
3581 cx: &mut Context<Self>,
3582 ) {
3583 self.show_edit_predictions_override = show_edit_predictions;
3584 self.update_edit_prediction_settings(cx);
3585
3586 if let Some(false) = show_edit_predictions {
3587 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
3588 } else {
3589 self.refresh_edit_prediction(false, true, window, cx);
3590 }
3591 }
3592
3593 fn edit_predictions_disabled_in_scope(
3594 &self,
3595 buffer: &Entity<Buffer>,
3596 buffer_position: language::Anchor,
3597 cx: &App,
3598 ) -> bool {
3599 let snapshot = buffer.read(cx).snapshot();
3600 let settings = snapshot.settings_at(buffer_position, cx);
3601
3602 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3603 return false;
3604 };
3605
3606 scope.override_name().is_some_and(|scope_name| {
3607 settings
3608 .edit_predictions_disabled_in
3609 .iter()
3610 .any(|s| s == scope_name)
3611 })
3612 }
3613
3614 pub fn set_use_modal_editing(&mut self, to: bool) {
3615 self.use_modal_editing = to;
3616 }
3617
3618 pub fn use_modal_editing(&self) -> bool {
3619 self.use_modal_editing
3620 }
3621
3622 fn selections_did_change(
3623 &mut self,
3624 local: bool,
3625 old_cursor_position: &Anchor,
3626 effects: SelectionEffects,
3627 window: &mut Window,
3628 cx: &mut Context<Self>,
3629 ) {
3630 window.invalidate_character_coordinates();
3631
3632 // Copy selections to primary selection buffer
3633 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3634 if local {
3635 let selections = self
3636 .selections
3637 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3638 let buffer_handle = self.buffer.read(cx).read(cx);
3639
3640 let mut text = String::new();
3641 for (index, selection) in selections.iter().enumerate() {
3642 let text_for_selection = buffer_handle
3643 .text_for_range(selection.start..selection.end)
3644 .collect::<String>();
3645
3646 text.push_str(&text_for_selection);
3647 if index != selections.len() - 1 {
3648 text.push('\n');
3649 }
3650 }
3651
3652 if !text.is_empty() {
3653 cx.write_to_primary(ClipboardItem::new_string(text));
3654 }
3655 }
3656
3657 let selection_anchors = self.selections.disjoint_anchors_arc();
3658
3659 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3660 self.buffer.update(cx, |buffer, cx| {
3661 buffer.set_active_selections(
3662 &selection_anchors,
3663 self.selections.line_mode(),
3664 self.cursor_shape,
3665 cx,
3666 )
3667 });
3668 }
3669 let display_map = self
3670 .display_map
3671 .update(cx, |display_map, cx| display_map.snapshot(cx));
3672 let buffer = display_map.buffer_snapshot();
3673 if self.selections.count() == 1 {
3674 self.add_selections_state = None;
3675 }
3676 self.select_next_state = None;
3677 self.select_prev_state = None;
3678 self.select_syntax_node_history.try_clear();
3679 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3680 self.snippet_stack.invalidate(&selection_anchors, buffer);
3681 self.take_rename(false, window, cx);
3682
3683 let newest_selection = self.selections.newest_anchor();
3684 let new_cursor_position = newest_selection.head();
3685 let selection_start = newest_selection.start;
3686
3687 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3688 self.push_to_nav_history(
3689 *old_cursor_position,
3690 Some(new_cursor_position.to_point(buffer)),
3691 false,
3692 effects.nav_history == Some(true),
3693 cx,
3694 );
3695 }
3696
3697 if local {
3698 if let Some(buffer_id) = new_cursor_position.text_anchor.buffer_id {
3699 self.register_buffer(buffer_id, cx);
3700 }
3701
3702 let mut context_menu = self.context_menu.borrow_mut();
3703 let completion_menu = match context_menu.as_ref() {
3704 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3705 Some(CodeContextMenu::CodeActions(_)) => {
3706 *context_menu = None;
3707 None
3708 }
3709 None => None,
3710 };
3711 let completion_position = completion_menu.map(|menu| menu.initial_position);
3712 drop(context_menu);
3713
3714 if effects.completions
3715 && let Some(completion_position) = completion_position
3716 {
3717 let start_offset = selection_start.to_offset(buffer);
3718 let position_matches = start_offset == completion_position.to_offset(buffer);
3719 let continue_showing = if let Some((snap, ..)) =
3720 buffer.point_to_buffer_offset(completion_position)
3721 && !snap.capability.editable()
3722 {
3723 false
3724 } else if position_matches {
3725 if self.snippet_stack.is_empty() {
3726 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3727 == Some(CharKind::Word)
3728 } else {
3729 // Snippet choices can be shown even when the cursor is in whitespace.
3730 // Dismissing the menu with actions like backspace is handled by
3731 // invalidation regions.
3732 true
3733 }
3734 } else {
3735 false
3736 };
3737
3738 if continue_showing {
3739 self.open_or_update_completions_menu(None, None, false, window, cx);
3740 } else {
3741 self.hide_context_menu(window, cx);
3742 }
3743 }
3744
3745 hide_hover(self, cx);
3746
3747 if old_cursor_position.to_display_point(&display_map).row()
3748 != new_cursor_position.to_display_point(&display_map).row()
3749 {
3750 self.available_code_actions.take();
3751 }
3752 self.refresh_code_actions(window, cx);
3753 self.refresh_document_highlights(cx);
3754 refresh_linked_ranges(self, window, cx);
3755
3756 self.refresh_selected_text_highlights(&display_map, false, window, cx);
3757 self.refresh_matching_bracket_highlights(&display_map, cx);
3758 self.refresh_outline_symbols_at_cursor(cx);
3759 self.update_visible_edit_prediction(window, cx);
3760 self.inline_blame_popover.take();
3761 if self.git_blame_inline_enabled {
3762 self.start_inline_blame_timer(window, cx);
3763 }
3764 }
3765
3766 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3767
3768 if local && !self.suppress_selection_callback {
3769 if let Some(callback) = self.on_local_selections_changed.as_ref() {
3770 let cursor_position = self.selections.newest::<Point>(&display_map).head();
3771 callback(cursor_position, window, cx);
3772 }
3773 }
3774
3775 cx.emit(EditorEvent::SelectionsChanged { local });
3776
3777 let selections = &self.selections.disjoint_anchors_arc();
3778 if selections.len() == 1 {
3779 cx.emit(SearchEvent::ActiveMatchChanged)
3780 }
3781 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3782 let inmemory_selections = selections
3783 .iter()
3784 .map(|s| {
3785 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3786 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3787 })
3788 .collect();
3789 self.update_restoration_data(cx, |data| {
3790 data.selections = inmemory_selections;
3791 });
3792
3793 if WorkspaceSettings::get(None, cx).restore_on_startup
3794 != RestoreOnStartupBehavior::EmptyTab
3795 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3796 {
3797 let snapshot = self.buffer().read(cx).snapshot(cx);
3798 let selections = selections.clone();
3799 let background_executor = cx.background_executor().clone();
3800 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3801 let db = EditorDb::global(cx);
3802 self.serialize_selections = cx.background_spawn(async move {
3803 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3804 let db_selections = selections
3805 .iter()
3806 .map(|selection| {
3807 (
3808 selection.start.to_offset(&snapshot).0,
3809 selection.end.to_offset(&snapshot).0,
3810 )
3811 })
3812 .collect();
3813
3814 db.save_editor_selections(editor_id, workspace_id, db_selections)
3815 .await
3816 .with_context(|| {
3817 format!(
3818 "persisting editor selections for editor {editor_id}, \
3819 workspace {workspace_id:?}"
3820 )
3821 })
3822 .log_err();
3823 });
3824 }
3825 }
3826
3827 cx.notify();
3828 }
3829
3830 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3831 use text::ToOffset as _;
3832 use text::ToPoint as _;
3833
3834 if self.mode.is_minimap()
3835 || WorkspaceSettings::get(None, cx).restore_on_startup
3836 == RestoreOnStartupBehavior::EmptyTab
3837 {
3838 return;
3839 }
3840
3841 if !self.buffer().read(cx).is_singleton() {
3842 return;
3843 }
3844
3845 let display_snapshot = self
3846 .display_map
3847 .update(cx, |display_map, cx| display_map.snapshot(cx));
3848 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3849 return;
3850 };
3851 let inmemory_folds = display_snapshot
3852 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3853 .map(|fold| {
3854 fold.range.start.text_anchor.to_point(&snapshot)
3855 ..fold.range.end.text_anchor.to_point(&snapshot)
3856 })
3857 .collect();
3858 self.update_restoration_data(cx, |data| {
3859 data.folds = inmemory_folds;
3860 });
3861
3862 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3863 return;
3864 };
3865
3866 // Get file path for path-based fold storage (survives tab close)
3867 let Some(file_path) = self.buffer().read(cx).as_singleton().and_then(|buffer| {
3868 project::File::from_dyn(buffer.read(cx).file())
3869 .map(|file| Arc::<Path>::from(file.abs_path(cx)))
3870 }) else {
3871 return;
3872 };
3873
3874 let background_executor = cx.background_executor().clone();
3875 const FINGERPRINT_LEN: usize = 32;
3876 let db_folds = display_snapshot
3877 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3878 .map(|fold| {
3879 let start = fold.range.start.text_anchor.to_offset(&snapshot);
3880 let end = fold.range.end.text_anchor.to_offset(&snapshot);
3881
3882 // Extract fingerprints - content at fold boundaries for validation on restore
3883 // Both fingerprints must be INSIDE the fold to avoid capturing surrounding
3884 // content that might change independently.
3885 // start_fp: first min(32, fold_len) bytes of fold content
3886 // end_fp: last min(32, fold_len) bytes of fold content
3887 // Clip to character boundaries to handle multibyte UTF-8 characters.
3888 let fold_len = end - start;
3889 let start_fp_end = snapshot
3890 .clip_offset(start + std::cmp::min(FINGERPRINT_LEN, fold_len), Bias::Left);
3891 let start_fp: String = snapshot.text_for_range(start..start_fp_end).collect();
3892 let end_fp_start = snapshot
3893 .clip_offset(end.saturating_sub(FINGERPRINT_LEN).max(start), Bias::Right);
3894 let end_fp: String = snapshot.text_for_range(end_fp_start..end).collect();
3895
3896 (start, end, start_fp, end_fp)
3897 })
3898 .collect::<Vec<_>>();
3899 let db = EditorDb::global(cx);
3900 self.serialize_folds = cx.background_spawn(async move {
3901 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3902 if db_folds.is_empty() {
3903 // No folds - delete any persisted folds for this file
3904 db.delete_file_folds(workspace_id, file_path)
3905 .await
3906 .with_context(|| format!("deleting file folds for workspace {workspace_id:?}"))
3907 .log_err();
3908 } else {
3909 db.save_file_folds(workspace_id, file_path, db_folds)
3910 .await
3911 .with_context(|| {
3912 format!("persisting file folds for workspace {workspace_id:?}")
3913 })
3914 .log_err();
3915 }
3916 });
3917 }
3918
3919 pub fn sync_selections(
3920 &mut self,
3921 other: Entity<Editor>,
3922 cx: &mut Context<Self>,
3923 ) -> gpui::Subscription {
3924 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3925 if !other_selections.is_empty() {
3926 self.selections
3927 .change_with(&self.display_snapshot(cx), |selections| {
3928 selections.select_anchors(other_selections);
3929 });
3930 }
3931
3932 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3933 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3934 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3935 if other_selections.is_empty() {
3936 return;
3937 }
3938 let snapshot = this.display_snapshot(cx);
3939 this.selections.change_with(&snapshot, |selections| {
3940 selections.select_anchors(other_selections);
3941 });
3942 }
3943 });
3944
3945 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3946 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3947 let these_selections = this.selections.disjoint_anchors().to_vec();
3948 if these_selections.is_empty() {
3949 return;
3950 }
3951 other.update(cx, |other_editor, cx| {
3952 let snapshot = other_editor.display_snapshot(cx);
3953 other_editor
3954 .selections
3955 .change_with(&snapshot, |selections| {
3956 selections.select_anchors(these_selections);
3957 })
3958 });
3959 }
3960 });
3961
3962 Subscription::join(other_subscription, this_subscription)
3963 }
3964
3965 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3966 if self.buffer().read(cx).is_singleton() {
3967 return;
3968 }
3969 let snapshot = self.buffer.read(cx).snapshot(cx);
3970 let buffer_ids: HashSet<BufferId> = self
3971 .selections
3972 .disjoint_anchor_ranges()
3973 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3974 .collect();
3975 for buffer_id in buffer_ids {
3976 self.unfold_buffer(buffer_id, cx);
3977 }
3978 }
3979
3980 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3981 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3982 /// effects of selection change occur at the end of the transaction.
3983 pub fn change_selections<R>(
3984 &mut self,
3985 effects: SelectionEffects,
3986 window: &mut Window,
3987 cx: &mut Context<Self>,
3988 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3989 ) -> R {
3990 let snapshot = self.display_snapshot(cx);
3991 if let Some(state) = &mut self.deferred_selection_effects_state {
3992 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3993 state.effects.completions = effects.completions;
3994 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3995 let (changed, result) = self.selections.change_with(&snapshot, change);
3996 state.changed |= changed;
3997 return result;
3998 }
3999 let mut state = DeferredSelectionEffectsState {
4000 changed: false,
4001 effects,
4002 old_cursor_position: self.selections.newest_anchor().head(),
4003 history_entry: SelectionHistoryEntry {
4004 selections: self.selections.disjoint_anchors_arc(),
4005 select_next_state: self.select_next_state.clone(),
4006 select_prev_state: self.select_prev_state.clone(),
4007 add_selections_state: self.add_selections_state.clone(),
4008 },
4009 };
4010 let (changed, result) = self.selections.change_with(&snapshot, change);
4011 state.changed = state.changed || changed;
4012 if self.defer_selection_effects {
4013 self.deferred_selection_effects_state = Some(state);
4014 } else {
4015 self.apply_selection_effects(state, window, cx);
4016 }
4017 result
4018 }
4019
4020 /// Defers the effects of selection change, so that the effects of multiple calls to
4021 /// `change_selections` are applied at the end. This way these intermediate states aren't added
4022 /// to selection history and the state of popovers based on selection position aren't
4023 /// erroneously updated.
4024 pub fn with_selection_effects_deferred<R>(
4025 &mut self,
4026 window: &mut Window,
4027 cx: &mut Context<Self>,
4028 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
4029 ) -> R {
4030 let already_deferred = self.defer_selection_effects;
4031 self.defer_selection_effects = true;
4032 let result = update(self, window, cx);
4033 if !already_deferred {
4034 self.defer_selection_effects = false;
4035 if let Some(state) = self.deferred_selection_effects_state.take() {
4036 self.apply_selection_effects(state, window, cx);
4037 }
4038 }
4039 result
4040 }
4041
4042 fn apply_selection_effects(
4043 &mut self,
4044 state: DeferredSelectionEffectsState,
4045 window: &mut Window,
4046 cx: &mut Context<Self>,
4047 ) {
4048 if state.changed {
4049 self.selection_history.push(state.history_entry);
4050
4051 if let Some(autoscroll) = state.effects.scroll {
4052 self.request_autoscroll(autoscroll, cx);
4053 }
4054
4055 let old_cursor_position = &state.old_cursor_position;
4056
4057 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
4058
4059 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
4060 self.show_signature_help_auto(window, cx);
4061 }
4062 }
4063 }
4064
4065 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
4066 where
4067 I: IntoIterator<Item = (Range<S>, T)>,
4068 S: ToOffset,
4069 T: Into<Arc<str>>,
4070 {
4071 if self.read_only(cx) {
4072 return;
4073 }
4074
4075 self.buffer
4076 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
4077 }
4078
4079 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
4080 where
4081 I: IntoIterator<Item = (Range<S>, T)>,
4082 S: ToOffset,
4083 T: Into<Arc<str>>,
4084 {
4085 if self.read_only(cx) {
4086 return;
4087 }
4088
4089 self.buffer.update(cx, |buffer, cx| {
4090 buffer.edit(edits, self.autoindent_mode.clone(), cx)
4091 });
4092 }
4093
4094 pub fn edit_with_block_indent<I, S, T>(
4095 &mut self,
4096 edits: I,
4097 original_indent_columns: Vec<Option<u32>>,
4098 cx: &mut Context<Self>,
4099 ) where
4100 I: IntoIterator<Item = (Range<S>, T)>,
4101 S: ToOffset,
4102 T: Into<Arc<str>>,
4103 {
4104 if self.read_only(cx) {
4105 return;
4106 }
4107
4108 self.buffer.update(cx, |buffer, cx| {
4109 buffer.edit(
4110 edits,
4111 Some(AutoindentMode::Block {
4112 original_indent_columns,
4113 }),
4114 cx,
4115 )
4116 });
4117 }
4118
4119 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
4120 self.hide_context_menu(window, cx);
4121
4122 match phase {
4123 SelectPhase::Begin {
4124 position,
4125 add,
4126 click_count,
4127 } => self.begin_selection(position, add, click_count, window, cx),
4128 SelectPhase::BeginColumnar {
4129 position,
4130 goal_column,
4131 reset,
4132 mode,
4133 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
4134 SelectPhase::Extend {
4135 position,
4136 click_count,
4137 } => self.extend_selection(position, click_count, window, cx),
4138 SelectPhase::Update {
4139 position,
4140 goal_column,
4141 scroll_delta,
4142 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
4143 SelectPhase::End => self.end_selection(window, cx),
4144 }
4145 }
4146
4147 fn extend_selection(
4148 &mut self,
4149 position: DisplayPoint,
4150 click_count: usize,
4151 window: &mut Window,
4152 cx: &mut Context<Self>,
4153 ) {
4154 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4155 let tail = self
4156 .selections
4157 .newest::<MultiBufferOffset>(&display_map)
4158 .tail();
4159 let click_count = click_count.max(match self.selections.select_mode() {
4160 SelectMode::Character => 1,
4161 SelectMode::Word(_) => 2,
4162 SelectMode::Line(_) => 3,
4163 SelectMode::All => 4,
4164 });
4165 self.begin_selection(position, false, click_count, window, cx);
4166
4167 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
4168
4169 let current_selection = match self.selections.select_mode() {
4170 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
4171 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
4172 };
4173
4174 let mut pending_selection = self
4175 .selections
4176 .pending_anchor()
4177 .cloned()
4178 .expect("extend_selection not called with pending selection");
4179
4180 if pending_selection
4181 .start
4182 .cmp(¤t_selection.start, display_map.buffer_snapshot())
4183 == Ordering::Greater
4184 {
4185 pending_selection.start = current_selection.start;
4186 }
4187 if pending_selection
4188 .end
4189 .cmp(¤t_selection.end, display_map.buffer_snapshot())
4190 == Ordering::Less
4191 {
4192 pending_selection.end = current_selection.end;
4193 pending_selection.reversed = true;
4194 }
4195
4196 let mut pending_mode = self.selections.pending_mode().unwrap();
4197 match &mut pending_mode {
4198 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
4199 _ => {}
4200 }
4201
4202 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
4203 SelectionEffects::scroll(Autoscroll::fit())
4204 } else {
4205 SelectionEffects::no_scroll()
4206 };
4207
4208 self.change_selections(effects, window, cx, |s| {
4209 s.set_pending(pending_selection.clone(), pending_mode);
4210 s.set_is_extending(true);
4211 });
4212 }
4213
4214 fn begin_selection(
4215 &mut self,
4216 position: DisplayPoint,
4217 add: bool,
4218 click_count: usize,
4219 window: &mut Window,
4220 cx: &mut Context<Self>,
4221 ) {
4222 if !self.focus_handle.is_focused(window) {
4223 self.last_focused_descendant = None;
4224 window.focus(&self.focus_handle, cx);
4225 }
4226
4227 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4228 let buffer = display_map.buffer_snapshot();
4229 let position = display_map.clip_point(position, Bias::Left);
4230
4231 let start;
4232 let end;
4233 let mode;
4234 let mut auto_scroll;
4235 match click_count {
4236 1 => {
4237 start = buffer.anchor_before(position.to_point(&display_map));
4238 end = start;
4239 mode = SelectMode::Character;
4240 auto_scroll = true;
4241 }
4242 2 => {
4243 let position = display_map
4244 .clip_point(position, Bias::Left)
4245 .to_offset(&display_map, Bias::Left);
4246 let (range, _) = buffer.surrounding_word(position, None);
4247 start = buffer.anchor_before(range.start);
4248 end = buffer.anchor_before(range.end);
4249 mode = SelectMode::Word(start..end);
4250 auto_scroll = true;
4251 }
4252 3 => {
4253 let position = display_map
4254 .clip_point(position, Bias::Left)
4255 .to_point(&display_map);
4256 let line_start = display_map.prev_line_boundary(position).0;
4257 let next_line_start = buffer.clip_point(
4258 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4259 Bias::Left,
4260 );
4261 start = buffer.anchor_before(line_start);
4262 end = buffer.anchor_before(next_line_start);
4263 mode = SelectMode::Line(start..end);
4264 auto_scroll = true;
4265 }
4266 _ => {
4267 start = buffer.anchor_before(MultiBufferOffset(0));
4268 end = buffer.anchor_before(buffer.len());
4269 mode = SelectMode::All;
4270 auto_scroll = false;
4271 }
4272 }
4273 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
4274
4275 let point_to_delete: Option<usize> = {
4276 let selected_points: Vec<Selection<Point>> =
4277 self.selections.disjoint_in_range(start..end, &display_map);
4278
4279 if !add || click_count > 1 {
4280 None
4281 } else if !selected_points.is_empty() {
4282 Some(selected_points[0].id)
4283 } else {
4284 let clicked_point_already_selected =
4285 self.selections.disjoint_anchors().iter().find(|selection| {
4286 selection.start.to_point(buffer) == start.to_point(buffer)
4287 || selection.end.to_point(buffer) == end.to_point(buffer)
4288 });
4289
4290 clicked_point_already_selected.map(|selection| selection.id)
4291 }
4292 };
4293
4294 let selections_count = self.selections.count();
4295 let effects = if auto_scroll {
4296 SelectionEffects::default()
4297 } else {
4298 SelectionEffects::no_scroll()
4299 };
4300
4301 self.change_selections(effects, window, cx, |s| {
4302 if let Some(point_to_delete) = point_to_delete {
4303 s.delete(point_to_delete);
4304
4305 if selections_count == 1 {
4306 s.set_pending_anchor_range(start..end, mode);
4307 }
4308 } else {
4309 if !add {
4310 s.clear_disjoint();
4311 }
4312
4313 s.set_pending_anchor_range(start..end, mode);
4314 }
4315 });
4316 }
4317
4318 fn begin_columnar_selection(
4319 &mut self,
4320 position: DisplayPoint,
4321 goal_column: u32,
4322 reset: bool,
4323 mode: ColumnarMode,
4324 window: &mut Window,
4325 cx: &mut Context<Self>,
4326 ) {
4327 if !self.focus_handle.is_focused(window) {
4328 self.last_focused_descendant = None;
4329 window.focus(&self.focus_handle, cx);
4330 }
4331
4332 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4333
4334 if reset {
4335 let pointer_position = display_map
4336 .buffer_snapshot()
4337 .anchor_before(position.to_point(&display_map));
4338
4339 self.change_selections(
4340 SelectionEffects::scroll(Autoscroll::newest()),
4341 window,
4342 cx,
4343 |s| {
4344 s.clear_disjoint();
4345 s.set_pending_anchor_range(
4346 pointer_position..pointer_position,
4347 SelectMode::Character,
4348 );
4349 },
4350 );
4351 };
4352
4353 let tail = self.selections.newest::<Point>(&display_map).tail();
4354 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
4355 self.columnar_selection_state = match mode {
4356 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
4357 selection_tail: selection_anchor,
4358 display_point: if reset {
4359 if position.column() != goal_column {
4360 Some(DisplayPoint::new(position.row(), goal_column))
4361 } else {
4362 None
4363 }
4364 } else {
4365 None
4366 },
4367 }),
4368 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
4369 selection_tail: selection_anchor,
4370 }),
4371 };
4372
4373 if !reset {
4374 self.select_columns(position, goal_column, &display_map, window, cx);
4375 }
4376 }
4377
4378 fn update_selection(
4379 &mut self,
4380 position: DisplayPoint,
4381 goal_column: u32,
4382 scroll_delta: gpui::Point<f32>,
4383 window: &mut Window,
4384 cx: &mut Context<Self>,
4385 ) {
4386 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4387
4388 if self.columnar_selection_state.is_some() {
4389 self.select_columns(position, goal_column, &display_map, window, cx);
4390 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
4391 let buffer = display_map.buffer_snapshot();
4392 let head;
4393 let tail;
4394 let mode = self.selections.pending_mode().unwrap();
4395 match &mode {
4396 SelectMode::Character => {
4397 head = position.to_point(&display_map);
4398 tail = pending.tail().to_point(buffer);
4399 }
4400 SelectMode::Word(original_range) => {
4401 let offset = display_map
4402 .clip_point(position, Bias::Left)
4403 .to_offset(&display_map, Bias::Left);
4404 let original_range = original_range.to_offset(buffer);
4405
4406 let head_offset = if buffer.is_inside_word(offset, None)
4407 || original_range.contains(&offset)
4408 {
4409 let (word_range, _) = buffer.surrounding_word(offset, None);
4410 if word_range.start < original_range.start {
4411 word_range.start
4412 } else {
4413 word_range.end
4414 }
4415 } else {
4416 offset
4417 };
4418
4419 head = head_offset.to_point(buffer);
4420 if head_offset <= original_range.start {
4421 tail = original_range.end.to_point(buffer);
4422 } else {
4423 tail = original_range.start.to_point(buffer);
4424 }
4425 }
4426 SelectMode::Line(original_range) => {
4427 let original_range = original_range.to_point(display_map.buffer_snapshot());
4428
4429 let position = display_map
4430 .clip_point(position, Bias::Left)
4431 .to_point(&display_map);
4432 let line_start = display_map.prev_line_boundary(position).0;
4433 let next_line_start = buffer.clip_point(
4434 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4435 Bias::Left,
4436 );
4437
4438 if line_start < original_range.start {
4439 head = line_start
4440 } else {
4441 head = next_line_start
4442 }
4443
4444 if head <= original_range.start {
4445 tail = original_range.end;
4446 } else {
4447 tail = original_range.start;
4448 }
4449 }
4450 SelectMode::All => {
4451 return;
4452 }
4453 };
4454
4455 if head < tail {
4456 pending.start = buffer.anchor_before(head);
4457 pending.end = buffer.anchor_before(tail);
4458 pending.reversed = true;
4459 } else {
4460 pending.start = buffer.anchor_before(tail);
4461 pending.end = buffer.anchor_before(head);
4462 pending.reversed = false;
4463 }
4464
4465 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4466 s.set_pending(pending.clone(), mode);
4467 });
4468 } else {
4469 log::error!("update_selection dispatched with no pending selection");
4470 return;
4471 }
4472
4473 self.apply_scroll_delta(scroll_delta, window, cx);
4474 cx.notify();
4475 }
4476
4477 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4478 self.columnar_selection_state.take();
4479 if let Some(pending_mode) = self.selections.pending_mode() {
4480 let selections = self
4481 .selections
4482 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4483 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4484 s.select(selections);
4485 s.clear_pending();
4486 if s.is_extending() {
4487 s.set_is_extending(false);
4488 } else {
4489 s.set_select_mode(pending_mode);
4490 }
4491 });
4492 }
4493 }
4494
4495 fn select_columns(
4496 &mut self,
4497 head: DisplayPoint,
4498 goal_column: u32,
4499 display_map: &DisplaySnapshot,
4500 window: &mut Window,
4501 cx: &mut Context<Self>,
4502 ) {
4503 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4504 return;
4505 };
4506
4507 let tail = match columnar_state {
4508 ColumnarSelectionState::FromMouse {
4509 selection_tail,
4510 display_point,
4511 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4512 ColumnarSelectionState::FromSelection { selection_tail } => {
4513 selection_tail.to_display_point(display_map)
4514 }
4515 };
4516
4517 let start_row = cmp::min(tail.row(), head.row());
4518 let end_row = cmp::max(tail.row(), head.row());
4519 let start_column = cmp::min(tail.column(), goal_column);
4520 let end_column = cmp::max(tail.column(), goal_column);
4521 let reversed = start_column < tail.column();
4522
4523 let selection_ranges = (start_row.0..=end_row.0)
4524 .map(DisplayRow)
4525 .filter_map(|row| {
4526 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4527 || start_column <= display_map.line_len(row))
4528 && !display_map.is_block_line(row)
4529 {
4530 let start = display_map
4531 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4532 .to_point(display_map);
4533 let end = display_map
4534 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4535 .to_point(display_map);
4536 if reversed {
4537 Some(end..start)
4538 } else {
4539 Some(start..end)
4540 }
4541 } else {
4542 None
4543 }
4544 })
4545 .collect::<Vec<_>>();
4546 if selection_ranges.is_empty() {
4547 return;
4548 }
4549
4550 let ranges = match columnar_state {
4551 ColumnarSelectionState::FromMouse { .. } => {
4552 let mut non_empty_ranges = selection_ranges
4553 .iter()
4554 .filter(|selection_range| selection_range.start != selection_range.end)
4555 .peekable();
4556 if non_empty_ranges.peek().is_some() {
4557 non_empty_ranges.cloned().collect()
4558 } else {
4559 selection_ranges
4560 }
4561 }
4562 _ => selection_ranges,
4563 };
4564
4565 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4566 s.select_ranges(ranges);
4567 });
4568 cx.notify();
4569 }
4570
4571 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4572 self.selections
4573 .all_adjusted(snapshot)
4574 .iter()
4575 .any(|selection| !selection.is_empty())
4576 }
4577
4578 pub fn has_pending_nonempty_selection(&self) -> bool {
4579 let pending_nonempty_selection = match self.selections.pending_anchor() {
4580 Some(Selection { start, end, .. }) => start != end,
4581 None => false,
4582 };
4583
4584 pending_nonempty_selection
4585 || (self.columnar_selection_state.is_some()
4586 && self.selections.disjoint_anchors().len() > 1)
4587 }
4588
4589 pub fn has_pending_selection(&self) -> bool {
4590 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4591 }
4592
4593 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4594 self.selection_mark_mode = false;
4595 self.selection_drag_state = SelectionDragState::None;
4596
4597 if self.dismiss_menus_and_popups(true, window, cx) {
4598 cx.notify();
4599 return;
4600 }
4601 if self.clear_expanded_diff_hunks(cx) {
4602 cx.notify();
4603 return;
4604 }
4605 if self.show_git_blame_gutter {
4606 self.show_git_blame_gutter = false;
4607 cx.notify();
4608 return;
4609 }
4610
4611 if self.mode.is_full()
4612 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4613 {
4614 cx.notify();
4615 return;
4616 }
4617
4618 cx.propagate();
4619 }
4620
4621 pub fn dismiss_menus_and_popups(
4622 &mut self,
4623 is_user_requested: bool,
4624 window: &mut Window,
4625 cx: &mut Context<Self>,
4626 ) -> bool {
4627 let mut dismissed = false;
4628
4629 dismissed |= self.take_rename(false, window, cx).is_some();
4630 dismissed |= self.hide_blame_popover(true, cx);
4631 dismissed |= hide_hover(self, cx);
4632 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4633 dismissed |= self.hide_context_menu(window, cx).is_some();
4634 dismissed |= self.mouse_context_menu.take().is_some();
4635 dismissed |= is_user_requested
4636 && self.discard_edit_prediction(EditPredictionDiscardReason::Rejected, cx);
4637 dismissed |= self.snippet_stack.pop().is_some();
4638 if self.diff_review_drag_state.is_some() {
4639 self.cancel_diff_review_drag(cx);
4640 dismissed = true;
4641 }
4642 if !self.diff_review_overlays.is_empty() {
4643 self.dismiss_all_diff_review_overlays(cx);
4644 dismissed = true;
4645 }
4646
4647 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4648 self.dismiss_diagnostics(cx);
4649 dismissed = true;
4650 }
4651
4652 dismissed
4653 }
4654
4655 fn linked_editing_ranges_for(
4656 &self,
4657 selection: Range<text::Anchor>,
4658 cx: &App,
4659 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4660 if self.linked_edit_ranges.is_empty() {
4661 return None;
4662 }
4663 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4664 selection.end.buffer_id.and_then(|end_buffer_id| {
4665 if selection.start.buffer_id != Some(end_buffer_id) {
4666 return None;
4667 }
4668 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4669 let snapshot = buffer.read(cx).snapshot();
4670 self.linked_edit_ranges
4671 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4672 .map(|ranges| (ranges, snapshot, buffer))
4673 })?;
4674 use text::ToOffset as TO;
4675 // find offset from the start of current range to current cursor position
4676 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4677
4678 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4679 let start_difference = start_offset - start_byte_offset;
4680 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4681 let end_difference = end_offset - start_byte_offset;
4682
4683 // Current range has associated linked ranges.
4684 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4685 for range in linked_ranges.iter() {
4686 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4687 let end_offset = start_offset + end_difference;
4688 let start_offset = start_offset + start_difference;
4689 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4690 continue;
4691 }
4692 if self.selections.disjoint_anchor_ranges().any(|s| {
4693 if s.start.text_anchor.buffer_id != selection.start.buffer_id
4694 || s.end.text_anchor.buffer_id != selection.end.buffer_id
4695 {
4696 return false;
4697 }
4698 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4699 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4700 }) {
4701 continue;
4702 }
4703 let start = buffer_snapshot.anchor_after(start_offset);
4704 let end = buffer_snapshot.anchor_after(end_offset);
4705 linked_edits
4706 .entry(buffer.clone())
4707 .or_default()
4708 .push(start..end);
4709 }
4710 Some(linked_edits)
4711 }
4712
4713 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4714 let text: Arc<str> = text.into();
4715
4716 if self.read_only(cx) {
4717 return;
4718 }
4719
4720 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4721
4722 self.unfold_buffers_with_selections(cx);
4723
4724 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4725 let mut bracket_inserted = false;
4726 let mut edits = Vec::new();
4727 let mut linked_edits = LinkedEdits::new();
4728 let mut new_selections = Vec::with_capacity(selections.len());
4729 let mut new_autoclose_regions = Vec::new();
4730 let snapshot = self.buffer.read(cx).read(cx);
4731 let mut clear_linked_edit_ranges = false;
4732 let mut all_selections_read_only = true;
4733 let mut has_adjacent_edits = false;
4734 let mut in_adjacent_group = false;
4735
4736 let mut regions = self
4737 .selections_with_autoclose_regions(selections, &snapshot)
4738 .peekable();
4739
4740 while let Some((selection, autoclose_region)) = regions.next() {
4741 if snapshot
4742 .point_to_buffer_point(selection.head())
4743 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4744 {
4745 continue;
4746 }
4747 if snapshot
4748 .point_to_buffer_point(selection.tail())
4749 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4750 {
4751 // note, ideally we'd clip the tail to the closest writeable region towards the head
4752 continue;
4753 }
4754 all_selections_read_only = false;
4755
4756 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4757 // Determine if the inserted text matches the opening or closing
4758 // bracket of any of this language's bracket pairs.
4759 let mut bracket_pair = None;
4760 let mut is_bracket_pair_start = false;
4761 let mut is_bracket_pair_end = false;
4762 if !text.is_empty() {
4763 let mut bracket_pair_matching_end = None;
4764 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4765 // and they are removing the character that triggered IME popup.
4766 for (pair, enabled) in scope.brackets() {
4767 if !pair.close && !pair.surround {
4768 continue;
4769 }
4770
4771 if enabled && pair.start.ends_with(text.as_ref()) {
4772 let prefix_len = pair.start.len() - text.len();
4773 let preceding_text_matches_prefix = prefix_len == 0
4774 || (selection.start.column >= (prefix_len as u32)
4775 && snapshot.contains_str_at(
4776 Point::new(
4777 selection.start.row,
4778 selection.start.column - (prefix_len as u32),
4779 ),
4780 &pair.start[..prefix_len],
4781 ));
4782 if preceding_text_matches_prefix {
4783 bracket_pair = Some(pair.clone());
4784 is_bracket_pair_start = true;
4785 break;
4786 }
4787 }
4788 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4789 {
4790 // take first bracket pair matching end, but don't break in case a later bracket
4791 // pair matches start
4792 bracket_pair_matching_end = Some(pair.clone());
4793 }
4794 }
4795 if let Some(end) = bracket_pair_matching_end
4796 && bracket_pair.is_none()
4797 {
4798 bracket_pair = Some(end);
4799 is_bracket_pair_end = true;
4800 }
4801 }
4802
4803 if let Some(bracket_pair) = bracket_pair {
4804 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4805 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4806 let auto_surround =
4807 self.use_auto_surround && snapshot_settings.use_auto_surround;
4808 if selection.is_empty() {
4809 if is_bracket_pair_start {
4810 // If the inserted text is a suffix of an opening bracket and the
4811 // selection is preceded by the rest of the opening bracket, then
4812 // insert the closing bracket.
4813 let following_text_allows_autoclose = snapshot
4814 .chars_at(selection.start)
4815 .next()
4816 .is_none_or(|c| scope.should_autoclose_before(c));
4817
4818 let preceding_text_allows_autoclose = selection.start.column == 0
4819 || snapshot
4820 .reversed_chars_at(selection.start)
4821 .next()
4822 .is_none_or(|c| {
4823 bracket_pair.start != bracket_pair.end
4824 || !snapshot
4825 .char_classifier_at(selection.start)
4826 .is_word(c)
4827 });
4828
4829 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4830 && bracket_pair.start.len() == 1
4831 {
4832 let target = bracket_pair.start.chars().next().unwrap();
4833 let mut byte_offset = 0u32;
4834 let current_line_count = snapshot
4835 .reversed_chars_at(selection.start)
4836 .take_while(|&c| c != '\n')
4837 .filter(|c| {
4838 byte_offset += c.len_utf8() as u32;
4839 if *c != target {
4840 return false;
4841 }
4842
4843 let point = Point::new(
4844 selection.start.row,
4845 selection.start.column.saturating_sub(byte_offset),
4846 );
4847
4848 let is_enabled = snapshot
4849 .language_scope_at(point)
4850 .and_then(|scope| {
4851 scope
4852 .brackets()
4853 .find(|(pair, _)| {
4854 pair.start == bracket_pair.start
4855 })
4856 .map(|(_, enabled)| enabled)
4857 })
4858 .unwrap_or(true);
4859
4860 let is_delimiter = snapshot
4861 .language_scope_at(Point::new(
4862 point.row,
4863 point.column + 1,
4864 ))
4865 .and_then(|scope| {
4866 scope
4867 .brackets()
4868 .find(|(pair, _)| {
4869 pair.start == bracket_pair.start
4870 })
4871 .map(|(_, enabled)| !enabled)
4872 })
4873 .unwrap_or(false);
4874
4875 is_enabled && !is_delimiter
4876 })
4877 .count();
4878 current_line_count % 2 == 1
4879 } else {
4880 false
4881 };
4882
4883 if autoclose
4884 && bracket_pair.close
4885 && following_text_allows_autoclose
4886 && preceding_text_allows_autoclose
4887 && !is_closing_quote
4888 {
4889 let anchor = snapshot.anchor_before(selection.end);
4890 new_selections.push((selection.map(|_| anchor), text.len()));
4891 new_autoclose_regions.push((
4892 anchor,
4893 text.len(),
4894 selection.id,
4895 bracket_pair.clone(),
4896 ));
4897 edits.push((
4898 selection.range(),
4899 format!("{}{}", text, bracket_pair.end).into(),
4900 ));
4901 bracket_inserted = true;
4902 continue;
4903 }
4904 }
4905
4906 if let Some(region) = autoclose_region {
4907 // If the selection is followed by an auto-inserted closing bracket,
4908 // then don't insert that closing bracket again; just move the selection
4909 // past the closing bracket.
4910 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4911 && text.as_ref() == region.pair.end.as_str()
4912 && snapshot.contains_str_at(region.range.end, text.as_ref());
4913 if should_skip {
4914 let anchor = snapshot.anchor_after(selection.end);
4915 new_selections
4916 .push((selection.map(|_| anchor), region.pair.end.len()));
4917 continue;
4918 }
4919 }
4920
4921 let always_treat_brackets_as_autoclosed = snapshot
4922 .language_settings_at(selection.start, cx)
4923 .always_treat_brackets_as_autoclosed;
4924 if always_treat_brackets_as_autoclosed
4925 && is_bracket_pair_end
4926 && snapshot.contains_str_at(selection.end, text.as_ref())
4927 {
4928 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4929 // and the inserted text is a closing bracket and the selection is followed
4930 // by the closing bracket then move the selection past the closing bracket.
4931 let anchor = snapshot.anchor_after(selection.end);
4932 new_selections.push((selection.map(|_| anchor), text.len()));
4933 continue;
4934 }
4935 }
4936 // If an opening bracket is 1 character long and is typed while
4937 // text is selected, then surround that text with the bracket pair.
4938 else if auto_surround
4939 && bracket_pair.surround
4940 && is_bracket_pair_start
4941 && bracket_pair.start.chars().count() == 1
4942 {
4943 edits.push((selection.start..selection.start, text.clone()));
4944 edits.push((
4945 selection.end..selection.end,
4946 bracket_pair.end.as_str().into(),
4947 ));
4948 bracket_inserted = true;
4949 new_selections.push((
4950 Selection {
4951 id: selection.id,
4952 start: snapshot.anchor_after(selection.start),
4953 end: snapshot.anchor_before(selection.end),
4954 reversed: selection.reversed,
4955 goal: selection.goal,
4956 },
4957 0,
4958 ));
4959 continue;
4960 }
4961 }
4962 }
4963
4964 if self.auto_replace_emoji_shortcode
4965 && selection.is_empty()
4966 && text.as_ref().ends_with(':')
4967 && let Some(possible_emoji_short_code) =
4968 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4969 && !possible_emoji_short_code.is_empty()
4970 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4971 {
4972 let emoji_shortcode_start = Point::new(
4973 selection.start.row,
4974 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4975 );
4976
4977 // Remove shortcode from buffer
4978 edits.push((
4979 emoji_shortcode_start..selection.start,
4980 "".to_string().into(),
4981 ));
4982 new_selections.push((
4983 Selection {
4984 id: selection.id,
4985 start: snapshot.anchor_after(emoji_shortcode_start),
4986 end: snapshot.anchor_before(selection.start),
4987 reversed: selection.reversed,
4988 goal: selection.goal,
4989 },
4990 0,
4991 ));
4992
4993 // Insert emoji
4994 let selection_start_anchor = snapshot.anchor_after(selection.start);
4995 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4996 edits.push((selection.start..selection.end, emoji.to_string().into()));
4997
4998 continue;
4999 }
5000
5001 let next_is_adjacent = regions
5002 .peek()
5003 .is_some_and(|(next, _)| selection.end == next.start);
5004
5005 // If not handling any auto-close operation, then just replace the selected
5006 // text with the given input and move the selection to the end of the
5007 // newly inserted text.
5008 let anchor = if in_adjacent_group || next_is_adjacent {
5009 // After edits the right bias would shift those anchor to the next visible fragment
5010 // but we want to resolve to the previous one
5011 snapshot.anchor_before(selection.end)
5012 } else {
5013 snapshot.anchor_after(selection.end)
5014 };
5015
5016 if !self.linked_edit_ranges.is_empty() {
5017 let start_anchor = snapshot.anchor_before(selection.start);
5018
5019 let is_word_char = text.chars().next().is_none_or(|char| {
5020 let classifier = snapshot
5021 .char_classifier_at(start_anchor.to_offset(&snapshot))
5022 .scope_context(Some(CharScopeContext::LinkedEdit));
5023 classifier.is_word(char)
5024 });
5025 let is_dot = text.as_ref() == ".";
5026 let should_apply_linked_edit = is_word_char || is_dot;
5027
5028 if should_apply_linked_edit {
5029 let anchor_range = start_anchor.text_anchor..anchor.text_anchor;
5030 linked_edits.push(&self, anchor_range, text.clone(), cx);
5031 } else {
5032 clear_linked_edit_ranges = true;
5033 }
5034 }
5035
5036 new_selections.push((selection.map(|_| anchor), 0));
5037 edits.push((selection.start..selection.end, text.clone()));
5038
5039 has_adjacent_edits |= next_is_adjacent;
5040 in_adjacent_group = next_is_adjacent;
5041 }
5042
5043 if all_selections_read_only {
5044 return;
5045 }
5046
5047 drop(regions);
5048 drop(snapshot);
5049
5050 self.transact(window, cx, |this, window, cx| {
5051 if clear_linked_edit_ranges {
5052 this.linked_edit_ranges.clear();
5053 }
5054 let initial_buffer_versions =
5055 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
5056
5057 this.buffer.update(cx, |buffer, cx| {
5058 if has_adjacent_edits {
5059 buffer.edit_non_coalesce(edits, this.autoindent_mode.clone(), cx);
5060 } else {
5061 buffer.edit(edits, this.autoindent_mode.clone(), cx);
5062 }
5063 });
5064 linked_edits.apply(cx);
5065 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
5066 let new_selection_deltas = new_selections.iter().map(|e| e.1);
5067 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
5068 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
5069 new_anchor_selections,
5070 &map,
5071 )
5072 .zip(new_selection_deltas)
5073 .map(|(selection, delta)| Selection {
5074 id: selection.id,
5075 start: selection.start + delta,
5076 end: selection.end + delta,
5077 reversed: selection.reversed,
5078 goal: SelectionGoal::None,
5079 })
5080 .collect::<Vec<_>>();
5081
5082 let mut i = 0;
5083 for (position, delta, selection_id, pair) in new_autoclose_regions {
5084 let position = position.to_offset(map.buffer_snapshot()) + delta;
5085 let start = map.buffer_snapshot().anchor_before(position);
5086 let end = map.buffer_snapshot().anchor_after(position);
5087 while let Some(existing_state) = this.autoclose_regions.get(i) {
5088 match existing_state
5089 .range
5090 .start
5091 .cmp(&start, map.buffer_snapshot())
5092 {
5093 Ordering::Less => i += 1,
5094 Ordering::Greater => break,
5095 Ordering::Equal => {
5096 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
5097 Ordering::Less => i += 1,
5098 Ordering::Equal => break,
5099 Ordering::Greater => break,
5100 }
5101 }
5102 }
5103 }
5104 this.autoclose_regions.insert(
5105 i,
5106 AutocloseRegion {
5107 selection_id,
5108 range: start..end,
5109 pair,
5110 },
5111 );
5112 }
5113
5114 let had_active_edit_prediction = this.has_active_edit_prediction();
5115 this.change_selections(
5116 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
5117 window,
5118 cx,
5119 |s| s.select(new_selections),
5120 );
5121
5122 if !bracket_inserted
5123 && let Some(on_type_format_task) =
5124 this.trigger_on_type_formatting(text.to_string(), window, cx)
5125 {
5126 on_type_format_task.detach_and_log_err(cx);
5127 }
5128
5129 let editor_settings = EditorSettings::get_global(cx);
5130 if bracket_inserted
5131 && (editor_settings.auto_signature_help
5132 || editor_settings.show_signature_help_after_edits)
5133 {
5134 this.show_signature_help(&ShowSignatureHelp, window, cx);
5135 }
5136
5137 let trigger_in_words =
5138 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
5139 if this.hard_wrap.is_some() {
5140 let latest: Range<Point> = this.selections.newest(&map).range();
5141 if latest.is_empty()
5142 && this
5143 .buffer()
5144 .read(cx)
5145 .snapshot(cx)
5146 .line_len(MultiBufferRow(latest.start.row))
5147 == latest.start.column
5148 {
5149 this.rewrap_impl(
5150 RewrapOptions {
5151 override_language_settings: true,
5152 preserve_existing_whitespace: true,
5153 },
5154 cx,
5155 )
5156 }
5157 }
5158 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
5159 refresh_linked_ranges(this, window, cx);
5160 this.refresh_edit_prediction(true, false, window, cx);
5161 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
5162 });
5163 }
5164
5165 fn find_possible_emoji_shortcode_at_position(
5166 snapshot: &MultiBufferSnapshot,
5167 position: Point,
5168 ) -> Option<String> {
5169 let mut chars = Vec::new();
5170 let mut found_colon = false;
5171 for char in snapshot.reversed_chars_at(position).take(100) {
5172 // Found a possible emoji shortcode in the middle of the buffer
5173 if found_colon {
5174 if char.is_whitespace() {
5175 chars.reverse();
5176 return Some(chars.iter().collect());
5177 }
5178 // If the previous character is not a whitespace, we are in the middle of a word
5179 // and we only want to complete the shortcode if the word is made up of other emojis
5180 let mut containing_word = String::new();
5181 for ch in snapshot
5182 .reversed_chars_at(position)
5183 .skip(chars.len() + 1)
5184 .take(100)
5185 {
5186 if ch.is_whitespace() {
5187 break;
5188 }
5189 containing_word.push(ch);
5190 }
5191 let containing_word = containing_word.chars().rev().collect::<String>();
5192 if util::word_consists_of_emojis(containing_word.as_str()) {
5193 chars.reverse();
5194 return Some(chars.iter().collect());
5195 }
5196 }
5197
5198 if char.is_whitespace() || !char.is_ascii() {
5199 return None;
5200 }
5201 if char == ':' {
5202 found_colon = true;
5203 } else {
5204 chars.push(char);
5205 }
5206 }
5207 // Found a possible emoji shortcode at the beginning of the buffer
5208 chars.reverse();
5209 Some(chars.iter().collect())
5210 }
5211
5212 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
5213 if self.read_only(cx) {
5214 return;
5215 }
5216
5217 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5218 self.transact(window, cx, |this, window, cx| {
5219 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
5220 let selections = this
5221 .selections
5222 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
5223 let multi_buffer = this.buffer.read(cx);
5224 let buffer = multi_buffer.snapshot(cx);
5225 selections
5226 .iter()
5227 .map(|selection| {
5228 let start_point = selection.start.to_point(&buffer);
5229 let mut existing_indent =
5230 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
5231 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
5232 let start = selection.start;
5233 let end = selection.end;
5234 let selection_is_empty = start == end;
5235 let language_scope = buffer.language_scope_at(start);
5236 let (delimiter, newline_config) = if let Some(language) = &language_scope {
5237 let needs_extra_newline = NewlineConfig::insert_extra_newline_brackets(
5238 &buffer,
5239 start..end,
5240 language,
5241 )
5242 || NewlineConfig::insert_extra_newline_tree_sitter(
5243 &buffer,
5244 start..end,
5245 );
5246
5247 let mut newline_config = NewlineConfig::Newline {
5248 additional_indent: IndentSize::spaces(0),
5249 extra_line_additional_indent: if needs_extra_newline {
5250 Some(IndentSize::spaces(0))
5251 } else {
5252 None
5253 },
5254 prevent_auto_indent: false,
5255 };
5256
5257 let comment_delimiter = maybe!({
5258 if !selection_is_empty {
5259 return None;
5260 }
5261
5262 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5263 return None;
5264 }
5265
5266 return comment_delimiter_for_newline(
5267 &start_point,
5268 &buffer,
5269 language,
5270 );
5271 });
5272
5273 let doc_delimiter = maybe!({
5274 if !selection_is_empty {
5275 return None;
5276 }
5277
5278 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5279 return None;
5280 }
5281
5282 return documentation_delimiter_for_newline(
5283 &start_point,
5284 &buffer,
5285 language,
5286 &mut newline_config,
5287 );
5288 });
5289
5290 let list_delimiter = maybe!({
5291 if !selection_is_empty {
5292 return None;
5293 }
5294
5295 if !multi_buffer.language_settings(cx).extend_list_on_newline {
5296 return None;
5297 }
5298
5299 return list_delimiter_for_newline(
5300 &start_point,
5301 &buffer,
5302 language,
5303 &mut newline_config,
5304 );
5305 });
5306
5307 (
5308 comment_delimiter.or(doc_delimiter).or(list_delimiter),
5309 newline_config,
5310 )
5311 } else {
5312 (
5313 None,
5314 NewlineConfig::Newline {
5315 additional_indent: IndentSize::spaces(0),
5316 extra_line_additional_indent: None,
5317 prevent_auto_indent: false,
5318 },
5319 )
5320 };
5321
5322 let (edit_start, new_text, prevent_auto_indent) = match &newline_config {
5323 NewlineConfig::ClearCurrentLine => {
5324 let row_start =
5325 buffer.point_to_offset(Point::new(start_point.row, 0));
5326 (row_start, String::new(), false)
5327 }
5328 NewlineConfig::UnindentCurrentLine { continuation } => {
5329 let row_start =
5330 buffer.point_to_offset(Point::new(start_point.row, 0));
5331 let tab_size = buffer.language_settings_at(start, cx).tab_size;
5332 let tab_size_indent = IndentSize::spaces(tab_size.get());
5333 let reduced_indent =
5334 existing_indent.with_delta(Ordering::Less, tab_size_indent);
5335 let mut new_text = String::new();
5336 new_text.extend(reduced_indent.chars());
5337 new_text.push_str(continuation);
5338 (row_start, new_text, true)
5339 }
5340 NewlineConfig::Newline {
5341 additional_indent,
5342 extra_line_additional_indent,
5343 prevent_auto_indent,
5344 } => {
5345 let auto_indent_mode =
5346 buffer.language_settings_at(start, cx).auto_indent;
5347 let preserve_indent =
5348 auto_indent_mode != language::AutoIndentMode::None;
5349 let apply_syntax_indent =
5350 auto_indent_mode == language::AutoIndentMode::SyntaxAware;
5351 let capacity_for_delimiter =
5352 delimiter.as_deref().map(str::len).unwrap_or_default();
5353 let existing_indent_len = if preserve_indent {
5354 existing_indent.len as usize
5355 } else {
5356 0
5357 };
5358 let extra_line_len = extra_line_additional_indent
5359 .map(|i| 1 + existing_indent_len + i.len as usize)
5360 .unwrap_or(0);
5361 let mut new_text = String::with_capacity(
5362 1 + capacity_for_delimiter
5363 + existing_indent_len
5364 + additional_indent.len as usize
5365 + extra_line_len,
5366 );
5367 new_text.push('\n');
5368 if preserve_indent {
5369 new_text.extend(existing_indent.chars());
5370 }
5371 new_text.extend(additional_indent.chars());
5372 if let Some(delimiter) = &delimiter {
5373 new_text.push_str(delimiter);
5374 }
5375 if let Some(extra_indent) = extra_line_additional_indent {
5376 new_text.push('\n');
5377 if preserve_indent {
5378 new_text.extend(existing_indent.chars());
5379 }
5380 new_text.extend(extra_indent.chars());
5381 }
5382 (
5383 start,
5384 new_text,
5385 *prevent_auto_indent || !apply_syntax_indent,
5386 )
5387 }
5388 };
5389
5390 let anchor = buffer.anchor_after(end);
5391 let new_selection = selection.map(|_| anchor);
5392 (
5393 ((edit_start..end, new_text), prevent_auto_indent),
5394 (newline_config.has_extra_line(), new_selection),
5395 )
5396 })
5397 .unzip()
5398 };
5399
5400 let mut auto_indent_edits = Vec::new();
5401 let mut edits = Vec::new();
5402 for (edit, prevent_auto_indent) in edits_with_flags {
5403 if prevent_auto_indent {
5404 edits.push(edit);
5405 } else {
5406 auto_indent_edits.push(edit);
5407 }
5408 }
5409 if !edits.is_empty() {
5410 this.edit(edits, cx);
5411 }
5412 if !auto_indent_edits.is_empty() {
5413 this.edit_with_autoindent(auto_indent_edits, cx);
5414 }
5415
5416 let buffer = this.buffer.read(cx).snapshot(cx);
5417 let new_selections = selection_info
5418 .into_iter()
5419 .map(|(extra_newline_inserted, new_selection)| {
5420 let mut cursor = new_selection.end.to_point(&buffer);
5421 if extra_newline_inserted {
5422 cursor.row -= 1;
5423 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
5424 }
5425 new_selection.map(|_| cursor)
5426 })
5427 .collect();
5428
5429 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
5430 this.refresh_edit_prediction(true, false, window, cx);
5431 if let Some(task) = this.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5432 task.detach_and_log_err(cx);
5433 }
5434 });
5435 }
5436
5437 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
5438 if self.read_only(cx) {
5439 return;
5440 }
5441
5442 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5443
5444 let buffer = self.buffer.read(cx);
5445 let snapshot = buffer.snapshot(cx);
5446
5447 let mut edits = Vec::new();
5448 let mut rows = Vec::new();
5449
5450 for (rows_inserted, selection) in self
5451 .selections
5452 .all_adjusted(&self.display_snapshot(cx))
5453 .into_iter()
5454 .enumerate()
5455 {
5456 let cursor = selection.head();
5457 let row = cursor.row;
5458
5459 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
5460
5461 let newline = "\n".to_string();
5462 edits.push((start_of_line..start_of_line, newline));
5463
5464 rows.push(row + rows_inserted as u32);
5465 }
5466
5467 self.transact(window, cx, |editor, window, cx| {
5468 editor.edit(edits, cx);
5469
5470 editor.change_selections(Default::default(), window, cx, |s| {
5471 let mut index = 0;
5472 s.move_cursors_with(&mut |map, _, _| {
5473 let row = rows[index];
5474 index += 1;
5475
5476 let point = Point::new(row, 0);
5477 let boundary = map.next_line_boundary(point).1;
5478 let clipped = map.clip_point(boundary, Bias::Left);
5479
5480 (clipped, SelectionGoal::None)
5481 });
5482 });
5483
5484 let mut indent_edits = Vec::new();
5485 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5486 for row in rows {
5487 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5488 for (row, indent) in indents {
5489 if indent.len == 0 {
5490 continue;
5491 }
5492
5493 let text = match indent.kind {
5494 IndentKind::Space => " ".repeat(indent.len as usize),
5495 IndentKind::Tab => "\t".repeat(indent.len as usize),
5496 };
5497 let point = Point::new(row.0, 0);
5498 indent_edits.push((point..point, text));
5499 }
5500 }
5501 editor.edit(indent_edits, cx);
5502 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5503 format.detach_and_log_err(cx);
5504 }
5505 });
5506 }
5507
5508 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5509 if self.read_only(cx) {
5510 return;
5511 }
5512
5513 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5514
5515 let mut buffer_edits: HashMap<EntityId, (Entity<Buffer>, Vec<Point>)> = HashMap::default();
5516 let mut rows = Vec::new();
5517 let mut rows_inserted = 0;
5518
5519 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5520 let cursor = selection.head();
5521 let row = cursor.row;
5522
5523 let point = Point::new(row, 0);
5524 let Some((buffer_handle, buffer_point, _)) =
5525 self.buffer.read(cx).point_to_buffer_point(point, cx)
5526 else {
5527 continue;
5528 };
5529
5530 buffer_edits
5531 .entry(buffer_handle.entity_id())
5532 .or_insert_with(|| (buffer_handle, Vec::new()))
5533 .1
5534 .push(buffer_point);
5535
5536 rows_inserted += 1;
5537 rows.push(row + rows_inserted);
5538 }
5539
5540 self.transact(window, cx, |editor, window, cx| {
5541 for (_, (buffer_handle, points)) in &buffer_edits {
5542 buffer_handle.update(cx, |buffer, cx| {
5543 let edits: Vec<_> = points
5544 .iter()
5545 .map(|point| {
5546 let target = Point::new(point.row + 1, 0);
5547 let start_of_line = buffer.point_to_offset(target).min(buffer.len());
5548 (start_of_line..start_of_line, "\n")
5549 })
5550 .collect();
5551 buffer.edit(edits, None, cx);
5552 });
5553 }
5554
5555 editor.change_selections(Default::default(), window, cx, |s| {
5556 let mut index = 0;
5557 s.move_cursors_with(&mut |map, _, _| {
5558 let row = rows[index];
5559 index += 1;
5560
5561 let point = Point::new(row, 0);
5562 let boundary = map.next_line_boundary(point).1;
5563 let clipped = map.clip_point(boundary, Bias::Left);
5564
5565 (clipped, SelectionGoal::None)
5566 });
5567 });
5568
5569 let mut indent_edits = Vec::new();
5570 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5571 for row in rows {
5572 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5573 for (row, indent) in indents {
5574 if indent.len == 0 {
5575 continue;
5576 }
5577
5578 let text = match indent.kind {
5579 IndentKind::Space => " ".repeat(indent.len as usize),
5580 IndentKind::Tab => "\t".repeat(indent.len as usize),
5581 };
5582 let point = Point::new(row.0, 0);
5583 indent_edits.push((point..point, text));
5584 }
5585 }
5586 editor.edit(indent_edits, cx);
5587 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5588 format.detach_and_log_err(cx);
5589 }
5590 });
5591 }
5592
5593 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5594 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5595 original_indent_columns: Vec::new(),
5596 });
5597 self.replace_selections(text, autoindent, window, cx, false);
5598 }
5599
5600 /// Replaces the editor's selections with the provided `text`, applying the
5601 /// given `autoindent_mode` (`None` will skip autoindentation).
5602 ///
5603 /// Early returns if the editor is in read-only mode, without applying any
5604 /// edits.
5605 fn replace_selections(
5606 &mut self,
5607 text: &str,
5608 autoindent_mode: Option<AutoindentMode>,
5609 window: &mut Window,
5610 cx: &mut Context<Self>,
5611 apply_linked_edits: bool,
5612 ) {
5613 if self.read_only(cx) {
5614 return;
5615 }
5616
5617 let text: Arc<str> = text.into();
5618 self.transact(window, cx, |this, window, cx| {
5619 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5620 let linked_edits = if apply_linked_edits {
5621 this.linked_edits_for_selections(text.clone(), cx)
5622 } else {
5623 LinkedEdits::new()
5624 };
5625
5626 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5627 let anchors = {
5628 let snapshot = buffer.read(cx);
5629 old_selections
5630 .iter()
5631 .map(|s| {
5632 let anchor = snapshot.anchor_after(s.head());
5633 s.map(|_| anchor)
5634 })
5635 .collect::<Vec<_>>()
5636 };
5637 buffer.edit(
5638 old_selections
5639 .iter()
5640 .map(|s| (s.start..s.end, text.clone())),
5641 autoindent_mode,
5642 cx,
5643 );
5644 anchors
5645 });
5646
5647 linked_edits.apply(cx);
5648
5649 this.change_selections(Default::default(), window, cx, |s| {
5650 s.select_anchors(selection_anchors);
5651 });
5652
5653 if apply_linked_edits {
5654 refresh_linked_ranges(this, window, cx);
5655 }
5656
5657 cx.notify();
5658 });
5659 }
5660
5661 /// Collects linked edits for the current selections, pairing each linked
5662 /// range with `text`.
5663 pub fn linked_edits_for_selections(&self, text: Arc<str>, cx: &App) -> LinkedEdits {
5664 let mut linked_edits = LinkedEdits::new();
5665 if !self.linked_edit_ranges.is_empty() {
5666 for selection in self.selections.disjoint_anchors() {
5667 let start = selection.start.text_anchor;
5668 let end = selection.end.text_anchor;
5669 linked_edits.push(self, start..end, text.clone(), cx);
5670 }
5671 }
5672 linked_edits
5673 }
5674
5675 /// Deletes the content covered by the current selections and applies
5676 /// linked edits.
5677 pub fn delete_selections_with_linked_edits(
5678 &mut self,
5679 window: &mut Window,
5680 cx: &mut Context<Self>,
5681 ) {
5682 self.replace_selections("", None, window, cx, true);
5683 }
5684
5685 #[cfg(any(test, feature = "test-support"))]
5686 pub fn set_linked_edit_ranges_for_testing(
5687 &mut self,
5688 ranges: Vec<(Range<Point>, Vec<Range<Point>>)>,
5689 cx: &mut Context<Self>,
5690 ) -> Option<()> {
5691 let Some((buffer, _)) = self
5692 .buffer
5693 .read(cx)
5694 .text_anchor_for_position(self.selections.newest_anchor().start, cx)
5695 else {
5696 return None;
5697 };
5698 let buffer = buffer.read(cx);
5699 let buffer_id = buffer.remote_id();
5700 let mut linked_ranges = Vec::with_capacity(ranges.len());
5701 for (base_range, linked_ranges_points) in ranges {
5702 let base_anchor =
5703 buffer.anchor_before(base_range.start)..buffer.anchor_after(base_range.end);
5704 let linked_anchors = linked_ranges_points
5705 .into_iter()
5706 .map(|range| buffer.anchor_before(range.start)..buffer.anchor_after(range.end))
5707 .collect();
5708 linked_ranges.push((base_anchor, linked_anchors));
5709 }
5710 let mut map = HashMap::default();
5711 map.insert(buffer_id, linked_ranges);
5712 self.linked_edit_ranges = linked_editing_ranges::LinkedEditingRanges(map);
5713 Some(())
5714 }
5715
5716 fn trigger_completion_on_input(
5717 &mut self,
5718 text: &str,
5719 trigger_in_words: bool,
5720 window: &mut Window,
5721 cx: &mut Context<Self>,
5722 ) {
5723 let completions_source = self
5724 .context_menu
5725 .borrow()
5726 .as_ref()
5727 .and_then(|menu| match menu {
5728 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5729 CodeContextMenu::CodeActions(_) => None,
5730 });
5731
5732 match completions_source {
5733 Some(CompletionsMenuSource::Words { .. }) => {
5734 self.open_or_update_completions_menu(
5735 Some(CompletionsMenuSource::Words {
5736 ignore_threshold: false,
5737 }),
5738 None,
5739 trigger_in_words,
5740 window,
5741 cx,
5742 );
5743 }
5744 _ => self.open_or_update_completions_menu(
5745 None,
5746 Some(text.to_owned()).filter(|x| !x.is_empty()),
5747 true,
5748 window,
5749 cx,
5750 ),
5751 }
5752 }
5753
5754 /// If any empty selections is touching the start of its innermost containing autoclose
5755 /// region, expand it to select the brackets.
5756 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5757 let selections = self
5758 .selections
5759 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5760 let buffer = self.buffer.read(cx).read(cx);
5761 let new_selections = self
5762 .selections_with_autoclose_regions(selections, &buffer)
5763 .map(|(mut selection, region)| {
5764 if !selection.is_empty() {
5765 return selection;
5766 }
5767
5768 if let Some(region) = region {
5769 let mut range = region.range.to_offset(&buffer);
5770 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5771 range.start -= region.pair.start.len();
5772 if buffer.contains_str_at(range.start, ®ion.pair.start)
5773 && buffer.contains_str_at(range.end, ®ion.pair.end)
5774 {
5775 range.end += region.pair.end.len();
5776 selection.start = range.start;
5777 selection.end = range.end;
5778
5779 return selection;
5780 }
5781 }
5782 }
5783
5784 let always_treat_brackets_as_autoclosed = buffer
5785 .language_settings_at(selection.start, cx)
5786 .always_treat_brackets_as_autoclosed;
5787
5788 if !always_treat_brackets_as_autoclosed {
5789 return selection;
5790 }
5791
5792 if let Some(scope) = buffer.language_scope_at(selection.start) {
5793 for (pair, enabled) in scope.brackets() {
5794 if !enabled || !pair.close {
5795 continue;
5796 }
5797
5798 if buffer.contains_str_at(selection.start, &pair.end) {
5799 let pair_start_len = pair.start.len();
5800 if buffer.contains_str_at(
5801 selection.start.saturating_sub_usize(pair_start_len),
5802 &pair.start,
5803 ) {
5804 selection.start -= pair_start_len;
5805 selection.end += pair.end.len();
5806
5807 return selection;
5808 }
5809 }
5810 }
5811 }
5812
5813 selection
5814 })
5815 .collect();
5816
5817 drop(buffer);
5818 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5819 selections.select(new_selections)
5820 });
5821 }
5822
5823 /// Iterate the given selections, and for each one, find the smallest surrounding
5824 /// autoclose region. This uses the ordering of the selections and the autoclose
5825 /// regions to avoid repeated comparisons.
5826 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5827 &'a self,
5828 selections: impl IntoIterator<Item = Selection<D>>,
5829 buffer: &'a MultiBufferSnapshot,
5830 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5831 let mut i = 0;
5832 let mut regions = self.autoclose_regions.as_slice();
5833 selections.into_iter().map(move |selection| {
5834 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5835
5836 let mut enclosing = None;
5837 while let Some(pair_state) = regions.get(i) {
5838 if pair_state.range.end.to_offset(buffer) < range.start {
5839 regions = ®ions[i + 1..];
5840 i = 0;
5841 } else if pair_state.range.start.to_offset(buffer) > range.end {
5842 break;
5843 } else {
5844 if pair_state.selection_id == selection.id {
5845 enclosing = Some(pair_state);
5846 }
5847 i += 1;
5848 }
5849 }
5850
5851 (selection, enclosing)
5852 })
5853 }
5854
5855 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5856 fn invalidate_autoclose_regions(
5857 &mut self,
5858 mut selections: &[Selection<Anchor>],
5859 buffer: &MultiBufferSnapshot,
5860 ) {
5861 self.autoclose_regions.retain(|state| {
5862 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5863 return false;
5864 }
5865
5866 let mut i = 0;
5867 while let Some(selection) = selections.get(i) {
5868 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5869 selections = &selections[1..];
5870 continue;
5871 }
5872 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5873 break;
5874 }
5875 if selection.id == state.selection_id {
5876 return true;
5877 } else {
5878 i += 1;
5879 }
5880 }
5881 false
5882 });
5883 }
5884
5885 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5886 let offset = position.to_offset(buffer);
5887 let (word_range, kind) =
5888 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5889 if offset > word_range.start && kind == Some(CharKind::Word) {
5890 Some(
5891 buffer
5892 .text_for_range(word_range.start..offset)
5893 .collect::<String>(),
5894 )
5895 } else {
5896 None
5897 }
5898 }
5899
5900 pub fn visible_excerpts(
5901 &self,
5902 lsp_related_only: bool,
5903 cx: &mut Context<Editor>,
5904 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5905 let project = self.project().cloned();
5906 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
5907 let multi_buffer = self.buffer().read(cx);
5908 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5909 multi_buffer_snapshot
5910 .range_to_buffer_ranges(
5911 self.multi_buffer_visible_range(&display_snapshot, cx)
5912 .to_inclusive(),
5913 )
5914 .into_iter()
5915 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5916 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5917 if !lsp_related_only {
5918 return Some((
5919 excerpt_id,
5920 (
5921 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5922 buffer.version().clone(),
5923 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5924 ),
5925 ));
5926 }
5927
5928 let project = project.as_ref()?.read(cx);
5929 let buffer_file = project::File::from_dyn(buffer.file())?;
5930 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5931 let worktree_entry = buffer_worktree
5932 .read(cx)
5933 .entry_for_id(buffer_file.project_entry_id()?)?;
5934 if worktree_entry.is_ignored {
5935 None
5936 } else {
5937 Some((
5938 excerpt_id,
5939 (
5940 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5941 buffer.version().clone(),
5942 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5943 ),
5944 ))
5945 }
5946 })
5947 .collect()
5948 }
5949
5950 pub fn text_layout_details(&self, window: &mut Window, cx: &mut App) -> TextLayoutDetails {
5951 TextLayoutDetails {
5952 text_system: window.text_system().clone(),
5953 editor_style: self.style.clone().unwrap(),
5954 rem_size: window.rem_size(),
5955 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
5956 visible_rows: self.visible_line_count(),
5957 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5958 }
5959 }
5960
5961 fn trigger_on_type_formatting(
5962 &self,
5963 input: String,
5964 window: &mut Window,
5965 cx: &mut Context<Self>,
5966 ) -> Option<Task<Result<()>>> {
5967 if input.chars().count() != 1 {
5968 return None;
5969 }
5970
5971 let project = self.project()?;
5972 let position = self.selections.newest_anchor().head();
5973 let (buffer, buffer_position) = self
5974 .buffer
5975 .read(cx)
5976 .text_anchor_for_position(position, cx)?;
5977
5978 let settings = language_settings::language_settings(
5979 buffer
5980 .read(cx)
5981 .language_at(buffer_position)
5982 .map(|l| l.name()),
5983 buffer.read(cx).file(),
5984 cx,
5985 );
5986 if !settings.use_on_type_format {
5987 return None;
5988 }
5989
5990 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5991 // hence we do LSP request & edit on host side only — add formats to host's history.
5992 let push_to_lsp_host_history = true;
5993 // If this is not the host, append its history with new edits.
5994 let push_to_client_history = project.read(cx).is_via_collab();
5995
5996 let on_type_formatting = project.update(cx, |project, cx| {
5997 project.on_type_format(
5998 buffer.clone(),
5999 buffer_position,
6000 input,
6001 push_to_lsp_host_history,
6002 cx,
6003 )
6004 });
6005 Some(cx.spawn_in(window, async move |editor, cx| {
6006 if let Some(transaction) = on_type_formatting.await? {
6007 if push_to_client_history {
6008 buffer.update(cx, |buffer, _| {
6009 buffer.push_transaction(transaction, Instant::now());
6010 buffer.finalize_last_transaction();
6011 });
6012 }
6013 editor.update(cx, |editor, cx| {
6014 editor.refresh_document_highlights(cx);
6015 })?;
6016 }
6017 Ok(())
6018 }))
6019 }
6020
6021 pub fn show_word_completions(
6022 &mut self,
6023 _: &ShowWordCompletions,
6024 window: &mut Window,
6025 cx: &mut Context<Self>,
6026 ) {
6027 self.open_or_update_completions_menu(
6028 Some(CompletionsMenuSource::Words {
6029 ignore_threshold: true,
6030 }),
6031 None,
6032 false,
6033 window,
6034 cx,
6035 );
6036 }
6037
6038 pub fn show_completions(
6039 &mut self,
6040 _: &ShowCompletions,
6041 window: &mut Window,
6042 cx: &mut Context<Self>,
6043 ) {
6044 self.open_or_update_completions_menu(None, None, false, window, cx);
6045 }
6046
6047 fn open_or_update_completions_menu(
6048 &mut self,
6049 requested_source: Option<CompletionsMenuSource>,
6050 trigger: Option<String>,
6051 trigger_in_words: bool,
6052 window: &mut Window,
6053 cx: &mut Context<Self>,
6054 ) {
6055 if self.pending_rename.is_some() {
6056 return;
6057 }
6058
6059 let completions_source = self
6060 .context_menu
6061 .borrow()
6062 .as_ref()
6063 .and_then(|menu| match menu {
6064 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
6065 CodeContextMenu::CodeActions(_) => None,
6066 });
6067
6068 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
6069
6070 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
6071 // inserted and selected. To handle that case, the start of the selection is used so that
6072 // the menu starts with all choices.
6073 let position = self
6074 .selections
6075 .newest_anchor()
6076 .start
6077 .bias_right(&multibuffer_snapshot);
6078 if position.diff_base_anchor.is_some() {
6079 return;
6080 }
6081 let buffer_position = multibuffer_snapshot.anchor_before(position);
6082 let Some(buffer) = buffer_position
6083 .text_anchor
6084 .buffer_id
6085 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
6086 else {
6087 return;
6088 };
6089 let buffer_snapshot = buffer.read(cx).snapshot();
6090
6091 let menu_is_open = matches!(
6092 self.context_menu.borrow().as_ref(),
6093 Some(CodeContextMenu::Completions(_))
6094 );
6095
6096 let language = buffer_snapshot
6097 .language_at(buffer_position.text_anchor)
6098 .map(|language| language.name());
6099
6100 let language_settings = language_settings(language.clone(), buffer_snapshot.file(), cx);
6101 let completion_settings = language_settings.completions.clone();
6102
6103 let show_completions_on_input = self
6104 .show_completions_on_input_override
6105 .unwrap_or(language_settings.show_completions_on_input);
6106 if !menu_is_open && trigger.is_some() && !show_completions_on_input {
6107 return;
6108 }
6109
6110 let query: Option<Arc<String>> =
6111 Self::completion_query(&multibuffer_snapshot, buffer_position)
6112 .map(|query| query.into());
6113
6114 drop(multibuffer_snapshot);
6115
6116 // Hide the current completions menu when query is empty. Without this, cached
6117 // completions from before the trigger char may be reused (#32774).
6118 if query.is_none() && menu_is_open {
6119 self.hide_context_menu(window, cx);
6120 }
6121
6122 let mut ignore_word_threshold = false;
6123 let provider = match requested_source {
6124 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
6125 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
6126 ignore_word_threshold = ignore_threshold;
6127 None
6128 }
6129 Some(CompletionsMenuSource::SnippetChoices)
6130 | Some(CompletionsMenuSource::SnippetsOnly) => {
6131 log::error!("bug: SnippetChoices requested_source is not handled");
6132 None
6133 }
6134 };
6135
6136 let sort_completions = provider
6137 .as_ref()
6138 .is_some_and(|provider| provider.sort_completions());
6139
6140 let filter_completions = provider
6141 .as_ref()
6142 .is_none_or(|provider| provider.filter_completions());
6143
6144 let was_snippets_only = matches!(
6145 completions_source,
6146 Some(CompletionsMenuSource::SnippetsOnly)
6147 );
6148
6149 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
6150 if filter_completions {
6151 menu.filter(
6152 query.clone().unwrap_or_default(),
6153 buffer_position.text_anchor,
6154 &buffer,
6155 provider.clone(),
6156 window,
6157 cx,
6158 );
6159 }
6160 // When `is_incomplete` is false, no need to re-query completions when the current query
6161 // is a suffix of the initial query.
6162 let was_complete = !menu.is_incomplete;
6163 if was_complete && !was_snippets_only {
6164 // If the new query is a suffix of the old query (typing more characters) and
6165 // the previous result was complete, the existing completions can be filtered.
6166 //
6167 // Note that snippet completions are always complete.
6168 let query_matches = match (&menu.initial_query, &query) {
6169 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
6170 (None, _) => true,
6171 _ => false,
6172 };
6173 if query_matches {
6174 let position_matches = if menu.initial_position == position {
6175 true
6176 } else {
6177 let snapshot = self.buffer.read(cx).read(cx);
6178 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
6179 };
6180 if position_matches {
6181 return;
6182 }
6183 }
6184 }
6185 };
6186
6187 let Anchor {
6188 excerpt_id: buffer_excerpt_id,
6189 text_anchor: buffer_position,
6190 ..
6191 } = buffer_position;
6192
6193 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
6194 buffer_snapshot.surrounding_word(buffer_position, None)
6195 {
6196 let word_to_exclude = buffer_snapshot
6197 .text_for_range(word_range.clone())
6198 .collect::<String>();
6199 (
6200 buffer_snapshot.anchor_before(word_range.start)
6201 ..buffer_snapshot.anchor_after(buffer_position),
6202 Some(word_to_exclude),
6203 )
6204 } else {
6205 (buffer_position..buffer_position, None)
6206 };
6207
6208 let show_completion_documentation = buffer_snapshot
6209 .settings_at(buffer_position, cx)
6210 .show_completion_documentation;
6211
6212 // The document can be large, so stay in reasonable bounds when searching for words,
6213 // otherwise completion pop-up might be slow to appear.
6214 const WORD_LOOKUP_ROWS: u32 = 5_000;
6215 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
6216 let min_word_search = buffer_snapshot.clip_point(
6217 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
6218 Bias::Left,
6219 );
6220 let max_word_search = buffer_snapshot.clip_point(
6221 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
6222 Bias::Right,
6223 );
6224 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
6225 ..buffer_snapshot.point_to_offset(max_word_search);
6226
6227 let skip_digits = query
6228 .as_ref()
6229 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
6230
6231 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
6232 trigger.as_ref().is_none_or(|trigger| {
6233 provider.is_completion_trigger(
6234 &buffer,
6235 position.text_anchor,
6236 trigger,
6237 trigger_in_words,
6238 cx,
6239 )
6240 })
6241 });
6242
6243 let provider_responses = if let Some(provider) = &provider
6244 && load_provider_completions
6245 {
6246 let trigger_character =
6247 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
6248 let completion_context = CompletionContext {
6249 trigger_kind: match &trigger_character {
6250 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
6251 None => CompletionTriggerKind::INVOKED,
6252 },
6253 trigger_character,
6254 };
6255
6256 provider.completions(
6257 buffer_excerpt_id,
6258 &buffer,
6259 buffer_position,
6260 completion_context,
6261 window,
6262 cx,
6263 )
6264 } else {
6265 Task::ready(Ok(Vec::new()))
6266 };
6267
6268 let load_word_completions = if !self.word_completions_enabled {
6269 false
6270 } else if requested_source
6271 == Some(CompletionsMenuSource::Words {
6272 ignore_threshold: true,
6273 })
6274 {
6275 true
6276 } else {
6277 load_provider_completions
6278 && completion_settings.words != WordsCompletionMode::Disabled
6279 && (ignore_word_threshold || {
6280 let words_min_length = completion_settings.words_min_length;
6281 // check whether word has at least `words_min_length` characters
6282 let query_chars = query.iter().flat_map(|q| q.chars());
6283 query_chars.take(words_min_length).count() == words_min_length
6284 })
6285 };
6286
6287 let mut words = if load_word_completions {
6288 cx.background_spawn({
6289 let buffer_snapshot = buffer_snapshot.clone();
6290 async move {
6291 buffer_snapshot.words_in_range(WordsQuery {
6292 fuzzy_contents: None,
6293 range: word_search_range,
6294 skip_digits,
6295 })
6296 }
6297 })
6298 } else {
6299 Task::ready(BTreeMap::default())
6300 };
6301
6302 let snippets = if let Some(provider) = &provider
6303 && provider.show_snippets()
6304 && let Some(project) = self.project()
6305 {
6306 let char_classifier = buffer_snapshot
6307 .char_classifier_at(buffer_position)
6308 .scope_context(Some(CharScopeContext::Completion));
6309 project.update(cx, |project, cx| {
6310 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
6311 })
6312 } else {
6313 Task::ready(Ok(CompletionResponse {
6314 completions: Vec::new(),
6315 display_options: Default::default(),
6316 is_incomplete: false,
6317 }))
6318 };
6319
6320 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
6321
6322 let id = post_inc(&mut self.next_completion_id);
6323 let task = cx.spawn_in(window, async move |editor, cx| {
6324 let Ok(()) = editor.update(cx, |this, _| {
6325 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
6326 }) else {
6327 return;
6328 };
6329
6330 // TODO: Ideally completions from different sources would be selectively re-queried, so
6331 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
6332 let mut completions = Vec::new();
6333 let mut is_incomplete = false;
6334 let mut display_options: Option<CompletionDisplayOptions> = None;
6335 if let Some(provider_responses) = provider_responses.await.log_err()
6336 && !provider_responses.is_empty()
6337 {
6338 for response in provider_responses {
6339 completions.extend(response.completions);
6340 is_incomplete = is_incomplete || response.is_incomplete;
6341 match display_options.as_mut() {
6342 None => {
6343 display_options = Some(response.display_options);
6344 }
6345 Some(options) => options.merge(&response.display_options),
6346 }
6347 }
6348 if completion_settings.words == WordsCompletionMode::Fallback {
6349 words = Task::ready(BTreeMap::default());
6350 }
6351 }
6352 let display_options = display_options.unwrap_or_default();
6353
6354 let mut words = words.await;
6355 if let Some(word_to_exclude) = &word_to_exclude {
6356 words.remove(word_to_exclude);
6357 }
6358 for lsp_completion in &completions {
6359 words.remove(&lsp_completion.new_text);
6360 }
6361 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
6362 replace_range: word_replace_range.clone(),
6363 new_text: word.clone(),
6364 label: CodeLabel::plain(word, None),
6365 match_start: None,
6366 snippet_deduplication_key: None,
6367 icon_path: None,
6368 documentation: None,
6369 source: CompletionSource::BufferWord {
6370 word_range,
6371 resolved: false,
6372 },
6373 insert_text_mode: Some(InsertTextMode::AS_IS),
6374 confirm: None,
6375 }));
6376
6377 completions.extend(
6378 snippets
6379 .await
6380 .into_iter()
6381 .flat_map(|response| response.completions),
6382 );
6383
6384 let menu = if completions.is_empty() {
6385 None
6386 } else {
6387 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
6388 let languages = editor
6389 .workspace
6390 .as_ref()
6391 .and_then(|(workspace, _)| workspace.upgrade())
6392 .map(|workspace| workspace.read(cx).app_state().languages.clone());
6393 let menu = CompletionsMenu::new(
6394 id,
6395 requested_source.unwrap_or(if load_provider_completions {
6396 CompletionsMenuSource::Normal
6397 } else {
6398 CompletionsMenuSource::SnippetsOnly
6399 }),
6400 sort_completions,
6401 show_completion_documentation,
6402 position,
6403 query.clone(),
6404 is_incomplete,
6405 buffer.clone(),
6406 completions.into(),
6407 editor
6408 .context_menu()
6409 .borrow_mut()
6410 .as_ref()
6411 .map(|menu| menu.primary_scroll_handle()),
6412 display_options,
6413 snippet_sort_order,
6414 languages,
6415 language,
6416 cx,
6417 );
6418
6419 let query = if filter_completions { query } else { None };
6420 let matches_task = menu.do_async_filtering(
6421 query.unwrap_or_default(),
6422 buffer_position,
6423 &buffer,
6424 cx,
6425 );
6426 (menu, matches_task)
6427 }) else {
6428 return;
6429 };
6430
6431 let matches = matches_task.await;
6432
6433 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
6434 // Newer menu already set, so exit.
6435 if let Some(CodeContextMenu::Completions(prev_menu)) =
6436 editor.context_menu.borrow().as_ref()
6437 && prev_menu.id > id
6438 {
6439 return;
6440 };
6441
6442 // Only valid to take prev_menu because either the new menu is immediately set
6443 // below, or the menu is hidden.
6444 if let Some(CodeContextMenu::Completions(prev_menu)) =
6445 editor.context_menu.borrow_mut().take()
6446 {
6447 let position_matches =
6448 if prev_menu.initial_position == menu.initial_position {
6449 true
6450 } else {
6451 let snapshot = editor.buffer.read(cx).read(cx);
6452 prev_menu.initial_position.to_offset(&snapshot)
6453 == menu.initial_position.to_offset(&snapshot)
6454 };
6455 if position_matches {
6456 // Preserve markdown cache before `set_filter_results` because it will
6457 // try to populate the documentation cache.
6458 menu.preserve_markdown_cache(prev_menu);
6459 }
6460 };
6461
6462 menu.set_filter_results(matches, provider, window, cx);
6463 }) else {
6464 return;
6465 };
6466
6467 menu.visible().then_some(menu)
6468 };
6469
6470 editor
6471 .update_in(cx, |editor, window, cx| {
6472 if editor.focus_handle.is_focused(window)
6473 && let Some(menu) = menu
6474 {
6475 *editor.context_menu.borrow_mut() =
6476 Some(CodeContextMenu::Completions(menu));
6477
6478 crate::hover_popover::hide_hover(editor, cx);
6479 if editor.show_edit_predictions_in_menu() {
6480 editor.update_visible_edit_prediction(window, cx);
6481 } else {
6482 editor
6483 .discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
6484 }
6485
6486 cx.notify();
6487 return;
6488 }
6489
6490 if editor.completion_tasks.len() <= 1 {
6491 // If there are no more completion tasks and the last menu was empty, we should hide it.
6492 let was_hidden = editor.hide_context_menu(window, cx).is_none();
6493 // If it was already hidden and we don't show edit predictions in the menu,
6494 // we should also show the edit prediction when available.
6495 if was_hidden && editor.show_edit_predictions_in_menu() {
6496 editor.update_visible_edit_prediction(window, cx);
6497 }
6498 }
6499 })
6500 .ok();
6501 });
6502
6503 self.completion_tasks.push((id, task));
6504 }
6505
6506 #[cfg(any(test, feature = "test-support"))]
6507 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
6508 let menu = self.context_menu.borrow();
6509 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
6510 let completions = menu.completions.borrow();
6511 Some(completions.to_vec())
6512 } else {
6513 None
6514 }
6515 }
6516
6517 pub fn with_completions_menu_matching_id<R>(
6518 &self,
6519 id: CompletionId,
6520 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
6521 ) -> R {
6522 let mut context_menu = self.context_menu.borrow_mut();
6523 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
6524 return f(None);
6525 };
6526 if completions_menu.id != id {
6527 return f(None);
6528 }
6529 f(Some(completions_menu))
6530 }
6531
6532 pub fn confirm_completion(
6533 &mut self,
6534 action: &ConfirmCompletion,
6535 window: &mut Window,
6536 cx: &mut Context<Self>,
6537 ) -> Option<Task<Result<()>>> {
6538 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6539 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
6540 }
6541
6542 pub fn confirm_completion_insert(
6543 &mut self,
6544 _: &ConfirmCompletionInsert,
6545 window: &mut Window,
6546 cx: &mut Context<Self>,
6547 ) -> Option<Task<Result<()>>> {
6548 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6549 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
6550 }
6551
6552 pub fn confirm_completion_replace(
6553 &mut self,
6554 _: &ConfirmCompletionReplace,
6555 window: &mut Window,
6556 cx: &mut Context<Self>,
6557 ) -> Option<Task<Result<()>>> {
6558 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6559 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
6560 }
6561
6562 pub fn compose_completion(
6563 &mut self,
6564 action: &ComposeCompletion,
6565 window: &mut Window,
6566 cx: &mut Context<Self>,
6567 ) -> Option<Task<Result<()>>> {
6568 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6569 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
6570 }
6571
6572 fn do_completion(
6573 &mut self,
6574 item_ix: Option<usize>,
6575 intent: CompletionIntent,
6576 window: &mut Window,
6577 cx: &mut Context<Editor>,
6578 ) -> Option<Task<Result<()>>> {
6579 use language::ToOffset as _;
6580
6581 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
6582 else {
6583 return None;
6584 };
6585
6586 let candidate_id = {
6587 let entries = completions_menu.entries.borrow();
6588 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6589 if self.show_edit_predictions_in_menu() {
6590 self.discard_edit_prediction(EditPredictionDiscardReason::Rejected, cx);
6591 }
6592 mat.candidate_id
6593 };
6594
6595 let completion = completions_menu
6596 .completions
6597 .borrow()
6598 .get(candidate_id)?
6599 .clone();
6600 cx.stop_propagation();
6601
6602 let buffer_handle = completions_menu.buffer.clone();
6603
6604 let CompletionEdit {
6605 new_text,
6606 snippet,
6607 replace_range,
6608 } = process_completion_for_edit(
6609 &completion,
6610 intent,
6611 &buffer_handle,
6612 &completions_menu.initial_position.text_anchor,
6613 cx,
6614 );
6615
6616 let buffer = buffer_handle.read(cx);
6617 let snapshot = self.buffer.read(cx).snapshot(cx);
6618 let newest_anchor = self.selections.newest_anchor();
6619 let replace_range_multibuffer = {
6620 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6621 excerpt.map_range_from_buffer(replace_range.clone())
6622 };
6623 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6624 return None;
6625 }
6626
6627 let old_text = buffer
6628 .text_for_range(replace_range.clone())
6629 .collect::<String>();
6630 let lookbehind = newest_anchor
6631 .start
6632 .text_anchor
6633 .to_offset(buffer)
6634 .saturating_sub(replace_range.start.0);
6635 let lookahead = replace_range
6636 .end
6637 .0
6638 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6639 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6640 let suffix = &old_text[lookbehind.min(old_text.len())..];
6641
6642 let selections = self
6643 .selections
6644 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6645 let mut ranges = Vec::new();
6646 let mut all_commit_ranges = Vec::new();
6647 let mut linked_edits = LinkedEdits::new();
6648
6649 let text: Arc<str> = new_text.clone().into();
6650 for selection in &selections {
6651 let range = if selection.id == newest_anchor.id {
6652 replace_range_multibuffer.clone()
6653 } else {
6654 let mut range = selection.range();
6655
6656 // if prefix is present, don't duplicate it
6657 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6658 range.start = range.start.saturating_sub_usize(lookbehind);
6659
6660 // if suffix is also present, mimic the newest cursor and replace it
6661 if selection.id != newest_anchor.id
6662 && snapshot.contains_str_at(range.end, suffix)
6663 {
6664 range.end += lookahead;
6665 }
6666 }
6667 range
6668 };
6669
6670 ranges.push(range.clone());
6671
6672 let start_anchor = snapshot.anchor_before(range.start);
6673 let end_anchor = snapshot.anchor_after(range.end);
6674 let anchor_range = start_anchor.text_anchor..end_anchor.text_anchor;
6675 all_commit_ranges.push(anchor_range.clone());
6676
6677 if !self.linked_edit_ranges.is_empty() {
6678 linked_edits.push(&self, anchor_range, text.clone(), cx);
6679 }
6680 }
6681
6682 let common_prefix_len = old_text
6683 .chars()
6684 .zip(new_text.chars())
6685 .take_while(|(a, b)| a == b)
6686 .map(|(a, _)| a.len_utf8())
6687 .sum::<usize>();
6688
6689 cx.emit(EditorEvent::InputHandled {
6690 utf16_range_to_replace: None,
6691 text: new_text[common_prefix_len..].into(),
6692 });
6693
6694 self.transact(window, cx, |editor, window, cx| {
6695 if let Some(mut snippet) = snippet {
6696 snippet.text = new_text.to_string();
6697 editor
6698 .insert_snippet(&ranges, snippet, window, cx)
6699 .log_err();
6700 } else {
6701 editor.buffer.update(cx, |multi_buffer, cx| {
6702 let auto_indent = match completion.insert_text_mode {
6703 Some(InsertTextMode::AS_IS) => None,
6704 _ => editor.autoindent_mode.clone(),
6705 };
6706 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6707 multi_buffer.edit(edits, auto_indent, cx);
6708 });
6709 }
6710 linked_edits.apply(cx);
6711 editor.refresh_edit_prediction(true, false, window, cx);
6712 });
6713 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6714
6715 let show_new_completions_on_confirm = completion
6716 .confirm
6717 .as_ref()
6718 .is_some_and(|confirm| confirm(intent, window, cx));
6719 if show_new_completions_on_confirm {
6720 self.open_or_update_completions_menu(None, None, false, window, cx);
6721 }
6722
6723 let provider = self.completion_provider.as_ref()?;
6724
6725 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6726 let command = lsp_store.as_ref().and_then(|lsp_store| {
6727 let CompletionSource::Lsp {
6728 lsp_completion,
6729 server_id,
6730 ..
6731 } = &completion.source
6732 else {
6733 return None;
6734 };
6735 let lsp_command = lsp_completion.command.as_ref()?;
6736 let available_commands = lsp_store
6737 .read(cx)
6738 .lsp_server_capabilities
6739 .get(server_id)
6740 .and_then(|server_capabilities| {
6741 server_capabilities
6742 .execute_command_provider
6743 .as_ref()
6744 .map(|options| options.commands.as_slice())
6745 })?;
6746 if available_commands.contains(&lsp_command.command) {
6747 Some(CodeAction {
6748 server_id: *server_id,
6749 range: language::Anchor::MIN..language::Anchor::MIN,
6750 lsp_action: LspAction::Command(lsp_command.clone()),
6751 resolved: false,
6752 })
6753 } else {
6754 None
6755 }
6756 });
6757
6758 drop(completion);
6759 let apply_edits = provider.apply_additional_edits_for_completion(
6760 buffer_handle.clone(),
6761 completions_menu.completions.clone(),
6762 candidate_id,
6763 true,
6764 all_commit_ranges,
6765 cx,
6766 );
6767
6768 let editor_settings = EditorSettings::get_global(cx);
6769 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6770 // After the code completion is finished, users often want to know what signatures are needed.
6771 // so we should automatically call signature_help
6772 self.show_signature_help(&ShowSignatureHelp, window, cx);
6773 }
6774
6775 Some(cx.spawn_in(window, async move |editor, cx| {
6776 apply_edits.await?;
6777
6778 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6779 let title = command.lsp_action.title().to_owned();
6780 let project_transaction = lsp_store
6781 .update(cx, |lsp_store, cx| {
6782 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6783 })
6784 .await
6785 .context("applying post-completion command")?;
6786 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6787 Self::open_project_transaction(
6788 &editor,
6789 workspace.downgrade(),
6790 project_transaction,
6791 title,
6792 cx,
6793 )
6794 .await?;
6795 }
6796 }
6797
6798 Ok(())
6799 }))
6800 }
6801
6802 pub fn toggle_code_actions(
6803 &mut self,
6804 action: &ToggleCodeActions,
6805 window: &mut Window,
6806 cx: &mut Context<Self>,
6807 ) {
6808 let quick_launch = action.quick_launch;
6809 let mut context_menu = self.context_menu.borrow_mut();
6810 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6811 if code_actions.deployed_from == action.deployed_from {
6812 // Toggle if we're selecting the same one
6813 *context_menu = None;
6814 cx.notify();
6815 return;
6816 } else {
6817 // Otherwise, clear it and start a new one
6818 *context_menu = None;
6819 cx.notify();
6820 }
6821 }
6822 drop(context_menu);
6823 let snapshot = self.snapshot(window, cx);
6824 let deployed_from = action.deployed_from.clone();
6825 let action = action.clone();
6826 self.completion_tasks.clear();
6827 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
6828
6829 let multibuffer_point = match &action.deployed_from {
6830 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6831 DisplayPoint::new(*row, 0).to_point(&snapshot)
6832 }
6833 _ => self
6834 .selections
6835 .newest::<Point>(&snapshot.display_snapshot)
6836 .head(),
6837 };
6838 let Some((buffer, buffer_row)) = snapshot
6839 .buffer_snapshot()
6840 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6841 .and_then(|(buffer_snapshot, range)| {
6842 self.buffer()
6843 .read(cx)
6844 .buffer(buffer_snapshot.remote_id())
6845 .map(|buffer| (buffer, range.start.row))
6846 })
6847 else {
6848 return;
6849 };
6850 let buffer_id = buffer.read(cx).remote_id();
6851 let tasks = self
6852 .runnables
6853 .runnables((buffer_id, buffer_row))
6854 .map(|t| Arc::new(t.to_owned()));
6855
6856 if !self.focus_handle.is_focused(window) {
6857 return;
6858 }
6859 let project = self.project.clone();
6860
6861 let code_actions_task = match deployed_from {
6862 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6863 _ => self.code_actions(buffer_row, window, cx),
6864 };
6865
6866 let runnable_task = match deployed_from {
6867 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6868 _ => {
6869 let mut task_context_task = Task::ready(None);
6870 if let Some(tasks) = &tasks
6871 && let Some(project) = project
6872 {
6873 task_context_task =
6874 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6875 }
6876
6877 cx.spawn_in(window, {
6878 let buffer = buffer.clone();
6879 async move |editor, cx| {
6880 let task_context = task_context_task.await;
6881
6882 let resolved_tasks =
6883 tasks
6884 .zip(task_context.clone())
6885 .map(|(tasks, task_context)| ResolvedTasks {
6886 templates: tasks.resolve(&task_context).collect(),
6887 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6888 multibuffer_point.row,
6889 tasks.column,
6890 )),
6891 });
6892 let debug_scenarios = editor
6893 .update(cx, |editor, cx| {
6894 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6895 })?
6896 .await;
6897 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6898 }
6899 })
6900 }
6901 };
6902
6903 cx.spawn_in(window, async move |editor, cx| {
6904 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6905 let code_actions = code_actions_task.await;
6906 let spawn_straight_away = quick_launch
6907 && resolved_tasks
6908 .as_ref()
6909 .is_some_and(|tasks| tasks.templates.len() == 1)
6910 && code_actions
6911 .as_ref()
6912 .is_none_or(|actions| actions.is_empty())
6913 && debug_scenarios.is_empty();
6914
6915 editor.update_in(cx, |editor, window, cx| {
6916 crate::hover_popover::hide_hover(editor, cx);
6917 let actions = CodeActionContents::new(
6918 resolved_tasks,
6919 code_actions,
6920 debug_scenarios,
6921 task_context.unwrap_or_default(),
6922 );
6923
6924 // Don't show the menu if there are no actions available
6925 if actions.is_empty() {
6926 cx.notify();
6927 return Task::ready(Ok(()));
6928 }
6929
6930 *editor.context_menu.borrow_mut() =
6931 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6932 buffer,
6933 actions,
6934 selected_item: Default::default(),
6935 scroll_handle: UniformListScrollHandle::default(),
6936 deployed_from,
6937 }));
6938 cx.notify();
6939 if spawn_straight_away
6940 && let Some(task) = editor.confirm_code_action(
6941 &ConfirmCodeAction { item_ix: Some(0) },
6942 window,
6943 cx,
6944 )
6945 {
6946 return task;
6947 }
6948
6949 Task::ready(Ok(()))
6950 })
6951 })
6952 .detach_and_log_err(cx);
6953 }
6954
6955 fn debug_scenarios(
6956 &mut self,
6957 resolved_tasks: &Option<ResolvedTasks>,
6958 buffer: &Entity<Buffer>,
6959 cx: &mut App,
6960 ) -> Task<Vec<task::DebugScenario>> {
6961 maybe!({
6962 let project = self.project()?;
6963 let dap_store = project.read(cx).dap_store();
6964 let mut scenarios = vec![];
6965 let resolved_tasks = resolved_tasks.as_ref()?;
6966 let buffer = buffer.read(cx);
6967 let language = buffer.language()?;
6968 let file = buffer.file();
6969 let debug_adapter = language_settings(language.name().into(), file, cx)
6970 .debuggers
6971 .first()
6972 .map(SharedString::from)
6973 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6974
6975 dap_store.update(cx, |dap_store, cx| {
6976 for (_, task) in &resolved_tasks.templates {
6977 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6978 task.original_task().clone(),
6979 debug_adapter.clone().into(),
6980 task.display_label().to_owned().into(),
6981 cx,
6982 );
6983 scenarios.push(maybe_scenario);
6984 }
6985 });
6986 Some(cx.background_spawn(async move {
6987 futures::future::join_all(scenarios)
6988 .await
6989 .into_iter()
6990 .flatten()
6991 .collect::<Vec<_>>()
6992 }))
6993 })
6994 .unwrap_or_else(|| Task::ready(vec![]))
6995 }
6996
6997 fn code_actions(
6998 &mut self,
6999 buffer_row: u32,
7000 window: &mut Window,
7001 cx: &mut Context<Self>,
7002 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
7003 let mut task = self.code_actions_task.take();
7004 cx.spawn_in(window, async move |editor, cx| {
7005 while let Some(prev_task) = task {
7006 prev_task.await.log_err();
7007 task = editor
7008 .update(cx, |this, _| this.code_actions_task.take())
7009 .ok()?;
7010 }
7011
7012 editor
7013 .update(cx, |editor, cx| {
7014 editor
7015 .available_code_actions
7016 .clone()
7017 .and_then(|(location, code_actions)| {
7018 let snapshot = location.buffer.read(cx).snapshot();
7019 let point_range = location.range.to_point(&snapshot);
7020 let point_range = point_range.start.row..=point_range.end.row;
7021 if point_range.contains(&buffer_row) {
7022 Some(code_actions)
7023 } else {
7024 None
7025 }
7026 })
7027 })
7028 .ok()
7029 .flatten()
7030 })
7031 }
7032
7033 pub fn confirm_code_action(
7034 &mut self,
7035 action: &ConfirmCodeAction,
7036 window: &mut Window,
7037 cx: &mut Context<Self>,
7038 ) -> Option<Task<Result<()>>> {
7039 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
7040
7041 let actions_menu =
7042 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
7043 menu
7044 } else {
7045 return None;
7046 };
7047
7048 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
7049 let action = actions_menu.actions.get(action_ix)?;
7050 let title = action.label();
7051 let buffer = actions_menu.buffer;
7052 let workspace = self.workspace()?;
7053
7054 match action {
7055 CodeActionsItem::Task(task_source_kind, resolved_task) => {
7056 workspace.update(cx, |workspace, cx| {
7057 workspace.schedule_resolved_task(
7058 task_source_kind,
7059 resolved_task,
7060 false,
7061 window,
7062 cx,
7063 );
7064
7065 Some(Task::ready(Ok(())))
7066 })
7067 }
7068 CodeActionsItem::CodeAction {
7069 excerpt_id,
7070 action,
7071 provider,
7072 } => {
7073 let apply_code_action =
7074 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
7075 let workspace = workspace.downgrade();
7076 Some(cx.spawn_in(window, async move |editor, cx| {
7077 let project_transaction = apply_code_action.await?;
7078 Self::open_project_transaction(
7079 &editor,
7080 workspace,
7081 project_transaction,
7082 title,
7083 cx,
7084 )
7085 .await
7086 }))
7087 }
7088 CodeActionsItem::DebugScenario(scenario) => {
7089 let context = actions_menu.actions.context.into();
7090
7091 workspace.update(cx, |workspace, cx| {
7092 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
7093 workspace.start_debug_session(
7094 scenario,
7095 context,
7096 Some(buffer),
7097 None,
7098 window,
7099 cx,
7100 );
7101 });
7102 Some(Task::ready(Ok(())))
7103 }
7104 }
7105 }
7106
7107 fn open_transaction_for_hidden_buffers(
7108 workspace: Entity<Workspace>,
7109 transaction: ProjectTransaction,
7110 title: String,
7111 window: &mut Window,
7112 cx: &mut Context<Self>,
7113 ) {
7114 if transaction.0.is_empty() {
7115 return;
7116 }
7117
7118 let edited_buffers_already_open = {
7119 let other_editors: Vec<Entity<Editor>> = workspace
7120 .read(cx)
7121 .panes()
7122 .iter()
7123 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
7124 .filter(|editor| editor.entity_id() != cx.entity_id())
7125 .collect();
7126
7127 transaction.0.keys().all(|buffer| {
7128 other_editors.iter().any(|editor| {
7129 let multi_buffer = editor.read(cx).buffer();
7130 multi_buffer.read(cx).is_singleton()
7131 && multi_buffer
7132 .read(cx)
7133 .as_singleton()
7134 .map_or(false, |singleton| {
7135 singleton.entity_id() == buffer.entity_id()
7136 })
7137 })
7138 })
7139 };
7140 if !edited_buffers_already_open {
7141 let workspace = workspace.downgrade();
7142 cx.defer_in(window, move |_, window, cx| {
7143 cx.spawn_in(window, async move |editor, cx| {
7144 Self::open_project_transaction(&editor, workspace, transaction, title, cx)
7145 .await
7146 .ok()
7147 })
7148 .detach();
7149 });
7150 }
7151 }
7152
7153 pub async fn open_project_transaction(
7154 editor: &WeakEntity<Editor>,
7155 workspace: WeakEntity<Workspace>,
7156 transaction: ProjectTransaction,
7157 title: String,
7158 cx: &mut AsyncWindowContext,
7159 ) -> Result<()> {
7160 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
7161 cx.update(|_, cx| {
7162 entries.sort_unstable_by_key(|(buffer, _)| {
7163 buffer.read(cx).file().map(|f| f.path().clone())
7164 });
7165 })?;
7166 if entries.is_empty() {
7167 return Ok(());
7168 }
7169
7170 // If the project transaction's edits are all contained within this editor, then
7171 // avoid opening a new editor to display them.
7172
7173 if let [(buffer, transaction)] = &*entries {
7174 let excerpt = editor.update(cx, |editor, cx| {
7175 editor
7176 .buffer()
7177 .read(cx)
7178 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
7179 })?;
7180 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
7181 && excerpted_buffer == *buffer
7182 {
7183 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
7184 let excerpt_range = excerpt_range.to_offset(buffer);
7185 buffer
7186 .edited_ranges_for_transaction::<usize>(transaction)
7187 .all(|range| {
7188 excerpt_range.start <= range.start && excerpt_range.end >= range.end
7189 })
7190 });
7191
7192 if all_edits_within_excerpt {
7193 return Ok(());
7194 }
7195 }
7196 }
7197
7198 let mut ranges_to_highlight = Vec::new();
7199 let excerpt_buffer = cx.new(|cx| {
7200 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
7201 for (buffer_handle, transaction) in &entries {
7202 let edited_ranges = buffer_handle
7203 .read(cx)
7204 .edited_ranges_for_transaction::<Point>(transaction)
7205 .collect::<Vec<_>>();
7206 let (ranges, _) = multibuffer.set_excerpts_for_path(
7207 PathKey::for_buffer(buffer_handle, cx),
7208 buffer_handle.clone(),
7209 edited_ranges,
7210 multibuffer_context_lines(cx),
7211 cx,
7212 );
7213
7214 ranges_to_highlight.extend(ranges);
7215 }
7216 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
7217 multibuffer
7218 });
7219
7220 workspace.update_in(cx, |workspace, window, cx| {
7221 let project = workspace.project().clone();
7222 let editor =
7223 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
7224 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
7225 editor.update(cx, |editor, cx| {
7226 editor.highlight_background(
7227 HighlightKey::Editor,
7228 &ranges_to_highlight,
7229 |_, theme| theme.colors().editor_highlighted_line_background,
7230 cx,
7231 );
7232 });
7233 })?;
7234
7235 Ok(())
7236 }
7237
7238 pub fn clear_code_action_providers(&mut self) {
7239 self.code_action_providers.clear();
7240 self.available_code_actions.take();
7241 }
7242
7243 pub fn add_code_action_provider(
7244 &mut self,
7245 provider: Rc<dyn CodeActionProvider>,
7246 window: &mut Window,
7247 cx: &mut Context<Self>,
7248 ) {
7249 if self
7250 .code_action_providers
7251 .iter()
7252 .any(|existing_provider| existing_provider.id() == provider.id())
7253 {
7254 return;
7255 }
7256
7257 self.code_action_providers.push(provider);
7258 self.refresh_code_actions(window, cx);
7259 }
7260
7261 pub fn remove_code_action_provider(
7262 &mut self,
7263 id: Arc<str>,
7264 window: &mut Window,
7265 cx: &mut Context<Self>,
7266 ) {
7267 self.code_action_providers
7268 .retain(|provider| provider.id() != id);
7269 self.refresh_code_actions(window, cx);
7270 }
7271
7272 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
7273 !self.code_action_providers.is_empty()
7274 && EditorSettings::get_global(cx).toolbar.code_actions
7275 }
7276
7277 pub fn has_available_code_actions(&self) -> bool {
7278 self.available_code_actions
7279 .as_ref()
7280 .is_some_and(|(_, actions)| !actions.is_empty())
7281 }
7282
7283 fn render_inline_code_actions(
7284 &self,
7285 icon_size: ui::IconSize,
7286 display_row: DisplayRow,
7287 is_active: bool,
7288 cx: &mut Context<Self>,
7289 ) -> AnyElement {
7290 let show_tooltip = !self.context_menu_visible();
7291 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
7292 .icon_size(icon_size)
7293 .shape(ui::IconButtonShape::Square)
7294 .icon_color(ui::Color::Hidden)
7295 .toggle_state(is_active)
7296 .when(show_tooltip, |this| {
7297 this.tooltip({
7298 let focus_handle = self.focus_handle.clone();
7299 move |_window, cx| {
7300 Tooltip::for_action_in(
7301 "Toggle Code Actions",
7302 &ToggleCodeActions {
7303 deployed_from: None,
7304 quick_launch: false,
7305 },
7306 &focus_handle,
7307 cx,
7308 )
7309 }
7310 })
7311 })
7312 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
7313 window.focus(&editor.focus_handle(cx), cx);
7314 editor.toggle_code_actions(
7315 &crate::actions::ToggleCodeActions {
7316 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
7317 display_row,
7318 )),
7319 quick_launch: false,
7320 },
7321 window,
7322 cx,
7323 );
7324 }))
7325 .into_any_element()
7326 }
7327
7328 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
7329 &self.context_menu
7330 }
7331
7332 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7333 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
7334 cx.background_executor()
7335 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
7336 .await;
7337
7338 let (start_buffer, start, _, end, newest_selection) = this
7339 .update(cx, |this, cx| {
7340 let newest_selection = this.selections.newest_anchor().clone();
7341 if newest_selection.head().diff_base_anchor.is_some() {
7342 return None;
7343 }
7344 let display_snapshot = this.display_snapshot(cx);
7345 let newest_selection_adjusted =
7346 this.selections.newest_adjusted(&display_snapshot);
7347 let buffer = this.buffer.read(cx);
7348
7349 let (start_buffer, start) =
7350 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
7351 let (end_buffer, end) =
7352 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
7353
7354 Some((start_buffer, start, end_buffer, end, newest_selection))
7355 })?
7356 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
7357 .context(
7358 "Expected selection to lie in a single buffer when refreshing code actions",
7359 )?;
7360 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
7361 let providers = this.code_action_providers.clone();
7362 let tasks = this
7363 .code_action_providers
7364 .iter()
7365 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
7366 .collect::<Vec<_>>();
7367 (providers, tasks)
7368 })?;
7369
7370 let mut actions = Vec::new();
7371 for (provider, provider_actions) in
7372 providers.into_iter().zip(future::join_all(tasks).await)
7373 {
7374 if let Some(provider_actions) = provider_actions.log_err() {
7375 actions.extend(provider_actions.into_iter().map(|action| {
7376 AvailableCodeAction {
7377 excerpt_id: newest_selection.start.excerpt_id,
7378 action,
7379 provider: provider.clone(),
7380 }
7381 }));
7382 }
7383 }
7384
7385 this.update(cx, |this, cx| {
7386 this.available_code_actions = if actions.is_empty() {
7387 None
7388 } else {
7389 Some((
7390 Location {
7391 buffer: start_buffer,
7392 range: start..end,
7393 },
7394 actions.into(),
7395 ))
7396 };
7397 cx.notify();
7398 })
7399 }));
7400 }
7401
7402 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7403 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
7404 self.show_git_blame_inline = false;
7405
7406 self.show_git_blame_inline_delay_task =
7407 Some(cx.spawn_in(window, async move |this, cx| {
7408 cx.background_executor().timer(delay).await;
7409
7410 this.update(cx, |this, cx| {
7411 this.show_git_blame_inline = true;
7412 cx.notify();
7413 })
7414 .log_err();
7415 }));
7416 }
7417 }
7418
7419 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
7420 let snapshot = self.snapshot(window, cx);
7421 let cursor = self
7422 .selections
7423 .newest::<Point>(&snapshot.display_snapshot)
7424 .head();
7425 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
7426 else {
7427 return;
7428 };
7429
7430 if self.blame.is_none() {
7431 self.start_git_blame(true, window, cx);
7432 }
7433 let Some(blame) = self.blame.as_ref() else {
7434 return;
7435 };
7436
7437 let row_info = RowInfo {
7438 buffer_id: Some(buffer.remote_id()),
7439 buffer_row: Some(point.row),
7440 ..Default::default()
7441 };
7442 let Some((buffer, blame_entry)) = blame
7443 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
7444 .flatten()
7445 else {
7446 return;
7447 };
7448
7449 let anchor = self.selections.newest_anchor().head();
7450 let position = self.to_pixel_point(anchor, &snapshot, window, cx);
7451 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
7452 self.show_blame_popover(
7453 buffer,
7454 &blame_entry,
7455 position + last_bounds.origin,
7456 true,
7457 cx,
7458 );
7459 };
7460 }
7461
7462 fn show_blame_popover(
7463 &mut self,
7464 buffer: BufferId,
7465 blame_entry: &BlameEntry,
7466 position: gpui::Point<Pixels>,
7467 ignore_timeout: bool,
7468 cx: &mut Context<Self>,
7469 ) {
7470 if let Some(state) = &mut self.inline_blame_popover {
7471 state.hide_task.take();
7472 } else {
7473 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
7474 let blame_entry = blame_entry.clone();
7475 let show_task = cx.spawn(async move |editor, cx| {
7476 if !ignore_timeout {
7477 cx.background_executor()
7478 .timer(std::time::Duration::from_millis(blame_popover_delay))
7479 .await;
7480 }
7481 editor
7482 .update(cx, |editor, cx| {
7483 editor.inline_blame_popover_show_task.take();
7484 let Some(blame) = editor.blame.as_ref() else {
7485 return;
7486 };
7487 let blame = blame.read(cx);
7488 let details = blame.details_for_entry(buffer, &blame_entry);
7489 let markdown = cx.new(|cx| {
7490 Markdown::new(
7491 details
7492 .as_ref()
7493 .map(|message| message.message.clone())
7494 .unwrap_or_default(),
7495 None,
7496 None,
7497 cx,
7498 )
7499 });
7500 editor.inline_blame_popover = Some(InlineBlamePopover {
7501 position,
7502 hide_task: None,
7503 popover_bounds: None,
7504 popover_state: InlineBlamePopoverState {
7505 scroll_handle: ScrollHandle::new(),
7506 commit_message: details,
7507 markdown,
7508 },
7509 keyboard_grace: ignore_timeout,
7510 });
7511 cx.notify();
7512 })
7513 .ok();
7514 });
7515 self.inline_blame_popover_show_task = Some(show_task);
7516 }
7517 }
7518
7519 pub fn has_mouse_context_menu(&self) -> bool {
7520 self.mouse_context_menu.is_some()
7521 }
7522
7523 pub fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
7524 self.inline_blame_popover_show_task.take();
7525 if let Some(state) = &mut self.inline_blame_popover {
7526 let hide_task = cx.spawn(async move |editor, cx| {
7527 if !ignore_timeout {
7528 cx.background_executor()
7529 .timer(std::time::Duration::from_millis(100))
7530 .await;
7531 }
7532 editor
7533 .update(cx, |editor, cx| {
7534 editor.inline_blame_popover.take();
7535 cx.notify();
7536 })
7537 .ok();
7538 });
7539 state.hide_task = Some(hide_task);
7540 true
7541 } else {
7542 false
7543 }
7544 }
7545
7546 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
7547 if self.pending_rename.is_some() {
7548 return None;
7549 }
7550
7551 let provider = self.semantics_provider.clone()?;
7552 let buffer = self.buffer.read(cx);
7553 let newest_selection = self.selections.newest_anchor().clone();
7554 let cursor_position = newest_selection.head();
7555 let (cursor_buffer, cursor_buffer_position) =
7556 buffer.text_anchor_for_position(cursor_position, cx)?;
7557 let (tail_buffer, tail_buffer_position) =
7558 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
7559 if cursor_buffer != tail_buffer {
7560 return None;
7561 }
7562
7563 let snapshot = cursor_buffer.read(cx).snapshot();
7564 let word_ranges = cx.background_spawn(async move {
7565 // this might look odd to put on the background thread, but
7566 // `surrounding_word` can be quite expensive as it calls into
7567 // tree-sitter language scopes
7568 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
7569 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
7570 (start_word_range, end_word_range)
7571 });
7572
7573 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
7574 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
7575 let (start_word_range, end_word_range) = word_ranges.await;
7576 if start_word_range != end_word_range {
7577 this.update(cx, |this, cx| {
7578 this.document_highlights_task.take();
7579 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
7580 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
7581 })
7582 .ok();
7583 return;
7584 }
7585 cx.background_executor()
7586 .timer(Duration::from_millis(debounce))
7587 .await;
7588
7589 let highlights = if let Some(highlights) = cx.update(|cx| {
7590 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
7591 }) {
7592 highlights.await.log_err()
7593 } else {
7594 None
7595 };
7596
7597 if let Some(highlights) = highlights {
7598 this.update(cx, |this, cx| {
7599 if this.pending_rename.is_some() {
7600 return;
7601 }
7602
7603 let buffer = this.buffer.read(cx);
7604 if buffer
7605 .text_anchor_for_position(cursor_position, cx)
7606 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
7607 {
7608 return;
7609 }
7610
7611 let cursor_buffer_snapshot = cursor_buffer.read(cx);
7612 let mut write_ranges = Vec::new();
7613 let mut read_ranges = Vec::new();
7614 for highlight in highlights {
7615 let buffer_id = cursor_buffer.read(cx).remote_id();
7616 for (excerpt_id, _, excerpt_range) in
7617 buffer.excerpts_for_buffer(buffer_id, cx)
7618 {
7619 let start = highlight
7620 .range
7621 .start
7622 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
7623 let end = highlight
7624 .range
7625 .end
7626 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
7627 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
7628 continue;
7629 }
7630
7631 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
7632 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7633 write_ranges.push(range);
7634 } else {
7635 read_ranges.push(range);
7636 }
7637 }
7638 }
7639
7640 this.highlight_background(
7641 HighlightKey::DocumentHighlightRead,
7642 &read_ranges,
7643 |_, theme| theme.colors().editor_document_highlight_read_background,
7644 cx,
7645 );
7646 this.highlight_background(
7647 HighlightKey::DocumentHighlightWrite,
7648 &write_ranges,
7649 |_, theme| theme.colors().editor_document_highlight_write_background,
7650 cx,
7651 );
7652 cx.notify();
7653 })
7654 .log_err();
7655 }
7656 }));
7657 None
7658 }
7659
7660 fn prepare_highlight_query_from_selection(
7661 &mut self,
7662 snapshot: &DisplaySnapshot,
7663 cx: &mut Context<Editor>,
7664 ) -> Option<(String, Range<Anchor>)> {
7665 if matches!(self.mode, EditorMode::SingleLine) {
7666 return None;
7667 }
7668 if !EditorSettings::get_global(cx).selection_highlight {
7669 return None;
7670 }
7671 if self.selections.count() != 1 || self.selections.line_mode() {
7672 return None;
7673 }
7674 let selection = self.selections.newest::<Point>(&snapshot);
7675 // If the selection spans multiple rows OR it is empty
7676 if selection.start.row != selection.end.row
7677 || selection.start.column == selection.end.column
7678 {
7679 return None;
7680 }
7681 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7682 let query = snapshot
7683 .buffer_snapshot()
7684 .text_for_range(selection_anchor_range.clone())
7685 .collect::<String>();
7686 if query.trim().is_empty() {
7687 return None;
7688 }
7689 Some((query, selection_anchor_range))
7690 }
7691
7692 #[ztracing::instrument(skip_all)]
7693 fn update_selection_occurrence_highlights(
7694 &mut self,
7695 multi_buffer_snapshot: MultiBufferSnapshot,
7696 query_text: String,
7697 query_range: Range<Anchor>,
7698 multi_buffer_range_to_query: Range<Point>,
7699 use_debounce: bool,
7700 window: &mut Window,
7701 cx: &mut Context<Editor>,
7702 ) -> Task<()> {
7703 cx.spawn_in(window, async move |editor, cx| {
7704 if use_debounce {
7705 cx.background_executor()
7706 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7707 .await;
7708 }
7709 let match_task = cx.background_spawn(async move {
7710 let buffer_ranges = multi_buffer_snapshot
7711 .range_to_buffer_ranges(
7712 multi_buffer_range_to_query.start..=multi_buffer_range_to_query.end,
7713 )
7714 .into_iter()
7715 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7716 let mut match_ranges = Vec::new();
7717 let Ok(regex) = project::search::SearchQuery::text(
7718 query_text,
7719 false,
7720 false,
7721 false,
7722 Default::default(),
7723 Default::default(),
7724 false,
7725 None,
7726 ) else {
7727 return Vec::default();
7728 };
7729 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7730 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7731 match_ranges.extend(
7732 regex
7733 .search(
7734 buffer_snapshot,
7735 Some(search_range.start.0..search_range.end.0),
7736 )
7737 .await
7738 .into_iter()
7739 .filter_map(|match_range| {
7740 let match_start = buffer_snapshot
7741 .anchor_after(search_range.start + match_range.start);
7742 let match_end = buffer_snapshot
7743 .anchor_before(search_range.start + match_range.end);
7744 let match_anchor_range =
7745 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7746 (match_anchor_range != query_range).then_some(match_anchor_range)
7747 }),
7748 );
7749 }
7750 match_ranges
7751 });
7752 let match_ranges = match_task.await;
7753 editor
7754 .update_in(cx, |editor, _, cx| {
7755 if use_debounce {
7756 editor.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
7757 editor.debounced_selection_highlight_complete = true;
7758 } else if editor.debounced_selection_highlight_complete {
7759 return;
7760 }
7761 if !match_ranges.is_empty() {
7762 editor.highlight_background(
7763 HighlightKey::SelectedTextHighlight,
7764 &match_ranges,
7765 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7766 cx,
7767 )
7768 }
7769 })
7770 .log_err();
7771 })
7772 }
7773
7774 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7775 struct NewlineFold;
7776 let type_id = std::any::TypeId::of::<NewlineFold>();
7777 if !self.mode.is_single_line() {
7778 return;
7779 }
7780 let snapshot = self.snapshot(window, cx);
7781 if snapshot.buffer_snapshot().max_point().row == 0 {
7782 return;
7783 }
7784 let task = cx.background_spawn(async move {
7785 let new_newlines = snapshot
7786 .buffer_chars_at(MultiBufferOffset(0))
7787 .filter_map(|(c, i)| {
7788 if c == '\n' {
7789 Some(
7790 snapshot.buffer_snapshot().anchor_after(i)
7791 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7792 )
7793 } else {
7794 None
7795 }
7796 })
7797 .collect::<Vec<_>>();
7798 let existing_newlines = snapshot
7799 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7800 .filter_map(|fold| {
7801 if fold.placeholder.type_tag == Some(type_id) {
7802 Some(fold.range.start..fold.range.end)
7803 } else {
7804 None
7805 }
7806 })
7807 .collect::<Vec<_>>();
7808
7809 (new_newlines, existing_newlines)
7810 });
7811 self.folding_newlines = cx.spawn(async move |this, cx| {
7812 let (new_newlines, existing_newlines) = task.await;
7813 if new_newlines == existing_newlines {
7814 return;
7815 }
7816 let placeholder = FoldPlaceholder {
7817 render: Arc::new(move |_, _, cx| {
7818 div()
7819 .bg(cx.theme().status().hint_background)
7820 .border_b_1()
7821 .size_full()
7822 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7823 .border_color(cx.theme().status().hint)
7824 .child("\\n")
7825 .into_any()
7826 }),
7827 constrain_width: false,
7828 merge_adjacent: false,
7829 type_tag: Some(type_id),
7830 collapsed_text: None,
7831 };
7832 let creases = new_newlines
7833 .into_iter()
7834 .map(|range| Crease::simple(range, placeholder.clone()))
7835 .collect();
7836 this.update(cx, |this, cx| {
7837 this.display_map.update(cx, |display_map, cx| {
7838 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7839 display_map.fold(creases, cx);
7840 });
7841 })
7842 .ok();
7843 });
7844 }
7845
7846 #[ztracing::instrument(skip_all)]
7847 fn refresh_outline_symbols_at_cursor(&mut self, cx: &mut Context<Editor>) {
7848 if !self.lsp_data_enabled() {
7849 return;
7850 }
7851 let cursor = self.selections.newest_anchor().head();
7852 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7853
7854 if self.uses_lsp_document_symbols(cursor, &multi_buffer_snapshot, cx) {
7855 self.outline_symbols_at_cursor =
7856 self.lsp_symbols_at_cursor(cursor, &multi_buffer_snapshot, cx);
7857 cx.emit(EditorEvent::OutlineSymbolsChanged);
7858 cx.notify();
7859 } else {
7860 let syntax = cx.theme().syntax().clone();
7861 let background_task = cx.background_spawn(async move {
7862 multi_buffer_snapshot.symbols_containing(cursor, Some(&syntax))
7863 });
7864 self.refresh_outline_symbols_at_cursor_at_cursor_task =
7865 cx.spawn(async move |this, cx| {
7866 let symbols = background_task.await;
7867 this.update(cx, |this, cx| {
7868 this.outline_symbols_at_cursor = symbols;
7869 cx.emit(EditorEvent::OutlineSymbolsChanged);
7870 cx.notify();
7871 })
7872 .ok();
7873 });
7874 }
7875 }
7876
7877 #[ztracing::instrument(skip_all)]
7878 fn refresh_selected_text_highlights(
7879 &mut self,
7880 snapshot: &DisplaySnapshot,
7881 on_buffer_edit: bool,
7882 window: &mut Window,
7883 cx: &mut Context<Editor>,
7884 ) {
7885 let Some((query_text, query_range)) =
7886 self.prepare_highlight_query_from_selection(snapshot, cx)
7887 else {
7888 self.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
7889 self.quick_selection_highlight_task.take();
7890 self.debounced_selection_highlight_task.take();
7891 self.debounced_selection_highlight_complete = false;
7892 return;
7893 };
7894 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7895 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7896 let query_changed = self
7897 .quick_selection_highlight_task
7898 .as_ref()
7899 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range);
7900 if query_changed {
7901 self.debounced_selection_highlight_complete = false;
7902 }
7903 if on_buffer_edit || query_changed {
7904 self.quick_selection_highlight_task = Some((
7905 query_range.clone(),
7906 self.update_selection_occurrence_highlights(
7907 snapshot.buffer.clone(),
7908 query_text.clone(),
7909 query_range.clone(),
7910 self.multi_buffer_visible_range(&display_snapshot, cx),
7911 false,
7912 window,
7913 cx,
7914 ),
7915 ));
7916 }
7917 if on_buffer_edit
7918 || self
7919 .debounced_selection_highlight_task
7920 .as_ref()
7921 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7922 {
7923 let multi_buffer_start = multi_buffer_snapshot
7924 .anchor_before(MultiBufferOffset(0))
7925 .to_point(&multi_buffer_snapshot);
7926 let multi_buffer_end = multi_buffer_snapshot
7927 .anchor_after(multi_buffer_snapshot.len())
7928 .to_point(&multi_buffer_snapshot);
7929 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7930 self.debounced_selection_highlight_task = Some((
7931 query_range.clone(),
7932 self.update_selection_occurrence_highlights(
7933 snapshot.buffer.clone(),
7934 query_text,
7935 query_range,
7936 multi_buffer_full_range,
7937 true,
7938 window,
7939 cx,
7940 ),
7941 ));
7942 }
7943 }
7944
7945 pub fn multi_buffer_visible_range(
7946 &self,
7947 display_snapshot: &DisplaySnapshot,
7948 cx: &App,
7949 ) -> Range<Point> {
7950 let visible_start = self
7951 .scroll_manager
7952 .native_anchor(display_snapshot, cx)
7953 .anchor
7954 .to_point(display_snapshot.buffer_snapshot())
7955 .to_display_point(display_snapshot);
7956
7957 let mut target_end = visible_start;
7958 *target_end.row_mut() += self.visible_line_count().unwrap_or(0.).ceil() as u32;
7959
7960 visible_start.to_point(display_snapshot)
7961 ..display_snapshot
7962 .clip_point(target_end, Bias::Right)
7963 .to_point(display_snapshot)
7964 }
7965
7966 pub fn refresh_edit_prediction(
7967 &mut self,
7968 debounce: bool,
7969 user_requested: bool,
7970 window: &mut Window,
7971 cx: &mut Context<Self>,
7972 ) -> Option<()> {
7973 if self.leader_id.is_some() {
7974 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7975 return None;
7976 }
7977
7978 let cursor = self.selections.newest_anchor().head();
7979 let (buffer, cursor_buffer_position) =
7980 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7981
7982 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
7983 return None;
7984 }
7985
7986 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7987 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7988 return None;
7989 }
7990
7991 self.update_visible_edit_prediction(window, cx);
7992
7993 if !user_requested
7994 && (!self.should_show_edit_predictions()
7995 || !self.is_focused(window)
7996 || buffer.read(cx).is_empty())
7997 {
7998 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7999 return None;
8000 }
8001
8002 self.edit_prediction_provider()?
8003 .refresh(buffer, cursor_buffer_position, debounce, cx);
8004 Some(())
8005 }
8006
8007 fn show_edit_predictions_in_menu(&self) -> bool {
8008 match self.edit_prediction_settings {
8009 EditPredictionSettings::Disabled => false,
8010 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
8011 }
8012 }
8013
8014 pub fn edit_predictions_enabled(&self) -> bool {
8015 match self.edit_prediction_settings {
8016 EditPredictionSettings::Disabled => false,
8017 EditPredictionSettings::Enabled { .. } => true,
8018 }
8019 }
8020
8021 fn edit_prediction_requires_modifier(&self) -> bool {
8022 match self.edit_prediction_settings {
8023 EditPredictionSettings::Disabled => false,
8024 EditPredictionSettings::Enabled {
8025 preview_requires_modifier,
8026 ..
8027 } => preview_requires_modifier,
8028 }
8029 }
8030
8031 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
8032 if self.edit_prediction_provider.is_none() {
8033 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8034 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8035 return;
8036 }
8037
8038 let selection = self.selections.newest_anchor();
8039 let cursor = selection.head();
8040
8041 if let Some((buffer, cursor_buffer_position)) =
8042 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
8043 {
8044 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
8045 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8046 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8047 return;
8048 }
8049 self.edit_prediction_settings =
8050 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8051 }
8052 }
8053
8054 fn edit_prediction_settings_at_position(
8055 &self,
8056 buffer: &Entity<Buffer>,
8057 buffer_position: language::Anchor,
8058 cx: &App,
8059 ) -> EditPredictionSettings {
8060 if !self.mode.is_full()
8061 || !self.show_edit_predictions_override.unwrap_or(true)
8062 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
8063 {
8064 return EditPredictionSettings::Disabled;
8065 }
8066
8067 let buffer = buffer.read(cx);
8068
8069 let file = buffer.file();
8070
8071 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
8072 return EditPredictionSettings::Disabled;
8073 };
8074
8075 let by_provider = matches!(
8076 self.menu_edit_predictions_policy,
8077 MenuEditPredictionsPolicy::ByProvider
8078 );
8079
8080 let show_in_menu = by_provider
8081 && self
8082 .edit_prediction_provider
8083 .as_ref()
8084 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
8085
8086 let preview_requires_modifier =
8087 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
8088
8089 EditPredictionSettings::Enabled {
8090 show_in_menu,
8091 preview_requires_modifier,
8092 }
8093 }
8094
8095 fn should_show_edit_predictions(&self) -> bool {
8096 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
8097 }
8098
8099 pub fn edit_prediction_preview_is_active(&self) -> bool {
8100 matches!(
8101 self.edit_prediction_preview,
8102 EditPredictionPreview::Active { .. }
8103 )
8104 }
8105
8106 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
8107 let cursor = self.selections.newest_anchor().head();
8108 if let Some((buffer, cursor_position)) =
8109 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
8110 {
8111 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
8112 } else {
8113 false
8114 }
8115 }
8116
8117 pub fn supports_minimap(&self, cx: &App) -> bool {
8118 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
8119 }
8120
8121 fn edit_predictions_enabled_in_buffer(
8122 &self,
8123 buffer: &Entity<Buffer>,
8124 buffer_position: language::Anchor,
8125 cx: &App,
8126 ) -> bool {
8127 maybe!({
8128 if self.read_only(cx) || self.leader_id.is_some() {
8129 return Some(false);
8130 }
8131 let provider = self.edit_prediction_provider()?;
8132 if !provider.is_enabled(buffer, buffer_position, cx) {
8133 return Some(false);
8134 }
8135 let buffer = buffer.read(cx);
8136 let Some(file) = buffer.file() else {
8137 return Some(true);
8138 };
8139 let settings = all_language_settings(Some(file), cx);
8140 Some(settings.edit_predictions_enabled_for_file(file, cx))
8141 })
8142 .unwrap_or(false)
8143 }
8144
8145 pub fn show_edit_prediction(
8146 &mut self,
8147 _: &ShowEditPrediction,
8148 window: &mut Window,
8149 cx: &mut Context<Self>,
8150 ) {
8151 if !self.has_active_edit_prediction() {
8152 self.refresh_edit_prediction(false, true, window, cx);
8153 return;
8154 }
8155
8156 self.update_visible_edit_prediction(window, cx);
8157 }
8158
8159 pub fn display_cursor_names(
8160 &mut self,
8161 _: &DisplayCursorNames,
8162 window: &mut Window,
8163 cx: &mut Context<Self>,
8164 ) {
8165 self.show_cursor_names(window, cx);
8166 }
8167
8168 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8169 self.show_cursor_names = true;
8170 cx.notify();
8171 cx.spawn_in(window, async move |this, cx| {
8172 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
8173 this.update(cx, |this, cx| {
8174 this.show_cursor_names = false;
8175 cx.notify()
8176 })
8177 .ok()
8178 })
8179 .detach();
8180 }
8181
8182 pub fn accept_partial_edit_prediction(
8183 &mut self,
8184 granularity: EditPredictionGranularity,
8185 window: &mut Window,
8186 cx: &mut Context<Self>,
8187 ) {
8188 if self.show_edit_predictions_in_menu() {
8189 self.hide_context_menu(window, cx);
8190 }
8191
8192 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
8193 return;
8194 };
8195
8196 if !matches!(granularity, EditPredictionGranularity::Full) && self.selections.count() != 1 {
8197 return;
8198 }
8199
8200 match &active_edit_prediction.completion {
8201 EditPrediction::MoveWithin { target, .. } => {
8202 let target = *target;
8203
8204 if matches!(granularity, EditPredictionGranularity::Full) {
8205 if let Some(position_map) = &self.last_position_map {
8206 let target_row = target.to_display_point(&position_map.snapshot).row();
8207 let is_visible = position_map.visible_row_range.contains(&target_row);
8208
8209 if is_visible || !self.edit_prediction_requires_modifier() {
8210 self.unfold_ranges(&[target..target], true, false, cx);
8211 self.change_selections(
8212 SelectionEffects::scroll(Autoscroll::newest()),
8213 window,
8214 cx,
8215 |selections| {
8216 selections.select_anchor_ranges([target..target]);
8217 },
8218 );
8219 self.clear_row_highlights::<EditPredictionPreview>();
8220 self.edit_prediction_preview
8221 .set_previous_scroll_position(None);
8222 } else {
8223 // Highlight and request scroll
8224 self.edit_prediction_preview
8225 .set_previous_scroll_position(Some(
8226 position_map.snapshot.scroll_anchor,
8227 ));
8228 self.highlight_rows::<EditPredictionPreview>(
8229 target..target,
8230 cx.theme().colors().editor_highlighted_line_background,
8231 RowHighlightOptions {
8232 autoscroll: true,
8233 ..Default::default()
8234 },
8235 cx,
8236 );
8237 self.request_autoscroll(Autoscroll::fit(), cx);
8238 }
8239 }
8240 } else {
8241 self.change_selections(
8242 SelectionEffects::scroll(Autoscroll::newest()),
8243 window,
8244 cx,
8245 |selections| {
8246 selections.select_anchor_ranges([target..target]);
8247 },
8248 );
8249 }
8250 }
8251 EditPrediction::MoveOutside { snapshot, target } => {
8252 if let Some(workspace) = self.workspace() {
8253 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
8254 .detach_and_log_err(cx);
8255 }
8256 }
8257 EditPrediction::Edit {
8258 edits,
8259 cursor_position,
8260 ..
8261 } => {
8262 self.report_edit_prediction_event(
8263 active_edit_prediction.completion_id.clone(),
8264 true,
8265 cx,
8266 );
8267
8268 match granularity {
8269 EditPredictionGranularity::Full => {
8270 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
8271
8272 // Compute fallback cursor position BEFORE applying the edit,
8273 // so the anchor tracks through the edit correctly
8274 let fallback_cursor_target = {
8275 let snapshot = self.buffer.read(cx).snapshot(cx);
8276 edits.last().unwrap().0.end.bias_right(&snapshot)
8277 };
8278
8279 self.buffer.update(cx, |buffer, cx| {
8280 buffer.edit(edits.iter().cloned(), None, cx)
8281 });
8282
8283 if let Some(provider) = self.edit_prediction_provider() {
8284 provider.accept(cx);
8285 }
8286
8287 // Resolve cursor position after the edit is applied
8288 let cursor_target = if let Some((anchor, offset)) = cursor_position {
8289 // The anchor tracks through the edit, then we add the offset
8290 let snapshot = self.buffer.read(cx).snapshot(cx);
8291 let base_offset = anchor.to_offset(&snapshot).0;
8292 let target_offset =
8293 MultiBufferOffset((base_offset + offset).min(snapshot.len().0));
8294 snapshot.anchor_after(target_offset)
8295 } else {
8296 fallback_cursor_target
8297 };
8298
8299 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
8300 s.select_anchor_ranges([cursor_target..cursor_target]);
8301 });
8302
8303 let selections = self.selections.disjoint_anchors_arc();
8304 if let Some(transaction_id_now) =
8305 self.buffer.read(cx).last_transaction_id(cx)
8306 {
8307 if transaction_id_prev != Some(transaction_id_now) {
8308 self.selection_history
8309 .insert_transaction(transaction_id_now, selections);
8310 }
8311 }
8312
8313 self.update_visible_edit_prediction(window, cx);
8314 if self.active_edit_prediction.is_none() {
8315 self.refresh_edit_prediction(true, true, window, cx);
8316 }
8317 cx.notify();
8318 }
8319 _ => {
8320 let snapshot = self.buffer.read(cx).snapshot(cx);
8321 let cursor_offset = self
8322 .selections
8323 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8324 .head();
8325
8326 let insertion = edits.iter().find_map(|(range, text)| {
8327 let range = range.to_offset(&snapshot);
8328 if range.is_empty() && range.start == cursor_offset {
8329 Some(text)
8330 } else {
8331 None
8332 }
8333 });
8334
8335 if let Some(text) = insertion {
8336 let text_to_insert = match granularity {
8337 EditPredictionGranularity::Word => {
8338 let mut partial = text
8339 .chars()
8340 .by_ref()
8341 .take_while(|c| c.is_alphabetic())
8342 .collect::<String>();
8343 if partial.is_empty() {
8344 partial = text
8345 .chars()
8346 .by_ref()
8347 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
8348 .collect::<String>();
8349 }
8350 partial
8351 }
8352 EditPredictionGranularity::Line => {
8353 if let Some(line) = text.split_inclusive('\n').next() {
8354 line.to_string()
8355 } else {
8356 text.to_string()
8357 }
8358 }
8359 EditPredictionGranularity::Full => unreachable!(),
8360 };
8361
8362 cx.emit(EditorEvent::InputHandled {
8363 utf16_range_to_replace: None,
8364 text: text_to_insert.clone().into(),
8365 });
8366
8367 self.replace_selections(&text_to_insert, None, window, cx, false);
8368 self.refresh_edit_prediction(true, true, window, cx);
8369 cx.notify();
8370 } else {
8371 self.accept_partial_edit_prediction(
8372 EditPredictionGranularity::Full,
8373 window,
8374 cx,
8375 );
8376 }
8377 }
8378 }
8379 }
8380 }
8381 }
8382
8383 pub fn accept_next_word_edit_prediction(
8384 &mut self,
8385 _: &AcceptNextWordEditPrediction,
8386 window: &mut Window,
8387 cx: &mut Context<Self>,
8388 ) {
8389 self.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
8390 }
8391
8392 pub fn accept_next_line_edit_prediction(
8393 &mut self,
8394 _: &AcceptNextLineEditPrediction,
8395 window: &mut Window,
8396 cx: &mut Context<Self>,
8397 ) {
8398 self.accept_partial_edit_prediction(EditPredictionGranularity::Line, window, cx);
8399 }
8400
8401 pub fn accept_edit_prediction(
8402 &mut self,
8403 _: &AcceptEditPrediction,
8404 window: &mut Window,
8405 cx: &mut Context<Self>,
8406 ) {
8407 self.accept_partial_edit_prediction(EditPredictionGranularity::Full, window, cx);
8408 }
8409
8410 fn discard_edit_prediction(
8411 &mut self,
8412 reason: EditPredictionDiscardReason,
8413 cx: &mut Context<Self>,
8414 ) -> bool {
8415 if reason == EditPredictionDiscardReason::Rejected {
8416 let completion_id = self
8417 .active_edit_prediction
8418 .as_ref()
8419 .and_then(|active_completion| active_completion.completion_id.clone());
8420
8421 self.report_edit_prediction_event(completion_id, false, cx);
8422 }
8423
8424 if let Some(provider) = self.edit_prediction_provider() {
8425 provider.discard(reason, cx);
8426 }
8427
8428 self.take_active_edit_prediction(cx)
8429 }
8430
8431 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
8432 let Some(provider) = self.edit_prediction_provider() else {
8433 return;
8434 };
8435
8436 let Some((_, buffer, _)) = self
8437 .buffer
8438 .read(cx)
8439 .excerpt_containing(self.selections.newest_anchor().head(), cx)
8440 else {
8441 return;
8442 };
8443
8444 let extension = buffer
8445 .read(cx)
8446 .file()
8447 .and_then(|file| Some(file.path().extension()?.to_string()));
8448
8449 let event_type = match accepted {
8450 true => "Edit Prediction Accepted",
8451 false => "Edit Prediction Discarded",
8452 };
8453 telemetry::event!(
8454 event_type,
8455 provider = provider.name(),
8456 prediction_id = id,
8457 suggestion_accepted = accepted,
8458 file_extension = extension,
8459 );
8460 }
8461
8462 fn open_editor_at_anchor(
8463 snapshot: &language::BufferSnapshot,
8464 target: language::Anchor,
8465 workspace: &Entity<Workspace>,
8466 window: &mut Window,
8467 cx: &mut App,
8468 ) -> Task<Result<()>> {
8469 workspace.update(cx, |workspace, cx| {
8470 let path = snapshot.file().map(|file| file.full_path(cx));
8471 let Some(path) =
8472 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
8473 else {
8474 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
8475 };
8476 let target = text::ToPoint::to_point(&target, snapshot);
8477 let item = workspace.open_path(path, None, true, window, cx);
8478 window.spawn(cx, async move |cx| {
8479 let Some(editor) = item.await?.downcast::<Editor>() else {
8480 return Ok(());
8481 };
8482 editor
8483 .update_in(cx, |editor, window, cx| {
8484 editor.go_to_singleton_buffer_point(target, window, cx);
8485 })
8486 .ok();
8487 anyhow::Ok(())
8488 })
8489 })
8490 }
8491
8492 pub fn has_active_edit_prediction(&self) -> bool {
8493 self.active_edit_prediction.is_some()
8494 }
8495
8496 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
8497 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
8498 return false;
8499 };
8500
8501 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
8502 self.clear_highlights(HighlightKey::EditPredictionHighlight, cx);
8503 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
8504 true
8505 }
8506
8507 /// Returns true when we're displaying the edit prediction popover below the cursor
8508 /// like we are not previewing and the LSP autocomplete menu is visible
8509 /// or we are in `when_holding_modifier` mode.
8510 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
8511 if self.edit_prediction_preview_is_active()
8512 || !self.show_edit_predictions_in_menu()
8513 || !self.edit_predictions_enabled()
8514 {
8515 return false;
8516 }
8517
8518 if self.has_visible_completions_menu() {
8519 return true;
8520 }
8521
8522 has_completion && self.edit_prediction_requires_modifier()
8523 }
8524
8525 fn handle_modifiers_changed(
8526 &mut self,
8527 modifiers: Modifiers,
8528 position_map: &PositionMap,
8529 window: &mut Window,
8530 cx: &mut Context<Self>,
8531 ) {
8532 self.update_edit_prediction_settings(cx);
8533
8534 // Ensure that the edit prediction preview is updated, even when not
8535 // enabled, if there's an active edit prediction preview.
8536 if self.show_edit_predictions_in_menu()
8537 || self.edit_prediction_requires_modifier()
8538 || matches!(
8539 self.edit_prediction_preview,
8540 EditPredictionPreview::Active { .. }
8541 )
8542 {
8543 self.update_edit_prediction_preview(&modifiers, window, cx);
8544 }
8545
8546 self.update_selection_mode(&modifiers, position_map, window, cx);
8547
8548 let mouse_position = window.mouse_position();
8549 if !position_map.text_hitbox.is_hovered(window) {
8550 return;
8551 }
8552
8553 self.update_hovered_link(
8554 position_map.point_for_position(mouse_position),
8555 Some(mouse_position),
8556 &position_map.snapshot,
8557 modifiers,
8558 window,
8559 cx,
8560 )
8561 }
8562
8563 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8564 match EditorSettings::get_global(cx).multi_cursor_modifier {
8565 MultiCursorModifier::Alt => modifiers.secondary(),
8566 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
8567 }
8568 }
8569
8570 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8571 match EditorSettings::get_global(cx).multi_cursor_modifier {
8572 MultiCursorModifier::Alt => modifiers.alt,
8573 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
8574 }
8575 }
8576
8577 fn columnar_selection_mode(
8578 modifiers: &Modifiers,
8579 cx: &mut Context<Self>,
8580 ) -> Option<ColumnarMode> {
8581 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
8582 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
8583 Some(ColumnarMode::FromMouse)
8584 } else if Self::is_alt_pressed(modifiers, cx) {
8585 Some(ColumnarMode::FromSelection)
8586 } else {
8587 None
8588 }
8589 } else {
8590 None
8591 }
8592 }
8593
8594 fn update_selection_mode(
8595 &mut self,
8596 modifiers: &Modifiers,
8597 position_map: &PositionMap,
8598 window: &mut Window,
8599 cx: &mut Context<Self>,
8600 ) {
8601 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
8602 return;
8603 };
8604 if self.selections.pending_anchor().is_none() {
8605 return;
8606 }
8607
8608 let mouse_position = window.mouse_position();
8609 let point_for_position = position_map.point_for_position(mouse_position);
8610 let position = point_for_position.previous_valid;
8611
8612 self.select(
8613 SelectPhase::BeginColumnar {
8614 position,
8615 reset: false,
8616 mode,
8617 goal_column: point_for_position.exact_unclipped.column(),
8618 },
8619 window,
8620 cx,
8621 );
8622 }
8623
8624 fn update_edit_prediction_preview(
8625 &mut self,
8626 modifiers: &Modifiers,
8627 window: &mut Window,
8628 cx: &mut Context<Self>,
8629 ) {
8630 let modifiers_held = self.edit_prediction_preview_modifiers_held(modifiers, window, cx);
8631
8632 if modifiers_held {
8633 if matches!(
8634 self.edit_prediction_preview,
8635 EditPredictionPreview::Inactive { .. }
8636 ) {
8637 self.edit_prediction_preview = EditPredictionPreview::Active {
8638 previous_scroll_position: None,
8639 since: Instant::now(),
8640 };
8641
8642 self.update_visible_edit_prediction(window, cx);
8643 cx.notify();
8644 }
8645 } else if let EditPredictionPreview::Active {
8646 previous_scroll_position,
8647 since,
8648 } = self.edit_prediction_preview
8649 {
8650 if let (Some(previous_scroll_position), Some(position_map)) =
8651 (previous_scroll_position, self.last_position_map.as_ref())
8652 {
8653 self.set_scroll_position(
8654 previous_scroll_position
8655 .scroll_position(&position_map.snapshot.display_snapshot),
8656 window,
8657 cx,
8658 );
8659 }
8660
8661 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8662 released_too_fast: since.elapsed() < Duration::from_millis(200),
8663 };
8664 self.clear_row_highlights::<EditPredictionPreview>();
8665 self.update_visible_edit_prediction(window, cx);
8666 cx.notify();
8667 }
8668 }
8669
8670 fn update_visible_edit_prediction(
8671 &mut self,
8672 _window: &mut Window,
8673 cx: &mut Context<Self>,
8674 ) -> Option<()> {
8675 if self.ime_transaction.is_some() {
8676 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8677 return None;
8678 }
8679
8680 let selection = self.selections.newest_anchor();
8681 let cursor = selection.head();
8682 let multibuffer = self.buffer.read(cx).snapshot(cx);
8683
8684 // Check project-level disable_ai setting for the current buffer
8685 if let Some((buffer, _)) = self.buffer.read(cx).text_anchor_for_position(cursor, cx) {
8686 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
8687 return None;
8688 }
8689 }
8690 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8691 let excerpt_id = cursor.excerpt_id;
8692
8693 let show_in_menu = self.show_edit_predictions_in_menu();
8694 let completions_menu_has_precedence = !show_in_menu
8695 && (self.context_menu.borrow().is_some()
8696 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8697
8698 if completions_menu_has_precedence
8699 || !offset_selection.is_empty()
8700 || self
8701 .active_edit_prediction
8702 .as_ref()
8703 .is_some_and(|completion| {
8704 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8705 return false;
8706 };
8707 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8708 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8709 !invalidation_range.contains(&offset_selection.head())
8710 })
8711 {
8712 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8713 return None;
8714 }
8715
8716 self.take_active_edit_prediction(cx);
8717 let Some(provider) = self.edit_prediction_provider() else {
8718 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8719 return None;
8720 };
8721
8722 let (buffer, cursor_buffer_position) =
8723 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8724
8725 self.edit_prediction_settings =
8726 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8727
8728 self.in_leading_whitespace = multibuffer.is_line_whitespace_upto(cursor);
8729
8730 if self.in_leading_whitespace {
8731 let cursor_point = cursor.to_point(&multibuffer);
8732 let mut suggested_indent = None;
8733 multibuffer.suggested_indents_callback(
8734 cursor_point.row..cursor_point.row + 1,
8735 &mut |_, indent| {
8736 suggested_indent = Some(indent);
8737 ControlFlow::Break(())
8738 },
8739 cx,
8740 );
8741
8742 if let Some(indent) = suggested_indent
8743 && indent.len == cursor_point.column
8744 {
8745 self.in_leading_whitespace = false;
8746 }
8747 }
8748
8749 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8750
8751 let (completion_id, edits, predicted_cursor_position, edit_preview) = match edit_prediction
8752 {
8753 edit_prediction_types::EditPrediction::Local {
8754 id,
8755 edits,
8756 cursor_position,
8757 edit_preview,
8758 } => (id, edits, cursor_position, edit_preview),
8759 edit_prediction_types::EditPrediction::Jump {
8760 id,
8761 snapshot,
8762 target,
8763 } => {
8764 if let Some(provider) = &self.edit_prediction_provider {
8765 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8766 }
8767 self.stale_edit_prediction_in_menu = None;
8768 self.active_edit_prediction = Some(EditPredictionState {
8769 inlay_ids: vec![],
8770 completion: EditPrediction::MoveOutside { snapshot, target },
8771 completion_id: id,
8772 invalidation_range: None,
8773 });
8774 cx.notify();
8775 return Some(());
8776 }
8777 };
8778
8779 let edits = edits
8780 .into_iter()
8781 .flat_map(|(range, new_text)| {
8782 Some((
8783 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8784 new_text,
8785 ))
8786 })
8787 .collect::<Vec<_>>();
8788 if edits.is_empty() {
8789 return None;
8790 }
8791
8792 let cursor_position = predicted_cursor_position.and_then(|predicted| {
8793 let anchor = multibuffer.anchor_in_excerpt(excerpt_id, predicted.anchor)?;
8794 Some((anchor, predicted.offset))
8795 });
8796
8797 let first_edit_start = edits.first().unwrap().0.start;
8798 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8799 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8800
8801 let last_edit_end = edits.last().unwrap().0.end;
8802 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8803 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8804
8805 let cursor_row = cursor.to_point(&multibuffer).row;
8806
8807 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8808
8809 let mut inlay_ids = Vec::new();
8810 let invalidation_row_range;
8811 let move_invalidation_row_range = if cursor_row < edit_start_row {
8812 Some(cursor_row..edit_end_row)
8813 } else if cursor_row > edit_end_row {
8814 Some(edit_start_row..cursor_row)
8815 } else {
8816 None
8817 };
8818 let supports_jump = self
8819 .edit_prediction_provider
8820 .as_ref()
8821 .map(|provider| provider.provider.supports_jump_to_edit())
8822 .unwrap_or(true);
8823
8824 let is_move = supports_jump
8825 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8826 let completion = if is_move {
8827 if let Some(provider) = &self.edit_prediction_provider {
8828 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8829 }
8830 invalidation_row_range =
8831 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8832 let target = first_edit_start;
8833 EditPrediction::MoveWithin { target, snapshot }
8834 } else {
8835 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8836 && !self.edit_predictions_hidden_for_vim_mode;
8837
8838 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8839 if provider.show_tab_accept_marker() {
8840 EditDisplayMode::TabAccept
8841 } else {
8842 EditDisplayMode::Inline
8843 }
8844 } else {
8845 EditDisplayMode::DiffPopover
8846 };
8847
8848 if show_completions_in_buffer {
8849 if let Some(provider) = &self.edit_prediction_provider {
8850 let suggestion_display_type = match display_mode {
8851 EditDisplayMode::DiffPopover => SuggestionDisplayType::DiffPopover,
8852 EditDisplayMode::Inline | EditDisplayMode::TabAccept => {
8853 SuggestionDisplayType::GhostText
8854 }
8855 };
8856 provider.provider.did_show(suggestion_display_type, cx);
8857 }
8858 if edits
8859 .iter()
8860 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8861 {
8862 let mut inlays = Vec::new();
8863 for (range, new_text) in &edits {
8864 let inlay = Inlay::edit_prediction(
8865 post_inc(&mut self.next_inlay_id),
8866 range.start,
8867 new_text.as_ref(),
8868 );
8869 inlay_ids.push(inlay.id);
8870 inlays.push(inlay);
8871 }
8872
8873 self.splice_inlays(&[], inlays, cx);
8874 } else {
8875 let background_color = cx.theme().status().deleted_background;
8876 self.highlight_text(
8877 HighlightKey::EditPredictionHighlight,
8878 edits.iter().map(|(range, _)| range.clone()).collect(),
8879 HighlightStyle {
8880 background_color: Some(background_color),
8881 ..Default::default()
8882 },
8883 cx,
8884 );
8885 }
8886 }
8887
8888 invalidation_row_range = edit_start_row..edit_end_row;
8889
8890 EditPrediction::Edit {
8891 edits,
8892 cursor_position,
8893 edit_preview,
8894 display_mode,
8895 snapshot,
8896 }
8897 };
8898
8899 let invalidation_range = multibuffer
8900 .anchor_before(Point::new(invalidation_row_range.start, 0))
8901 ..multibuffer.anchor_after(Point::new(
8902 invalidation_row_range.end,
8903 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8904 ));
8905
8906 self.stale_edit_prediction_in_menu = None;
8907 self.active_edit_prediction = Some(EditPredictionState {
8908 inlay_ids,
8909 completion,
8910 completion_id,
8911 invalidation_range: Some(invalidation_range),
8912 });
8913
8914 cx.notify();
8915
8916 Some(())
8917 }
8918
8919 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8920 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8921 }
8922
8923 /// Get all display points of breakpoints that will be rendered within editor
8924 ///
8925 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8926 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8927 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8928 fn active_breakpoints(
8929 &self,
8930 range: Range<DisplayRow>,
8931 window: &mut Window,
8932 cx: &mut Context<Self>,
8933 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8934 let mut breakpoint_display_points = HashMap::default();
8935
8936 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8937 return breakpoint_display_points;
8938 };
8939
8940 let snapshot = self.snapshot(window, cx);
8941
8942 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8943 let Some(project) = self.project() else {
8944 return breakpoint_display_points;
8945 };
8946
8947 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8948 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8949
8950 for (buffer_snapshot, range, excerpt_id) in
8951 multi_buffer_snapshot.range_to_buffer_ranges(range.start..=range.end)
8952 {
8953 let Some(buffer) = project
8954 .read(cx)
8955 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8956 else {
8957 continue;
8958 };
8959 let breakpoints = breakpoint_store.read(cx).breakpoints(
8960 &buffer,
8961 Some(
8962 buffer_snapshot.anchor_before(range.start)
8963 ..buffer_snapshot.anchor_after(range.end),
8964 ),
8965 buffer_snapshot,
8966 cx,
8967 );
8968 for (breakpoint, state) in breakpoints {
8969 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8970 let position = multi_buffer_anchor
8971 .to_point(&multi_buffer_snapshot)
8972 .to_display_point(&snapshot);
8973
8974 breakpoint_display_points.insert(
8975 position.row(),
8976 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8977 );
8978 }
8979 }
8980
8981 breakpoint_display_points
8982 }
8983
8984 fn breakpoint_context_menu(
8985 &self,
8986 anchor: Anchor,
8987 window: &mut Window,
8988 cx: &mut Context<Self>,
8989 ) -> Entity<ui::ContextMenu> {
8990 let weak_editor = cx.weak_entity();
8991 let focus_handle = self.focus_handle(cx);
8992
8993 let row = self
8994 .buffer
8995 .read(cx)
8996 .snapshot(cx)
8997 .summary_for_anchor::<Point>(&anchor)
8998 .row;
8999
9000 let breakpoint = self
9001 .breakpoint_at_row(row, window, cx)
9002 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
9003
9004 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
9005 "Edit Log Breakpoint"
9006 } else {
9007 "Set Log Breakpoint"
9008 };
9009
9010 let condition_breakpoint_msg = if breakpoint
9011 .as_ref()
9012 .is_some_and(|bp| bp.1.condition.is_some())
9013 {
9014 "Edit Condition Breakpoint"
9015 } else {
9016 "Set Condition Breakpoint"
9017 };
9018
9019 let hit_condition_breakpoint_msg = if breakpoint
9020 .as_ref()
9021 .is_some_and(|bp| bp.1.hit_condition.is_some())
9022 {
9023 "Edit Hit Condition Breakpoint"
9024 } else {
9025 "Set Hit Condition Breakpoint"
9026 };
9027
9028 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
9029 "Unset Breakpoint"
9030 } else {
9031 "Set Breakpoint"
9032 };
9033
9034 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
9035
9036 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
9037 BreakpointState::Enabled => Some("Disable"),
9038 BreakpointState::Disabled => Some("Enable"),
9039 });
9040
9041 let (anchor, breakpoint) =
9042 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
9043
9044 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
9045 menu.on_blur_subscription(Subscription::new(|| {}))
9046 .context(focus_handle)
9047 .when(run_to_cursor, |this| {
9048 let weak_editor = weak_editor.clone();
9049 this.entry("Run to Cursor", None, move |window, cx| {
9050 weak_editor
9051 .update(cx, |editor, cx| {
9052 editor.change_selections(
9053 SelectionEffects::no_scroll(),
9054 window,
9055 cx,
9056 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
9057 );
9058 })
9059 .ok();
9060
9061 window.dispatch_action(Box::new(RunToCursor), cx);
9062 })
9063 .separator()
9064 })
9065 .when_some(toggle_state_msg, |this, msg| {
9066 this.entry(msg, None, {
9067 let weak_editor = weak_editor.clone();
9068 let breakpoint = breakpoint.clone();
9069 move |_window, cx| {
9070 weak_editor
9071 .update(cx, |this, cx| {
9072 this.edit_breakpoint_at_anchor(
9073 anchor,
9074 breakpoint.as_ref().clone(),
9075 BreakpointEditAction::InvertState,
9076 cx,
9077 );
9078 })
9079 .log_err();
9080 }
9081 })
9082 })
9083 .entry(set_breakpoint_msg, None, {
9084 let weak_editor = weak_editor.clone();
9085 let breakpoint = breakpoint.clone();
9086 move |_window, cx| {
9087 weak_editor
9088 .update(cx, |this, cx| {
9089 this.edit_breakpoint_at_anchor(
9090 anchor,
9091 breakpoint.as_ref().clone(),
9092 BreakpointEditAction::Toggle,
9093 cx,
9094 );
9095 })
9096 .log_err();
9097 }
9098 })
9099 .entry(log_breakpoint_msg, None, {
9100 let breakpoint = breakpoint.clone();
9101 let weak_editor = weak_editor.clone();
9102 move |window, cx| {
9103 weak_editor
9104 .update(cx, |this, cx| {
9105 this.add_edit_breakpoint_block(
9106 anchor,
9107 breakpoint.as_ref(),
9108 BreakpointPromptEditAction::Log,
9109 window,
9110 cx,
9111 );
9112 })
9113 .log_err();
9114 }
9115 })
9116 .entry(condition_breakpoint_msg, None, {
9117 let breakpoint = breakpoint.clone();
9118 let weak_editor = weak_editor.clone();
9119 move |window, cx| {
9120 weak_editor
9121 .update(cx, |this, cx| {
9122 this.add_edit_breakpoint_block(
9123 anchor,
9124 breakpoint.as_ref(),
9125 BreakpointPromptEditAction::Condition,
9126 window,
9127 cx,
9128 );
9129 })
9130 .log_err();
9131 }
9132 })
9133 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
9134 weak_editor
9135 .update(cx, |this, cx| {
9136 this.add_edit_breakpoint_block(
9137 anchor,
9138 breakpoint.as_ref(),
9139 BreakpointPromptEditAction::HitCondition,
9140 window,
9141 cx,
9142 );
9143 })
9144 .log_err();
9145 })
9146 })
9147 }
9148
9149 fn render_breakpoint(
9150 &self,
9151 position: Anchor,
9152 row: DisplayRow,
9153 breakpoint: &Breakpoint,
9154 state: Option<BreakpointSessionState>,
9155 cx: &mut Context<Self>,
9156 ) -> IconButton {
9157 let is_rejected = state.is_some_and(|s| !s.verified);
9158 // Is it a breakpoint that shows up when hovering over gutter?
9159 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
9160 (false, false),
9161 |PhantomBreakpointIndicator {
9162 is_active,
9163 display_row,
9164 collides_with_existing_breakpoint,
9165 }| {
9166 (
9167 is_active && display_row == row,
9168 collides_with_existing_breakpoint,
9169 )
9170 },
9171 );
9172
9173 let (color, icon) = {
9174 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
9175 (false, false) => ui::IconName::DebugBreakpoint,
9176 (true, false) => ui::IconName::DebugLogBreakpoint,
9177 (false, true) => ui::IconName::DebugDisabledBreakpoint,
9178 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
9179 };
9180
9181 let theme_colors = cx.theme().colors();
9182
9183 let color = if is_phantom {
9184 if collides_with_existing {
9185 Color::Custom(
9186 theme_colors
9187 .debugger_accent
9188 .blend(theme_colors.text.opacity(0.6)),
9189 )
9190 } else {
9191 Color::Hint
9192 }
9193 } else if is_rejected {
9194 Color::Disabled
9195 } else {
9196 Color::Debugger
9197 };
9198
9199 (color, icon)
9200 };
9201
9202 let breakpoint = Arc::from(breakpoint.clone());
9203
9204 let alt_as_text = gpui::Keystroke {
9205 modifiers: Modifiers::secondary_key(),
9206 ..Default::default()
9207 };
9208 let primary_action_text = if breakpoint.is_disabled() {
9209 "Enable breakpoint"
9210 } else if is_phantom && !collides_with_existing {
9211 "Set breakpoint"
9212 } else {
9213 "Unset breakpoint"
9214 };
9215 let focus_handle = self.focus_handle.clone();
9216
9217 let meta = if is_rejected {
9218 SharedString::from("No executable code is associated with this line.")
9219 } else if collides_with_existing && !breakpoint.is_disabled() {
9220 SharedString::from(format!(
9221 "{alt_as_text}-click to disable,\nright-click for more options."
9222 ))
9223 } else {
9224 SharedString::from("Right-click for more options.")
9225 };
9226 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
9227 .icon_size(IconSize::XSmall)
9228 .size(ui::ButtonSize::None)
9229 .when(is_rejected, |this| {
9230 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
9231 })
9232 .icon_color(color)
9233 .style(ButtonStyle::Transparent)
9234 .on_click(cx.listener({
9235 move |editor, event: &ClickEvent, window, cx| {
9236 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
9237 BreakpointEditAction::InvertState
9238 } else {
9239 BreakpointEditAction::Toggle
9240 };
9241
9242 window.focus(&editor.focus_handle(cx), cx);
9243 editor.update_breakpoint_collision_on_toggle(row, &edit_action);
9244 editor.edit_breakpoint_at_anchor(
9245 position,
9246 breakpoint.as_ref().clone(),
9247 edit_action,
9248 cx,
9249 );
9250 }
9251 }))
9252 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
9253 editor.set_breakpoint_context_menu(
9254 row,
9255 Some(position),
9256 event.position(),
9257 window,
9258 cx,
9259 );
9260 }))
9261 .tooltip(move |_window, cx| {
9262 Tooltip::with_meta_in(
9263 primary_action_text,
9264 Some(&ToggleBreakpoint),
9265 meta.clone(),
9266 &focus_handle,
9267 cx,
9268 )
9269 })
9270 }
9271
9272 fn build_tasks_context(
9273 project: &Entity<Project>,
9274 buffer: &Entity<Buffer>,
9275 buffer_row: u32,
9276 tasks: &Arc<RunnableTasks>,
9277 cx: &mut Context<Self>,
9278 ) -> Task<Option<task::TaskContext>> {
9279 let position = Point::new(buffer_row, tasks.column);
9280 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
9281 let location = Location {
9282 buffer: buffer.clone(),
9283 range: range_start..range_start,
9284 };
9285 // Fill in the environmental variables from the tree-sitter captures
9286 let mut captured_task_variables = TaskVariables::default();
9287 for (capture_name, value) in tasks.extra_variables.clone() {
9288 captured_task_variables.insert(
9289 task::VariableName::Custom(capture_name.into()),
9290 value.clone(),
9291 );
9292 }
9293 project.update(cx, |project, cx| {
9294 project.task_store().update(cx, |task_store, cx| {
9295 task_store.task_context_for_location(captured_task_variables, location, cx)
9296 })
9297 })
9298 }
9299
9300 pub fn context_menu_visible(&self) -> bool {
9301 !self.edit_prediction_preview_is_active()
9302 && self
9303 .context_menu
9304 .borrow()
9305 .as_ref()
9306 .is_some_and(|menu| menu.visible())
9307 }
9308
9309 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
9310 self.context_menu
9311 .borrow()
9312 .as_ref()
9313 .map(|menu| menu.origin())
9314 }
9315
9316 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
9317 self.context_menu_options = Some(options);
9318 }
9319
9320 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
9321 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
9322
9323 fn render_edit_prediction_popover(
9324 &mut self,
9325 text_bounds: &Bounds<Pixels>,
9326 content_origin: gpui::Point<Pixels>,
9327 right_margin: Pixels,
9328 editor_snapshot: &EditorSnapshot,
9329 visible_row_range: Range<DisplayRow>,
9330 scroll_top: ScrollOffset,
9331 scroll_bottom: ScrollOffset,
9332 line_layouts: &[LineWithInvisibles],
9333 line_height: Pixels,
9334 scroll_position: gpui::Point<ScrollOffset>,
9335 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9336 newest_selection_head: Option<DisplayPoint>,
9337 editor_width: Pixels,
9338 style: &EditorStyle,
9339 window: &mut Window,
9340 cx: &mut App,
9341 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9342 if self.mode().is_minimap() {
9343 return None;
9344 }
9345 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
9346
9347 if self.edit_prediction_visible_in_cursor_popover(true) {
9348 return None;
9349 }
9350
9351 match &active_edit_prediction.completion {
9352 EditPrediction::MoveWithin { target, .. } => {
9353 let target_display_point = target.to_display_point(editor_snapshot);
9354
9355 if self.edit_prediction_requires_modifier() {
9356 if !self.edit_prediction_preview_is_active() {
9357 return None;
9358 }
9359
9360 self.render_edit_prediction_modifier_jump_popover(
9361 text_bounds,
9362 content_origin,
9363 visible_row_range,
9364 line_layouts,
9365 line_height,
9366 scroll_pixel_position,
9367 newest_selection_head,
9368 target_display_point,
9369 window,
9370 cx,
9371 )
9372 } else {
9373 self.render_edit_prediction_eager_jump_popover(
9374 text_bounds,
9375 content_origin,
9376 editor_snapshot,
9377 visible_row_range,
9378 scroll_top,
9379 scroll_bottom,
9380 line_height,
9381 scroll_pixel_position,
9382 target_display_point,
9383 editor_width,
9384 window,
9385 cx,
9386 )
9387 }
9388 }
9389 EditPrediction::Edit {
9390 display_mode: EditDisplayMode::Inline,
9391 ..
9392 } => None,
9393 EditPrediction::Edit {
9394 display_mode: EditDisplayMode::TabAccept,
9395 edits,
9396 ..
9397 } => {
9398 let range = &edits.first()?.0;
9399 let target_display_point = range.end.to_display_point(editor_snapshot);
9400
9401 self.render_edit_prediction_end_of_line_popover(
9402 "Accept",
9403 editor_snapshot,
9404 visible_row_range,
9405 target_display_point,
9406 line_height,
9407 scroll_pixel_position,
9408 content_origin,
9409 editor_width,
9410 window,
9411 cx,
9412 )
9413 }
9414 EditPrediction::Edit {
9415 edits,
9416 edit_preview,
9417 display_mode: EditDisplayMode::DiffPopover,
9418 snapshot,
9419 ..
9420 } => self.render_edit_prediction_diff_popover(
9421 text_bounds,
9422 content_origin,
9423 right_margin,
9424 editor_snapshot,
9425 visible_row_range,
9426 line_layouts,
9427 line_height,
9428 scroll_position,
9429 scroll_pixel_position,
9430 newest_selection_head,
9431 editor_width,
9432 style,
9433 edits,
9434 edit_preview,
9435 snapshot,
9436 window,
9437 cx,
9438 ),
9439 EditPrediction::MoveOutside { snapshot, .. } => {
9440 let mut element = self
9441 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
9442 .into_any();
9443
9444 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9445 let origin_x = text_bounds.size.width - size.width - px(30.);
9446 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
9447 element.prepaint_at(origin, window, cx);
9448
9449 Some((element, origin))
9450 }
9451 }
9452 }
9453
9454 fn render_edit_prediction_modifier_jump_popover(
9455 &mut self,
9456 text_bounds: &Bounds<Pixels>,
9457 content_origin: gpui::Point<Pixels>,
9458 visible_row_range: Range<DisplayRow>,
9459 line_layouts: &[LineWithInvisibles],
9460 line_height: Pixels,
9461 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9462 newest_selection_head: Option<DisplayPoint>,
9463 target_display_point: DisplayPoint,
9464 window: &mut Window,
9465 cx: &mut App,
9466 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9467 let scrolled_content_origin =
9468 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
9469
9470 const SCROLL_PADDING_Y: Pixels = px(12.);
9471
9472 if target_display_point.row() < visible_row_range.start {
9473 return self.render_edit_prediction_scroll_popover(
9474 &|_| SCROLL_PADDING_Y,
9475 IconName::ArrowUp,
9476 visible_row_range,
9477 line_layouts,
9478 newest_selection_head,
9479 scrolled_content_origin,
9480 window,
9481 cx,
9482 );
9483 } else if target_display_point.row() >= visible_row_range.end {
9484 return self.render_edit_prediction_scroll_popover(
9485 &|size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
9486 IconName::ArrowDown,
9487 visible_row_range,
9488 line_layouts,
9489 newest_selection_head,
9490 scrolled_content_origin,
9491 window,
9492 cx,
9493 );
9494 }
9495
9496 const POLE_WIDTH: Pixels = px(2.);
9497
9498 let line_layout =
9499 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
9500 let target_column = target_display_point.column() as usize;
9501
9502 let target_x = line_layout.x_for_index(target_column);
9503 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
9504 - scroll_pixel_position.y;
9505
9506 let flag_on_right = target_x < text_bounds.size.width / 2.;
9507
9508 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
9509 border_color.l += 0.001;
9510
9511 let mut element = v_flex()
9512 .items_end()
9513 .when(flag_on_right, |el| el.items_start())
9514 .child(if flag_on_right {
9515 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9516 .rounded_bl(px(0.))
9517 .rounded_tl(px(0.))
9518 .border_l_2()
9519 .border_color(border_color)
9520 } else {
9521 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9522 .rounded_br(px(0.))
9523 .rounded_tr(px(0.))
9524 .border_r_2()
9525 .border_color(border_color)
9526 })
9527 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9528 .into_any();
9529
9530 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9531
9532 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9533 - point(
9534 if flag_on_right {
9535 POLE_WIDTH
9536 } else {
9537 size.width - POLE_WIDTH
9538 },
9539 size.height - line_height,
9540 );
9541
9542 origin.x = origin.x.max(content_origin.x);
9543
9544 element.prepaint_at(origin, window, cx);
9545
9546 Some((element, origin))
9547 }
9548
9549 fn render_edit_prediction_scroll_popover(
9550 &mut self,
9551 to_y: &dyn Fn(Size<Pixels>) -> Pixels,
9552 scroll_icon: IconName,
9553 visible_row_range: Range<DisplayRow>,
9554 line_layouts: &[LineWithInvisibles],
9555 newest_selection_head: Option<DisplayPoint>,
9556 scrolled_content_origin: gpui::Point<Pixels>,
9557 window: &mut Window,
9558 cx: &mut App,
9559 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9560 let mut element = self
9561 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9562 .into_any();
9563
9564 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9565
9566 let cursor = newest_selection_head?;
9567 let cursor_row_layout =
9568 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9569 let cursor_column = cursor.column() as usize;
9570
9571 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9572
9573 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9574
9575 element.prepaint_at(origin, window, cx);
9576 Some((element, origin))
9577 }
9578
9579 fn render_edit_prediction_eager_jump_popover(
9580 &mut self,
9581 text_bounds: &Bounds<Pixels>,
9582 content_origin: gpui::Point<Pixels>,
9583 editor_snapshot: &EditorSnapshot,
9584 visible_row_range: Range<DisplayRow>,
9585 scroll_top: ScrollOffset,
9586 scroll_bottom: ScrollOffset,
9587 line_height: Pixels,
9588 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9589 target_display_point: DisplayPoint,
9590 editor_width: Pixels,
9591 window: &mut Window,
9592 cx: &mut App,
9593 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9594 if target_display_point.row().as_f64() < scroll_top {
9595 let mut element = self
9596 .render_edit_prediction_line_popover(
9597 "Jump to Edit",
9598 Some(IconName::ArrowUp),
9599 window,
9600 cx,
9601 )
9602 .into_any();
9603
9604 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9605 let offset = point(
9606 (text_bounds.size.width - size.width) / 2.,
9607 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9608 );
9609
9610 let origin = text_bounds.origin + offset;
9611 element.prepaint_at(origin, window, cx);
9612 Some((element, origin))
9613 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9614 let mut element = self
9615 .render_edit_prediction_line_popover(
9616 "Jump to Edit",
9617 Some(IconName::ArrowDown),
9618 window,
9619 cx,
9620 )
9621 .into_any();
9622
9623 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9624 let offset = point(
9625 (text_bounds.size.width - size.width) / 2.,
9626 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9627 );
9628
9629 let origin = text_bounds.origin + offset;
9630 element.prepaint_at(origin, window, cx);
9631 Some((element, origin))
9632 } else {
9633 self.render_edit_prediction_end_of_line_popover(
9634 "Jump to Edit",
9635 editor_snapshot,
9636 visible_row_range,
9637 target_display_point,
9638 line_height,
9639 scroll_pixel_position,
9640 content_origin,
9641 editor_width,
9642 window,
9643 cx,
9644 )
9645 }
9646 }
9647
9648 fn render_edit_prediction_end_of_line_popover(
9649 self: &mut Editor,
9650 label: &'static str,
9651 editor_snapshot: &EditorSnapshot,
9652 visible_row_range: Range<DisplayRow>,
9653 target_display_point: DisplayPoint,
9654 line_height: Pixels,
9655 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9656 content_origin: gpui::Point<Pixels>,
9657 editor_width: Pixels,
9658 window: &mut Window,
9659 cx: &mut App,
9660 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9661 let target_line_end = DisplayPoint::new(
9662 target_display_point.row(),
9663 editor_snapshot.line_len(target_display_point.row()),
9664 );
9665
9666 let mut element = self
9667 .render_edit_prediction_line_popover(label, None, window, cx)
9668 .into_any();
9669
9670 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9671
9672 let line_origin =
9673 self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
9674
9675 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9676 let mut origin = start_point
9677 + line_origin
9678 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9679 origin.x = origin.x.max(content_origin.x);
9680
9681 let max_x = content_origin.x + editor_width - size.width;
9682
9683 if origin.x > max_x {
9684 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9685
9686 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9687 origin.y += offset;
9688 IconName::ArrowUp
9689 } else {
9690 origin.y -= offset;
9691 IconName::ArrowDown
9692 };
9693
9694 element = self
9695 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9696 .into_any();
9697
9698 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9699
9700 origin.x = content_origin.x + editor_width - size.width - px(2.);
9701 }
9702
9703 element.prepaint_at(origin, window, cx);
9704 Some((element, origin))
9705 }
9706
9707 fn render_edit_prediction_diff_popover(
9708 self: &Editor,
9709 text_bounds: &Bounds<Pixels>,
9710 content_origin: gpui::Point<Pixels>,
9711 right_margin: Pixels,
9712 editor_snapshot: &EditorSnapshot,
9713 visible_row_range: Range<DisplayRow>,
9714 line_layouts: &[LineWithInvisibles],
9715 line_height: Pixels,
9716 scroll_position: gpui::Point<ScrollOffset>,
9717 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9718 newest_selection_head: Option<DisplayPoint>,
9719 editor_width: Pixels,
9720 style: &EditorStyle,
9721 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9722 edit_preview: &Option<language::EditPreview>,
9723 snapshot: &language::BufferSnapshot,
9724 window: &mut Window,
9725 cx: &mut App,
9726 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9727 let edit_start = edits
9728 .first()
9729 .unwrap()
9730 .0
9731 .start
9732 .to_display_point(editor_snapshot);
9733 let edit_end = edits
9734 .last()
9735 .unwrap()
9736 .0
9737 .end
9738 .to_display_point(editor_snapshot);
9739
9740 let is_visible = visible_row_range.contains(&edit_start.row())
9741 || visible_row_range.contains(&edit_end.row());
9742 if !is_visible {
9743 return None;
9744 }
9745
9746 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9747 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9748 } else {
9749 // Fallback for providers without edit_preview
9750 crate::edit_prediction_fallback_text(edits, cx)
9751 };
9752
9753 let styled_text = highlighted_edits.to_styled_text(&style.text);
9754 let line_count = highlighted_edits.text.lines().count();
9755
9756 const BORDER_WIDTH: Pixels = px(1.);
9757
9758 let keybind = self.render_edit_prediction_keybind(window, cx);
9759 let has_keybind = keybind.is_some();
9760
9761 let mut element = h_flex()
9762 .items_start()
9763 .child(
9764 h_flex()
9765 .bg(cx.theme().colors().editor_background)
9766 .border(BORDER_WIDTH)
9767 .shadow_xs()
9768 .border_color(cx.theme().colors().border)
9769 .rounded_l_lg()
9770 .when(line_count > 1, |el| el.rounded_br_lg())
9771 .pr_1()
9772 .child(styled_text),
9773 )
9774 .child(
9775 h_flex()
9776 .h(line_height + BORDER_WIDTH * 2.)
9777 .px_1p5()
9778 .gap_1()
9779 // Workaround: For some reason, there's a gap if we don't do this
9780 .ml(-BORDER_WIDTH)
9781 .shadow(vec![gpui::BoxShadow {
9782 color: gpui::black().opacity(0.05),
9783 offset: point(px(1.), px(1.)),
9784 blur_radius: px(2.),
9785 spread_radius: px(0.),
9786 }])
9787 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9788 .border(BORDER_WIDTH)
9789 .border_color(cx.theme().colors().border)
9790 .rounded_r_lg()
9791 .id("edit_prediction_diff_popover_keybind")
9792 .when(!has_keybind, |el| {
9793 let status_colors = cx.theme().status();
9794
9795 el.bg(status_colors.error_background)
9796 .border_color(status_colors.error.opacity(0.6))
9797 .child(Icon::new(IconName::Info).color(Color::Error))
9798 .cursor_default()
9799 .hoverable_tooltip(move |_window, cx| {
9800 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9801 })
9802 })
9803 .children(keybind),
9804 )
9805 .into_any();
9806
9807 let longest_row =
9808 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9809 let longest_line_width = if visible_row_range.contains(&longest_row) {
9810 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9811 } else {
9812 layout_line(
9813 longest_row,
9814 editor_snapshot,
9815 style,
9816 editor_width,
9817 |_| false,
9818 window,
9819 cx,
9820 )
9821 .width
9822 };
9823
9824 let viewport_bounds =
9825 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9826 right: -right_margin,
9827 ..Default::default()
9828 });
9829
9830 let x_after_longest = Pixels::from(
9831 ScrollPixelOffset::from(
9832 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9833 ) - scroll_pixel_position.x,
9834 );
9835
9836 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9837
9838 // Fully visible if it can be displayed within the window (allow overlapping other
9839 // panes). However, this is only allowed if the popover starts within text_bounds.
9840 let can_position_to_the_right = x_after_longest < text_bounds.right()
9841 && x_after_longest + element_bounds.width < viewport_bounds.right();
9842
9843 let mut origin = if can_position_to_the_right {
9844 point(
9845 x_after_longest,
9846 text_bounds.origin.y
9847 + Pixels::from(
9848 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9849 - scroll_pixel_position.y,
9850 ),
9851 )
9852 } else {
9853 let cursor_row = newest_selection_head.map(|head| head.row());
9854 let above_edit = edit_start
9855 .row()
9856 .0
9857 .checked_sub(line_count as u32)
9858 .map(DisplayRow);
9859 let below_edit = Some(edit_end.row() + 1);
9860 let above_cursor =
9861 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9862 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9863
9864 // Place the edit popover adjacent to the edit if there is a location
9865 // available that is onscreen and does not obscure the cursor. Otherwise,
9866 // place it adjacent to the cursor.
9867 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9868 .into_iter()
9869 .flatten()
9870 .find(|&start_row| {
9871 let end_row = start_row + line_count as u32;
9872 visible_row_range.contains(&start_row)
9873 && visible_row_range.contains(&end_row)
9874 && cursor_row
9875 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9876 })?;
9877
9878 content_origin
9879 + point(
9880 Pixels::from(-scroll_pixel_position.x),
9881 Pixels::from(
9882 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9883 ),
9884 )
9885 };
9886
9887 origin.x -= BORDER_WIDTH;
9888
9889 window.with_content_mask(
9890 Some(gpui::ContentMask {
9891 bounds: *text_bounds,
9892 }),
9893 |window| {
9894 window.defer_draw(element, origin, 1, Some(window.content_mask()));
9895 },
9896 );
9897
9898 // Do not return an element, since it will already be drawn due to defer_draw.
9899 None
9900 }
9901
9902 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9903 px(30.)
9904 }
9905
9906 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9907 if self.read_only(cx) {
9908 cx.theme().players().read_only()
9909 } else {
9910 self.style.as_ref().unwrap().local_player
9911 }
9912 }
9913
9914 fn render_edit_prediction_inline_keystroke(
9915 &self,
9916 keystroke: &gpui::KeybindingKeystroke,
9917 modifiers_color: Color,
9918 cx: &App,
9919 ) -> AnyElement {
9920 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9921
9922 h_flex()
9923 .px_0p5()
9924 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9925 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9926 .text_size(TextSize::XSmall.rems(cx))
9927 .child(h_flex().children(ui::render_modifiers(
9928 keystroke.modifiers(),
9929 PlatformStyle::platform(),
9930 Some(modifiers_color),
9931 Some(IconSize::XSmall.rems().into()),
9932 true,
9933 )))
9934 .when(is_platform_style_mac, |parent| {
9935 parent.child(keystroke.key().to_string())
9936 })
9937 .when(!is_platform_style_mac, |parent| {
9938 parent.child(
9939 Key::new(util::capitalize(keystroke.key()), Some(Color::Default))
9940 .size(Some(IconSize::XSmall.rems().into())),
9941 )
9942 })
9943 .into_any()
9944 }
9945
9946 fn render_edit_prediction_popover_keystroke(
9947 &self,
9948 keystroke: &gpui::KeybindingKeystroke,
9949 color: Color,
9950 cx: &App,
9951 ) -> AnyElement {
9952 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9953
9954 if keystroke.modifiers().modified() {
9955 h_flex()
9956 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9957 .when(is_platform_style_mac, |parent| parent.gap_1())
9958 .child(h_flex().children(ui::render_modifiers(
9959 keystroke.modifiers(),
9960 PlatformStyle::platform(),
9961 Some(color),
9962 None,
9963 false,
9964 )))
9965 .into_any()
9966 } else {
9967 Key::new(util::capitalize(keystroke.key()), Some(color))
9968 .size(Some(IconSize::XSmall.rems().into()))
9969 .into_any_element()
9970 }
9971 }
9972
9973 fn render_edit_prediction_keybind(
9974 &self,
9975 window: &mut Window,
9976 cx: &mut App,
9977 ) -> Option<AnyElement> {
9978 let keybind_display =
9979 self.edit_prediction_keybind_display(EditPredictionKeybindSurface::Inline, window, cx);
9980 let keystroke = keybind_display.displayed_keystroke.as_ref()?;
9981
9982 let modifiers_color = if *keystroke.modifiers() == window.modifiers() {
9983 Color::Accent
9984 } else {
9985 Color::Muted
9986 };
9987
9988 Some(self.render_edit_prediction_inline_keystroke(keystroke, modifiers_color, cx))
9989 }
9990
9991 fn render_edit_prediction_line_popover(
9992 &self,
9993 label: impl Into<SharedString>,
9994 icon: Option<IconName>,
9995 window: &mut Window,
9996 cx: &mut App,
9997 ) -> Stateful<Div> {
9998 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9999
10000 let keybind = self.render_edit_prediction_keybind(window, cx);
10001 let has_keybind = keybind.is_some();
10002 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10003
10004 h_flex()
10005 .id("ep-line-popover")
10006 .py_0p5()
10007 .pl_1()
10008 .pr(padding_right)
10009 .gap_1()
10010 .rounded_md()
10011 .border_1()
10012 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10013 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
10014 .shadow_xs()
10015 .when(!has_keybind, |el| {
10016 let status_colors = cx.theme().status();
10017
10018 el.bg(status_colors.error_background)
10019 .border_color(status_colors.error.opacity(0.6))
10020 .pl_2()
10021 .child(Icon::new(icons.error).color(Color::Error))
10022 .cursor_default()
10023 .hoverable_tooltip(move |_window, cx| {
10024 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
10025 })
10026 })
10027 .children(keybind)
10028 .child(
10029 Label::new(label)
10030 .size(LabelSize::Small)
10031 .when(!has_keybind, |el| {
10032 el.color(cx.theme().status().error.into()).strikethrough()
10033 }),
10034 )
10035 .when(!has_keybind, |el| {
10036 el.child(
10037 h_flex().ml_1().child(
10038 Icon::new(IconName::Info)
10039 .size(IconSize::Small)
10040 .color(cx.theme().status().error.into()),
10041 ),
10042 )
10043 })
10044 .when_some(icon, |element, icon| {
10045 element.child(
10046 div()
10047 .mt(px(1.5))
10048 .child(Icon::new(icon).size(IconSize::Small)),
10049 )
10050 })
10051 }
10052
10053 fn render_edit_prediction_jump_outside_popover(
10054 &self,
10055 snapshot: &BufferSnapshot,
10056 window: &mut Window,
10057 cx: &mut App,
10058 ) -> Stateful<Div> {
10059 let keybind = self.render_edit_prediction_keybind(window, cx);
10060 let has_keybind = keybind.is_some();
10061 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10062
10063 let file_name = snapshot
10064 .file()
10065 .map(|file| SharedString::new(file.file_name(cx)))
10066 .unwrap_or(SharedString::new_static("untitled"));
10067
10068 h_flex()
10069 .id("ep-jump-outside-popover")
10070 .py_1()
10071 .px_2()
10072 .gap_1()
10073 .rounded_md()
10074 .border_1()
10075 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10076 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
10077 .shadow_xs()
10078 .when(!has_keybind, |el| {
10079 let status_colors = cx.theme().status();
10080
10081 el.bg(status_colors.error_background)
10082 .border_color(status_colors.error.opacity(0.6))
10083 .pl_2()
10084 .child(Icon::new(icons.error).color(Color::Error))
10085 .cursor_default()
10086 .hoverable_tooltip(move |_window, cx| {
10087 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
10088 })
10089 })
10090 .children(keybind)
10091 .child(
10092 Label::new(file_name)
10093 .size(LabelSize::Small)
10094 .buffer_font(cx)
10095 .when(!has_keybind, |el| {
10096 el.color(cx.theme().status().error.into()).strikethrough()
10097 }),
10098 )
10099 .when(!has_keybind, |el| {
10100 el.child(
10101 h_flex().ml_1().child(
10102 Icon::new(IconName::Info)
10103 .size(IconSize::Small)
10104 .color(cx.theme().status().error.into()),
10105 ),
10106 )
10107 })
10108 .child(
10109 div()
10110 .mt(px(1.5))
10111 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
10112 )
10113 }
10114
10115 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
10116 let accent_color = cx.theme().colors().text_accent;
10117 let editor_bg_color = cx.theme().colors().editor_background;
10118 editor_bg_color.blend(accent_color.opacity(0.1))
10119 }
10120
10121 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
10122 let accent_color = cx.theme().colors().text_accent;
10123 let editor_bg_color = cx.theme().colors().editor_background;
10124 editor_bg_color.blend(accent_color.opacity(0.6))
10125 }
10126 fn get_prediction_provider_icons(
10127 provider: &Option<RegisteredEditPredictionDelegate>,
10128 cx: &App,
10129 ) -> edit_prediction_types::EditPredictionIconSet {
10130 match provider {
10131 Some(provider) => provider.provider.icons(cx),
10132 None => edit_prediction_types::EditPredictionIconSet::new(IconName::ZedPredict),
10133 }
10134 }
10135
10136 fn render_edit_prediction_cursor_popover(
10137 &self,
10138 min_width: Pixels,
10139 max_width: Pixels,
10140 cursor_point: Point,
10141 style: &EditorStyle,
10142 window: &mut Window,
10143 cx: &mut Context<Editor>,
10144 ) -> Option<AnyElement> {
10145 let provider = self.edit_prediction_provider.as_ref()?;
10146 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10147
10148 let is_refreshing = provider.provider.is_refreshing(cx);
10149
10150 fn pending_completion_container(icon: IconName) -> Div {
10151 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
10152 }
10153
10154 let completion = match &self.active_edit_prediction {
10155 Some(prediction) => {
10156 if !self.has_visible_completions_menu() {
10157 const RADIUS: Pixels = px(6.);
10158 const BORDER_WIDTH: Pixels = px(1.);
10159 let keybind_display = self.edit_prediction_keybind_display(
10160 EditPredictionKeybindSurface::CursorPopoverCompact,
10161 window,
10162 cx,
10163 );
10164
10165 return Some(
10166 h_flex()
10167 .elevation_2(cx)
10168 .border(BORDER_WIDTH)
10169 .border_color(cx.theme().colors().border)
10170 .when(keybind_display.missing_accept_keystroke, |el| {
10171 el.border_color(cx.theme().status().error)
10172 })
10173 .rounded(RADIUS)
10174 .rounded_tl(px(0.))
10175 .overflow_hidden()
10176 .child(div().px_1p5().child(match &prediction.completion {
10177 EditPrediction::MoveWithin { target, snapshot } => {
10178 use text::ToPoint as _;
10179 if target.text_anchor.to_point(snapshot).row > cursor_point.row
10180 {
10181 Icon::new(icons.down)
10182 } else {
10183 Icon::new(icons.up)
10184 }
10185 }
10186 EditPrediction::MoveOutside { .. } => {
10187 // TODO [zeta2] custom icon for external jump?
10188 Icon::new(icons.base)
10189 }
10190 EditPrediction::Edit { .. } => Icon::new(icons.base),
10191 }))
10192 .child(
10193 h_flex()
10194 .gap_1()
10195 .py_1()
10196 .px_2()
10197 .rounded_r(RADIUS - BORDER_WIDTH)
10198 .border_l_1()
10199 .border_color(cx.theme().colors().border)
10200 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10201 .when(keybind_display.show_hold_label, |el| {
10202 el.child(
10203 Label::new("Hold")
10204 .size(LabelSize::Small)
10205 .when(
10206 keybind_display.missing_accept_keystroke,
10207 |el| el.strikethrough(),
10208 )
10209 .line_height_style(LineHeightStyle::UiLabel),
10210 )
10211 })
10212 .id("edit_prediction_cursor_popover_keybind")
10213 .when(keybind_display.missing_accept_keystroke, |el| {
10214 let status_colors = cx.theme().status();
10215
10216 el.bg(status_colors.error_background)
10217 .border_color(status_colors.error.opacity(0.6))
10218 .child(Icon::new(IconName::Info).color(Color::Error))
10219 .cursor_default()
10220 .hoverable_tooltip(move |_window, cx| {
10221 cx.new(|_| MissingEditPredictionKeybindingTooltip)
10222 .into()
10223 })
10224 })
10225 .when_some(
10226 keybind_display.displayed_keystroke.as_ref(),
10227 |el, compact_keystroke| {
10228 el.child(self.render_edit_prediction_popover_keystroke(
10229 compact_keystroke,
10230 Color::Default,
10231 cx,
10232 ))
10233 },
10234 ),
10235 )
10236 .into_any(),
10237 );
10238 }
10239
10240 self.render_edit_prediction_cursor_popover_preview(
10241 prediction,
10242 cursor_point,
10243 style,
10244 cx,
10245 )?
10246 }
10247
10248 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
10249 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
10250 stale_completion,
10251 cursor_point,
10252 style,
10253 cx,
10254 )?,
10255
10256 None => pending_completion_container(icons.base)
10257 .child(Label::new("...").size(LabelSize::Small)),
10258 },
10259
10260 None => pending_completion_container(icons.base)
10261 .child(Label::new("...").size(LabelSize::Small)),
10262 };
10263
10264 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
10265 completion
10266 .with_animation(
10267 "loading-completion",
10268 Animation::new(Duration::from_secs(2))
10269 .repeat()
10270 .with_easing(pulsating_between(0.4, 0.8)),
10271 |label, delta| label.opacity(delta),
10272 )
10273 .into_any_element()
10274 } else {
10275 completion.into_any_element()
10276 };
10277
10278 let has_completion = self.active_edit_prediction.is_some();
10279 let keybind_display = self.edit_prediction_keybind_display(
10280 EditPredictionKeybindSurface::CursorPopoverExpanded,
10281 window,
10282 cx,
10283 );
10284
10285 Some(
10286 h_flex()
10287 .min_w(min_width)
10288 .max_w(max_width)
10289 .flex_1()
10290 .elevation_2(cx)
10291 .border_color(cx.theme().colors().border)
10292 .child(
10293 div()
10294 .flex_1()
10295 .py_1()
10296 .px_2()
10297 .overflow_hidden()
10298 .child(completion),
10299 )
10300 .when_some(
10301 keybind_display.displayed_keystroke.as_ref(),
10302 |el, keystroke| {
10303 let key_color = if !has_completion {
10304 Color::Muted
10305 } else {
10306 Color::Default
10307 };
10308
10309 if keybind_display.action == EditPredictionKeybindAction::Preview {
10310 el.child(
10311 h_flex()
10312 .h_full()
10313 .border_l_1()
10314 .rounded_r_lg()
10315 .border_color(cx.theme().colors().border)
10316 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10317 .gap_1()
10318 .py_1()
10319 .px_2()
10320 .child(self.render_edit_prediction_popover_keystroke(
10321 keystroke, key_color, cx,
10322 ))
10323 .child(Label::new("Preview").into_any_element())
10324 .opacity(if has_completion { 1.0 } else { 0.4 }),
10325 )
10326 } else {
10327 el.child(
10328 h_flex()
10329 .h_full()
10330 .border_l_1()
10331 .rounded_r_lg()
10332 .border_color(cx.theme().colors().border)
10333 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10334 .gap_1()
10335 .py_1()
10336 .px_2()
10337 .child(self.render_edit_prediction_popover_keystroke(
10338 keystroke, key_color, cx,
10339 ))
10340 .opacity(if has_completion { 1.0 } else { 0.4 }),
10341 )
10342 }
10343 },
10344 )
10345 .into_any(),
10346 )
10347 }
10348
10349 fn render_edit_prediction_cursor_popover_preview(
10350 &self,
10351 completion: &EditPredictionState,
10352 cursor_point: Point,
10353 style: &EditorStyle,
10354 cx: &mut Context<Editor>,
10355 ) -> Option<Div> {
10356 use text::ToPoint as _;
10357
10358 fn render_relative_row_jump(
10359 prefix: impl Into<String>,
10360 current_row: u32,
10361 target_row: u32,
10362 ) -> Div {
10363 let (row_diff, arrow) = if target_row < current_row {
10364 (current_row - target_row, IconName::ArrowUp)
10365 } else {
10366 (target_row - current_row, IconName::ArrowDown)
10367 };
10368
10369 h_flex()
10370 .child(
10371 Label::new(format!("{}{}", prefix.into(), row_diff))
10372 .color(Color::Muted)
10373 .size(LabelSize::Small),
10374 )
10375 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
10376 }
10377
10378 let supports_jump = self
10379 .edit_prediction_provider
10380 .as_ref()
10381 .map(|provider| provider.provider.supports_jump_to_edit())
10382 .unwrap_or(true);
10383
10384 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10385
10386 match &completion.completion {
10387 EditPrediction::MoveWithin {
10388 target, snapshot, ..
10389 } => {
10390 if !supports_jump {
10391 return None;
10392 }
10393
10394 Some(
10395 h_flex()
10396 .px_2()
10397 .gap_2()
10398 .flex_1()
10399 .child(
10400 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
10401 Icon::new(icons.down)
10402 } else {
10403 Icon::new(icons.up)
10404 },
10405 )
10406 .child(Label::new("Jump to Edit")),
10407 )
10408 }
10409 EditPrediction::MoveOutside { snapshot, .. } => {
10410 let file_name = snapshot
10411 .file()
10412 .map(|file| file.file_name(cx))
10413 .unwrap_or("untitled");
10414 Some(
10415 h_flex()
10416 .px_2()
10417 .gap_2()
10418 .flex_1()
10419 .child(Icon::new(icons.base))
10420 .child(Label::new(format!("Jump to {file_name}"))),
10421 )
10422 }
10423 EditPrediction::Edit {
10424 edits,
10425 edit_preview,
10426 snapshot,
10427 ..
10428 } => {
10429 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
10430
10431 let (highlighted_edits, has_more_lines) =
10432 if let Some(edit_preview) = edit_preview.as_ref() {
10433 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
10434 .first_line_preview()
10435 } else {
10436 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
10437 };
10438
10439 let styled_text = gpui::StyledText::new(highlighted_edits.text)
10440 .with_default_highlights(&style.text, highlighted_edits.highlights);
10441
10442 let preview = h_flex()
10443 .gap_1()
10444 .min_w_16()
10445 .child(styled_text)
10446 .when(has_more_lines, |parent| parent.child("…"));
10447
10448 let left = if supports_jump && first_edit_row != cursor_point.row {
10449 render_relative_row_jump("", cursor_point.row, first_edit_row)
10450 .into_any_element()
10451 } else {
10452 Icon::new(icons.base).into_any_element()
10453 };
10454
10455 Some(
10456 h_flex()
10457 .h_full()
10458 .flex_1()
10459 .gap_2()
10460 .pr_1()
10461 .overflow_x_hidden()
10462 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
10463 .child(left)
10464 .child(preview),
10465 )
10466 }
10467 }
10468 }
10469
10470 pub fn render_context_menu(
10471 &mut self,
10472 max_height_in_lines: u32,
10473 window: &mut Window,
10474 cx: &mut Context<Editor>,
10475 ) -> Option<AnyElement> {
10476 let menu = self.context_menu.borrow();
10477 let menu = menu.as_ref()?;
10478 if !menu.visible() {
10479 return None;
10480 };
10481 self.style
10482 .as_ref()
10483 .map(|style| menu.render(style, max_height_in_lines, window, cx))
10484 }
10485
10486 fn render_context_menu_aside(
10487 &mut self,
10488 max_size: Size<Pixels>,
10489 window: &mut Window,
10490 cx: &mut Context<Editor>,
10491 ) -> Option<AnyElement> {
10492 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
10493 if menu.visible() {
10494 menu.render_aside(max_size, window, cx)
10495 } else {
10496 None
10497 }
10498 })
10499 }
10500
10501 fn hide_context_menu(
10502 &mut self,
10503 window: &mut Window,
10504 cx: &mut Context<Self>,
10505 ) -> Option<CodeContextMenu> {
10506 cx.notify();
10507 self.completion_tasks.clear();
10508 let context_menu = self.context_menu.borrow_mut().take();
10509 self.stale_edit_prediction_in_menu.take();
10510 self.update_visible_edit_prediction(window, cx);
10511 if let Some(CodeContextMenu::Completions(_)) = &context_menu
10512 && let Some(completion_provider) = &self.completion_provider
10513 {
10514 completion_provider.selection_changed(None, window, cx);
10515 }
10516 context_menu
10517 }
10518
10519 fn show_snippet_choices(
10520 &mut self,
10521 choices: &Vec<String>,
10522 selection: Range<Anchor>,
10523 cx: &mut Context<Self>,
10524 ) {
10525 let Some((_, buffer, _)) = self
10526 .buffer()
10527 .read(cx)
10528 .excerpt_containing(selection.start, cx)
10529 else {
10530 return;
10531 };
10532 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
10533 else {
10534 return;
10535 };
10536 if buffer != end_buffer {
10537 log::error!("expected anchor range to have matching buffer IDs");
10538 return;
10539 }
10540
10541 let id = post_inc(&mut self.next_completion_id);
10542 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
10543 let mut context_menu = self.context_menu.borrow_mut();
10544 let old_menu = context_menu.take();
10545 *context_menu = Some(CodeContextMenu::Completions(
10546 CompletionsMenu::new_snippet_choices(
10547 id,
10548 true,
10549 choices,
10550 selection,
10551 buffer,
10552 old_menu.map(|menu| menu.primary_scroll_handle()),
10553 snippet_sort_order,
10554 ),
10555 ));
10556 }
10557
10558 pub fn insert_snippet(
10559 &mut self,
10560 insertion_ranges: &[Range<MultiBufferOffset>],
10561 snippet: Snippet,
10562 window: &mut Window,
10563 cx: &mut Context<Self>,
10564 ) -> Result<()> {
10565 struct Tabstop<T> {
10566 is_end_tabstop: bool,
10567 ranges: Vec<Range<T>>,
10568 choices: Option<Vec<String>>,
10569 }
10570
10571 let tabstops = self.buffer.update(cx, |buffer, cx| {
10572 let snippet_text: Arc<str> = snippet.text.clone().into();
10573 let edits = insertion_ranges
10574 .iter()
10575 .cloned()
10576 .map(|range| (range, snippet_text.clone()));
10577 let autoindent_mode = AutoindentMode::Block {
10578 original_indent_columns: Vec::new(),
10579 };
10580 buffer.edit(edits, Some(autoindent_mode), cx);
10581
10582 let snapshot = &*buffer.read(cx);
10583 let snippet = &snippet;
10584 snippet
10585 .tabstops
10586 .iter()
10587 .map(|tabstop| {
10588 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10589 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10590 });
10591 let mut tabstop_ranges = tabstop
10592 .ranges
10593 .iter()
10594 .flat_map(|tabstop_range| {
10595 let mut delta = 0_isize;
10596 insertion_ranges.iter().map(move |insertion_range| {
10597 let insertion_start = insertion_range.start + delta;
10598 delta += snippet.text.len() as isize
10599 - (insertion_range.end - insertion_range.start) as isize;
10600
10601 let start =
10602 (insertion_start + tabstop_range.start).min(snapshot.len());
10603 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10604 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10605 })
10606 })
10607 .collect::<Vec<_>>();
10608 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10609
10610 Tabstop {
10611 is_end_tabstop,
10612 ranges: tabstop_ranges,
10613 choices: tabstop.choices.clone(),
10614 }
10615 })
10616 .collect::<Vec<_>>()
10617 });
10618 if let Some(tabstop) = tabstops.first() {
10619 self.change_selections(Default::default(), window, cx, |s| {
10620 // Reverse order so that the first range is the newest created selection.
10621 // Completions will use it and autoscroll will prioritize it.
10622 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10623 });
10624
10625 if let Some(choices) = &tabstop.choices
10626 && let Some(selection) = tabstop.ranges.first()
10627 {
10628 self.show_snippet_choices(choices, selection.clone(), cx)
10629 }
10630
10631 // If we're already at the last tabstop and it's at the end of the snippet,
10632 // we're done, we don't need to keep the state around.
10633 if !tabstop.is_end_tabstop {
10634 let choices = tabstops
10635 .iter()
10636 .map(|tabstop| tabstop.choices.clone())
10637 .collect();
10638
10639 let ranges = tabstops
10640 .into_iter()
10641 .map(|tabstop| tabstop.ranges)
10642 .collect::<Vec<_>>();
10643
10644 self.snippet_stack.push(SnippetState {
10645 active_index: 0,
10646 ranges,
10647 choices,
10648 });
10649 }
10650
10651 // Check whether the just-entered snippet ends with an auto-closable bracket.
10652 if self.autoclose_regions.is_empty() {
10653 let snapshot = self.buffer.read(cx).snapshot(cx);
10654 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10655 let selection_head = selection.head();
10656 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10657 continue;
10658 };
10659
10660 let mut bracket_pair = None;
10661 let max_lookup_length = scope
10662 .brackets()
10663 .map(|(pair, _)| {
10664 pair.start
10665 .as_str()
10666 .chars()
10667 .count()
10668 .max(pair.end.as_str().chars().count())
10669 })
10670 .max();
10671 if let Some(max_lookup_length) = max_lookup_length {
10672 let next_text = snapshot
10673 .chars_at(selection_head)
10674 .take(max_lookup_length)
10675 .collect::<String>();
10676 let prev_text = snapshot
10677 .reversed_chars_at(selection_head)
10678 .take(max_lookup_length)
10679 .collect::<String>();
10680
10681 for (pair, enabled) in scope.brackets() {
10682 if enabled
10683 && pair.close
10684 && prev_text.starts_with(pair.start.as_str())
10685 && next_text.starts_with(pair.end.as_str())
10686 {
10687 bracket_pair = Some(pair.clone());
10688 break;
10689 }
10690 }
10691 }
10692
10693 if let Some(pair) = bracket_pair {
10694 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10695 let autoclose_enabled =
10696 self.use_autoclose && snapshot_settings.use_autoclose;
10697 if autoclose_enabled {
10698 let start = snapshot.anchor_after(selection_head);
10699 let end = snapshot.anchor_after(selection_head);
10700 self.autoclose_regions.push(AutocloseRegion {
10701 selection_id: selection.id,
10702 range: start..end,
10703 pair,
10704 });
10705 }
10706 }
10707 }
10708 }
10709 }
10710 Ok(())
10711 }
10712
10713 pub fn move_to_next_snippet_tabstop(
10714 &mut self,
10715 window: &mut Window,
10716 cx: &mut Context<Self>,
10717 ) -> bool {
10718 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10719 }
10720
10721 pub fn move_to_prev_snippet_tabstop(
10722 &mut self,
10723 window: &mut Window,
10724 cx: &mut Context<Self>,
10725 ) -> bool {
10726 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10727 }
10728
10729 pub fn move_to_snippet_tabstop(
10730 &mut self,
10731 bias: Bias,
10732 window: &mut Window,
10733 cx: &mut Context<Self>,
10734 ) -> bool {
10735 if let Some(mut snippet) = self.snippet_stack.pop() {
10736 match bias {
10737 Bias::Left => {
10738 if snippet.active_index > 0 {
10739 snippet.active_index -= 1;
10740 } else {
10741 self.snippet_stack.push(snippet);
10742 return false;
10743 }
10744 }
10745 Bias::Right => {
10746 if snippet.active_index + 1 < snippet.ranges.len() {
10747 snippet.active_index += 1;
10748 } else {
10749 self.snippet_stack.push(snippet);
10750 return false;
10751 }
10752 }
10753 }
10754 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10755 self.change_selections(Default::default(), window, cx, |s| {
10756 // Reverse order so that the first range is the newest created selection.
10757 // Completions will use it and autoscroll will prioritize it.
10758 s.select_ranges(current_ranges.iter().rev().cloned())
10759 });
10760
10761 if let Some(choices) = &snippet.choices[snippet.active_index]
10762 && let Some(selection) = current_ranges.first()
10763 {
10764 self.show_snippet_choices(choices, selection.clone(), cx);
10765 }
10766
10767 // If snippet state is not at the last tabstop, push it back on the stack
10768 if snippet.active_index + 1 < snippet.ranges.len() {
10769 self.snippet_stack.push(snippet);
10770 }
10771 return true;
10772 }
10773 }
10774
10775 false
10776 }
10777
10778 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10779 self.transact(window, cx, |this, window, cx| {
10780 this.select_all(&SelectAll, window, cx);
10781 this.insert("", window, cx);
10782 });
10783 }
10784
10785 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10786 if self.read_only(cx) {
10787 return;
10788 }
10789 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10790 self.transact(window, cx, |this, window, cx| {
10791 this.select_autoclose_pair(window, cx);
10792
10793 let linked_edits = this.linked_edits_for_selections(Arc::from(""), cx);
10794
10795 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10796 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10797 for selection in &mut selections {
10798 if selection.is_empty() {
10799 let old_head = selection.head();
10800 let mut new_head =
10801 movement::left(&display_map, old_head.to_display_point(&display_map))
10802 .to_point(&display_map);
10803 if let Some((buffer, line_buffer_range)) = display_map
10804 .buffer_snapshot()
10805 .buffer_line_for_row(MultiBufferRow(old_head.row))
10806 {
10807 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10808 let indent_len = match indent_size.kind {
10809 IndentKind::Space => {
10810 buffer.settings_at(line_buffer_range.start, cx).tab_size
10811 }
10812 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10813 };
10814 if old_head.column <= indent_size.len && old_head.column > 0 {
10815 let indent_len = indent_len.get();
10816 new_head = cmp::min(
10817 new_head,
10818 MultiBufferPoint::new(
10819 old_head.row,
10820 ((old_head.column - 1) / indent_len) * indent_len,
10821 ),
10822 );
10823 }
10824 }
10825
10826 selection.set_head(new_head, SelectionGoal::None);
10827 }
10828 }
10829
10830 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10831 this.insert("", window, cx);
10832 linked_edits.apply_with_left_expansion(cx);
10833 this.refresh_edit_prediction(true, false, window, cx);
10834 refresh_linked_ranges(this, window, cx);
10835 });
10836 }
10837
10838 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10839 if self.read_only(cx) {
10840 return;
10841 }
10842 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10843 self.transact(window, cx, |this, window, cx| {
10844 this.change_selections(Default::default(), window, cx, |s| {
10845 s.move_with(&mut |map, selection| {
10846 if selection.is_empty() {
10847 let cursor = movement::right(map, selection.head());
10848 selection.end = cursor;
10849 selection.reversed = true;
10850 selection.goal = SelectionGoal::None;
10851 }
10852 })
10853 });
10854 let linked_edits = this.linked_edits_for_selections(Arc::from(""), cx);
10855 this.insert("", window, cx);
10856 linked_edits.apply(cx);
10857 this.refresh_edit_prediction(true, false, window, cx);
10858 refresh_linked_ranges(this, window, cx);
10859 });
10860 }
10861
10862 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10863 if self.mode.is_single_line() {
10864 cx.propagate();
10865 return;
10866 }
10867
10868 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10869 if self.move_to_prev_snippet_tabstop(window, cx) {
10870 return;
10871 }
10872 self.outdent(&Outdent, window, cx);
10873 }
10874
10875 pub fn next_snippet_tabstop(
10876 &mut self,
10877 _: &NextSnippetTabstop,
10878 window: &mut Window,
10879 cx: &mut Context<Self>,
10880 ) {
10881 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10882 cx.propagate();
10883 return;
10884 }
10885
10886 if self.move_to_next_snippet_tabstop(window, cx) {
10887 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10888 return;
10889 }
10890 cx.propagate();
10891 }
10892
10893 pub fn previous_snippet_tabstop(
10894 &mut self,
10895 _: &PreviousSnippetTabstop,
10896 window: &mut Window,
10897 cx: &mut Context<Self>,
10898 ) {
10899 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10900 cx.propagate();
10901 return;
10902 }
10903
10904 if self.move_to_prev_snippet_tabstop(window, cx) {
10905 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10906 return;
10907 }
10908 cx.propagate();
10909 }
10910
10911 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10912 if self.mode.is_single_line() {
10913 cx.propagate();
10914 return;
10915 }
10916
10917 if self.move_to_next_snippet_tabstop(window, cx) {
10918 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10919 return;
10920 }
10921 if self.read_only(cx) {
10922 return;
10923 }
10924 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10925 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10926 let buffer = self.buffer.read(cx);
10927 let snapshot = buffer.snapshot(cx);
10928 let rows_iter = selections.iter().map(|s| s.head().row);
10929 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10930
10931 let has_some_cursor_in_whitespace = selections
10932 .iter()
10933 .filter(|selection| selection.is_empty())
10934 .any(|selection| {
10935 let cursor = selection.head();
10936 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10937 cursor.column < current_indent.len
10938 });
10939
10940 let mut edits = Vec::new();
10941 let mut prev_edited_row = 0;
10942 let mut row_delta = 0;
10943 for selection in &mut selections {
10944 if selection.start.row != prev_edited_row {
10945 row_delta = 0;
10946 }
10947 prev_edited_row = selection.end.row;
10948
10949 // If cursor is after a list prefix, make selection non-empty to trigger line indent
10950 if selection.is_empty() {
10951 let cursor = selection.head();
10952 let settings = buffer.language_settings_at(cursor, cx);
10953 if settings.indent_list_on_tab {
10954 if let Some(language) = snapshot.language_scope_at(Point::new(cursor.row, 0)) {
10955 if is_list_prefix_row(MultiBufferRow(cursor.row), &snapshot, &language) {
10956 row_delta = Self::indent_selection(
10957 buffer, &snapshot, selection, &mut edits, row_delta, cx,
10958 );
10959 continue;
10960 }
10961 }
10962 }
10963 }
10964
10965 // If the selection is non-empty, then increase the indentation of the selected lines.
10966 if !selection.is_empty() {
10967 row_delta =
10968 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10969 continue;
10970 }
10971
10972 let cursor = selection.head();
10973 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10974 if let Some(suggested_indent) =
10975 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10976 {
10977 // Don't do anything if already at suggested indent
10978 // and there is any other cursor which is not
10979 if has_some_cursor_in_whitespace
10980 && cursor.column == current_indent.len
10981 && current_indent.len == suggested_indent.len
10982 {
10983 continue;
10984 }
10985
10986 // Adjust line and move cursor to suggested indent
10987 // if cursor is not at suggested indent
10988 if cursor.column < suggested_indent.len
10989 && cursor.column <= current_indent.len
10990 && current_indent.len <= suggested_indent.len
10991 {
10992 selection.start = Point::new(cursor.row, suggested_indent.len);
10993 selection.end = selection.start;
10994 if row_delta == 0 {
10995 edits.extend(Buffer::edit_for_indent_size_adjustment(
10996 cursor.row,
10997 current_indent,
10998 suggested_indent,
10999 ));
11000 row_delta = suggested_indent.len - current_indent.len;
11001 }
11002 continue;
11003 }
11004
11005 // If current indent is more than suggested indent
11006 // only move cursor to current indent and skip indent
11007 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
11008 selection.start = Point::new(cursor.row, current_indent.len);
11009 selection.end = selection.start;
11010 continue;
11011 }
11012 }
11013
11014 // Otherwise, insert a hard or soft tab.
11015 let settings = buffer.language_settings_at(cursor, cx);
11016 let tab_size = if settings.hard_tabs {
11017 IndentSize::tab()
11018 } else {
11019 let tab_size = settings.tab_size.get();
11020 let indent_remainder = snapshot
11021 .text_for_range(Point::new(cursor.row, 0)..cursor)
11022 .flat_map(str::chars)
11023 .fold(row_delta % tab_size, |counter: u32, c| {
11024 if c == '\t' {
11025 0
11026 } else {
11027 (counter + 1) % tab_size
11028 }
11029 });
11030
11031 let chars_to_next_tab_stop = tab_size - indent_remainder;
11032 IndentSize::spaces(chars_to_next_tab_stop)
11033 };
11034 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
11035 selection.end = selection.start;
11036 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
11037 row_delta += tab_size.len;
11038 }
11039
11040 self.transact(window, cx, |this, window, cx| {
11041 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
11042 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11043 this.refresh_edit_prediction(true, false, window, cx);
11044 });
11045 }
11046
11047 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
11048 if self.read_only(cx) {
11049 return;
11050 }
11051 if self.mode.is_single_line() {
11052 cx.propagate();
11053 return;
11054 }
11055
11056 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11057 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
11058 let mut prev_edited_row = 0;
11059 let mut row_delta = 0;
11060 let mut edits = Vec::new();
11061 let buffer = self.buffer.read(cx);
11062 let snapshot = buffer.snapshot(cx);
11063 for selection in &mut selections {
11064 if selection.start.row != prev_edited_row {
11065 row_delta = 0;
11066 }
11067 prev_edited_row = selection.end.row;
11068
11069 row_delta =
11070 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
11071 }
11072
11073 self.transact(window, cx, |this, window, cx| {
11074 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
11075 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11076 });
11077 }
11078
11079 fn indent_selection(
11080 buffer: &MultiBuffer,
11081 snapshot: &MultiBufferSnapshot,
11082 selection: &mut Selection<Point>,
11083 edits: &mut Vec<(Range<Point>, String)>,
11084 delta_for_start_row: u32,
11085 cx: &App,
11086 ) -> u32 {
11087 let settings = buffer.language_settings_at(selection.start, cx);
11088 let tab_size = settings.tab_size.get();
11089 let indent_kind = if settings.hard_tabs {
11090 IndentKind::Tab
11091 } else {
11092 IndentKind::Space
11093 };
11094 let mut start_row = selection.start.row;
11095 let mut end_row = selection.end.row + 1;
11096
11097 // If a selection ends at the beginning of a line, don't indent
11098 // that last line.
11099 if selection.end.column == 0 && selection.end.row > selection.start.row {
11100 end_row -= 1;
11101 }
11102
11103 // Avoid re-indenting a row that has already been indented by a
11104 // previous selection, but still update this selection's column
11105 // to reflect that indentation.
11106 if delta_for_start_row > 0 {
11107 start_row += 1;
11108 selection.start.column += delta_for_start_row;
11109 if selection.end.row == selection.start.row {
11110 selection.end.column += delta_for_start_row;
11111 }
11112 }
11113
11114 let mut delta_for_end_row = 0;
11115 let has_multiple_rows = start_row + 1 != end_row;
11116 for row in start_row..end_row {
11117 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
11118 let indent_delta = match (current_indent.kind, indent_kind) {
11119 (IndentKind::Space, IndentKind::Space) => {
11120 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
11121 IndentSize::spaces(columns_to_next_tab_stop)
11122 }
11123 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
11124 (_, IndentKind::Tab) => IndentSize::tab(),
11125 };
11126
11127 let start = if has_multiple_rows || current_indent.len < selection.start.column {
11128 0
11129 } else {
11130 selection.start.column
11131 };
11132 let row_start = Point::new(row, start);
11133 edits.push((
11134 row_start..row_start,
11135 indent_delta.chars().collect::<String>(),
11136 ));
11137
11138 // Update this selection's endpoints to reflect the indentation.
11139 if row == selection.start.row {
11140 selection.start.column += indent_delta.len;
11141 }
11142 if row == selection.end.row {
11143 selection.end.column += indent_delta.len;
11144 delta_for_end_row = indent_delta.len;
11145 }
11146 }
11147
11148 if selection.start.row == selection.end.row {
11149 delta_for_start_row + delta_for_end_row
11150 } else {
11151 delta_for_end_row
11152 }
11153 }
11154
11155 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
11156 if self.read_only(cx) {
11157 return;
11158 }
11159 if self.mode.is_single_line() {
11160 cx.propagate();
11161 return;
11162 }
11163
11164 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11165 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11166 let selections = self.selections.all::<Point>(&display_map);
11167 let mut deletion_ranges = Vec::new();
11168 let mut last_outdent = None;
11169 {
11170 let buffer = self.buffer.read(cx);
11171 let snapshot = buffer.snapshot(cx);
11172 for selection in &selections {
11173 let settings = buffer.language_settings_at(selection.start, cx);
11174 let tab_size = settings.tab_size.get();
11175 let mut rows = selection.spanned_rows(false, &display_map);
11176
11177 // Avoid re-outdenting a row that has already been outdented by a
11178 // previous selection.
11179 if let Some(last_row) = last_outdent
11180 && last_row == rows.start
11181 {
11182 rows.start = rows.start.next_row();
11183 }
11184 let has_multiple_rows = rows.len() > 1;
11185 for row in rows.iter_rows() {
11186 let indent_size = snapshot.indent_size_for_line(row);
11187 if indent_size.len > 0 {
11188 let deletion_len = match indent_size.kind {
11189 IndentKind::Space => {
11190 let columns_to_prev_tab_stop = indent_size.len % tab_size;
11191 if columns_to_prev_tab_stop == 0 {
11192 tab_size
11193 } else {
11194 columns_to_prev_tab_stop
11195 }
11196 }
11197 IndentKind::Tab => 1,
11198 };
11199 let start = if has_multiple_rows
11200 || deletion_len > selection.start.column
11201 || indent_size.len < selection.start.column
11202 {
11203 0
11204 } else {
11205 selection.start.column - deletion_len
11206 };
11207 deletion_ranges.push(
11208 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
11209 );
11210 last_outdent = Some(row);
11211 }
11212 }
11213 }
11214 }
11215
11216 self.transact(window, cx, |this, window, cx| {
11217 this.buffer.update(cx, |buffer, cx| {
11218 let empty_str: Arc<str> = Arc::default();
11219 buffer.edit(
11220 deletion_ranges
11221 .into_iter()
11222 .map(|range| (range, empty_str.clone())),
11223 None,
11224 cx,
11225 );
11226 });
11227 let selections = this
11228 .selections
11229 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11230 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11231 });
11232 }
11233
11234 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
11235 if self.read_only(cx) {
11236 return;
11237 }
11238 if self.mode.is_single_line() {
11239 cx.propagate();
11240 return;
11241 }
11242
11243 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11244 let selections = self
11245 .selections
11246 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11247 .into_iter()
11248 .map(|s| s.range());
11249
11250 self.transact(window, cx, |this, window, cx| {
11251 this.buffer.update(cx, |buffer, cx| {
11252 buffer.autoindent_ranges(selections, cx);
11253 });
11254 let selections = this
11255 .selections
11256 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11257 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11258 });
11259 }
11260
11261 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
11262 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11263 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11264 let selections = self.selections.all::<Point>(&display_map);
11265
11266 let mut new_cursors = Vec::new();
11267 let mut edit_ranges = Vec::new();
11268 let mut selections = selections.iter().peekable();
11269 while let Some(selection) = selections.next() {
11270 let mut rows = selection.spanned_rows(false, &display_map);
11271
11272 // Accumulate contiguous regions of rows that we want to delete.
11273 while let Some(next_selection) = selections.peek() {
11274 let next_rows = next_selection.spanned_rows(false, &display_map);
11275 if next_rows.start <= rows.end {
11276 rows.end = next_rows.end;
11277 selections.next().unwrap();
11278 } else {
11279 break;
11280 }
11281 }
11282
11283 let buffer = display_map.buffer_snapshot();
11284 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
11285 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
11286 // If there's a line after the range, delete the \n from the end of the row range
11287 (
11288 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
11289 rows.end,
11290 )
11291 } else {
11292 // If there isn't a line after the range, delete the \n from the line before the
11293 // start of the row range
11294 edit_start = edit_start.saturating_sub_usize(1);
11295 (buffer.len(), rows.start.previous_row())
11296 };
11297
11298 let text_layout_details = self.text_layout_details(window, cx);
11299 let x = display_map.x_for_display_point(
11300 selection.head().to_display_point(&display_map),
11301 &text_layout_details,
11302 );
11303 let row = Point::new(target_row.0, 0)
11304 .to_display_point(&display_map)
11305 .row();
11306 let column = display_map.display_column_for_x(row, x, &text_layout_details);
11307
11308 new_cursors.push((
11309 selection.id,
11310 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
11311 SelectionGoal::None,
11312 ));
11313 edit_ranges.push(edit_start..edit_end);
11314 }
11315
11316 self.transact(window, cx, |this, window, cx| {
11317 let buffer = this.buffer.update(cx, |buffer, cx| {
11318 let empty_str: Arc<str> = Arc::default();
11319 buffer.edit(
11320 edit_ranges
11321 .into_iter()
11322 .map(|range| (range, empty_str.clone())),
11323 None,
11324 cx,
11325 );
11326 buffer.snapshot(cx)
11327 });
11328 let new_selections = new_cursors
11329 .into_iter()
11330 .map(|(id, cursor, goal)| {
11331 let cursor = cursor.to_point(&buffer);
11332 Selection {
11333 id,
11334 start: cursor,
11335 end: cursor,
11336 reversed: false,
11337 goal,
11338 }
11339 })
11340 .collect();
11341
11342 this.change_selections(Default::default(), window, cx, |s| {
11343 s.select(new_selections);
11344 });
11345 });
11346 }
11347
11348 pub fn join_lines_impl(
11349 &mut self,
11350 insert_whitespace: bool,
11351 window: &mut Window,
11352 cx: &mut Context<Self>,
11353 ) {
11354 if self.read_only(cx) {
11355 return;
11356 }
11357 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
11358 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
11359 let start = MultiBufferRow(selection.start.row);
11360 // Treat single line selections as if they include the next line. Otherwise this action
11361 // would do nothing for single line selections individual cursors.
11362 let end = if selection.start.row == selection.end.row {
11363 MultiBufferRow(selection.start.row + 1)
11364 } else if selection.end.column == 0 {
11365 // If the selection ends at the start of a line, it's logically at the end of the
11366 // previous line (plus its newline).
11367 // Don't include the end line unless there's only one line selected.
11368 if selection.start.row + 1 == selection.end.row {
11369 MultiBufferRow(selection.end.row)
11370 } else {
11371 MultiBufferRow(selection.end.row - 1)
11372 }
11373 } else {
11374 MultiBufferRow(selection.end.row)
11375 };
11376
11377 if let Some(last_row_range) = row_ranges.last_mut()
11378 && start <= last_row_range.end
11379 {
11380 last_row_range.end = end;
11381 continue;
11382 }
11383 row_ranges.push(start..end);
11384 }
11385
11386 let snapshot = self.buffer.read(cx).snapshot(cx);
11387 let mut cursor_positions = Vec::new();
11388 for row_range in &row_ranges {
11389 let anchor = snapshot.anchor_before(Point::new(
11390 row_range.end.previous_row().0,
11391 snapshot.line_len(row_range.end.previous_row()),
11392 ));
11393 cursor_positions.push(anchor..anchor);
11394 }
11395
11396 self.transact(window, cx, |this, window, cx| {
11397 for row_range in row_ranges.into_iter().rev() {
11398 for row in row_range.iter_rows().rev() {
11399 let end_of_line = Point::new(row.0, snapshot.line_len(row));
11400 let next_line_row = row.next_row();
11401 let indent = snapshot.indent_size_for_line(next_line_row);
11402 let mut join_start_column = indent.len;
11403
11404 if let Some(language_scope) =
11405 snapshot.language_scope_at(Point::new(next_line_row.0, indent.len))
11406 {
11407 let line_end =
11408 Point::new(next_line_row.0, snapshot.line_len(next_line_row));
11409 let line_text_after_indent = snapshot
11410 .text_for_range(Point::new(next_line_row.0, indent.len)..line_end)
11411 .collect::<String>();
11412
11413 if !line_text_after_indent.is_empty() {
11414 let block_prefix = language_scope
11415 .block_comment()
11416 .map(|c| c.prefix.as_ref())
11417 .filter(|p| !p.is_empty());
11418 let doc_prefix = language_scope
11419 .documentation_comment()
11420 .map(|c| c.prefix.as_ref())
11421 .filter(|p| !p.is_empty());
11422 let all_prefixes = language_scope
11423 .line_comment_prefixes()
11424 .iter()
11425 .map(|p| p.as_ref())
11426 .chain(block_prefix)
11427 .chain(doc_prefix)
11428 .chain(language_scope.unordered_list().iter().map(|p| p.as_ref()));
11429
11430 let mut longest_prefix_len = None;
11431 for prefix in all_prefixes {
11432 let trimmed = prefix.trim_end();
11433 if line_text_after_indent.starts_with(trimmed) {
11434 let candidate_len =
11435 if line_text_after_indent.starts_with(prefix) {
11436 prefix.len()
11437 } else {
11438 trimmed.len()
11439 };
11440 if longest_prefix_len.map_or(true, |len| candidate_len > len) {
11441 longest_prefix_len = Some(candidate_len);
11442 }
11443 }
11444 }
11445
11446 if let Some(prefix_len) = longest_prefix_len {
11447 join_start_column =
11448 join_start_column.saturating_add(prefix_len as u32);
11449 }
11450 }
11451 }
11452
11453 let start_of_next_line = Point::new(next_line_row.0, join_start_column);
11454
11455 let replace = if snapshot.line_len(next_line_row) > join_start_column
11456 && insert_whitespace
11457 {
11458 " "
11459 } else {
11460 ""
11461 };
11462
11463 this.buffer.update(cx, |buffer, cx| {
11464 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
11465 });
11466 }
11467 }
11468
11469 this.change_selections(Default::default(), window, cx, |s| {
11470 s.select_anchor_ranges(cursor_positions)
11471 });
11472 });
11473 }
11474
11475 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
11476 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11477 self.join_lines_impl(true, window, cx);
11478 }
11479
11480 pub fn sort_lines_case_sensitive(
11481 &mut self,
11482 _: &SortLinesCaseSensitive,
11483 window: &mut Window,
11484 cx: &mut Context<Self>,
11485 ) {
11486 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
11487 }
11488
11489 pub fn sort_lines_by_length(
11490 &mut self,
11491 _: &SortLinesByLength,
11492 window: &mut Window,
11493 cx: &mut Context<Self>,
11494 ) {
11495 self.manipulate_immutable_lines(window, cx, |lines| {
11496 lines.sort_by_key(|&line| line.chars().count())
11497 })
11498 }
11499
11500 pub fn sort_lines_case_insensitive(
11501 &mut self,
11502 _: &SortLinesCaseInsensitive,
11503 window: &mut Window,
11504 cx: &mut Context<Self>,
11505 ) {
11506 self.manipulate_immutable_lines(window, cx, |lines| {
11507 lines.sort_by_key(|line| line.to_lowercase())
11508 })
11509 }
11510
11511 pub fn unique_lines_case_insensitive(
11512 &mut self,
11513 _: &UniqueLinesCaseInsensitive,
11514 window: &mut Window,
11515 cx: &mut Context<Self>,
11516 ) {
11517 self.manipulate_immutable_lines(window, cx, |lines| {
11518 let mut seen = HashSet::default();
11519 lines.retain(|line| seen.insert(line.to_lowercase()));
11520 })
11521 }
11522
11523 pub fn unique_lines_case_sensitive(
11524 &mut self,
11525 _: &UniqueLinesCaseSensitive,
11526 window: &mut Window,
11527 cx: &mut Context<Self>,
11528 ) {
11529 self.manipulate_immutable_lines(window, cx, |lines| {
11530 let mut seen = HashSet::default();
11531 lines.retain(|line| seen.insert(*line));
11532 })
11533 }
11534
11535 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
11536 let snapshot = self.buffer.read(cx).snapshot(cx);
11537 for selection in self.selections.disjoint_anchors_arc().iter() {
11538 if snapshot
11539 .language_at(selection.start)
11540 .and_then(|lang| lang.config().wrap_characters.as_ref())
11541 .is_some()
11542 {
11543 return true;
11544 }
11545 }
11546 false
11547 }
11548
11549 fn wrap_selections_in_tag(
11550 &mut self,
11551 _: &WrapSelectionsInTag,
11552 window: &mut Window,
11553 cx: &mut Context<Self>,
11554 ) {
11555 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11556
11557 let snapshot = self.buffer.read(cx).snapshot(cx);
11558
11559 let mut edits = Vec::new();
11560 let mut boundaries = Vec::new();
11561
11562 for selection in self
11563 .selections
11564 .all_adjusted(&self.display_snapshot(cx))
11565 .iter()
11566 {
11567 let Some(wrap_config) = snapshot
11568 .language_at(selection.start)
11569 .and_then(|lang| lang.config().wrap_characters.clone())
11570 else {
11571 continue;
11572 };
11573
11574 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
11575 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
11576
11577 let start_before = snapshot.anchor_before(selection.start);
11578 let end_after = snapshot.anchor_after(selection.end);
11579
11580 edits.push((start_before..start_before, open_tag));
11581 edits.push((end_after..end_after, close_tag));
11582
11583 boundaries.push((
11584 start_before,
11585 end_after,
11586 wrap_config.start_prefix.len(),
11587 wrap_config.end_suffix.len(),
11588 ));
11589 }
11590
11591 if edits.is_empty() {
11592 return;
11593 }
11594
11595 self.transact(window, cx, |this, window, cx| {
11596 let buffer = this.buffer.update(cx, |buffer, cx| {
11597 buffer.edit(edits, None, cx);
11598 buffer.snapshot(cx)
11599 });
11600
11601 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
11602 for (start_before, end_after, start_prefix_len, end_suffix_len) in
11603 boundaries.into_iter()
11604 {
11605 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
11606 let close_offset = end_after
11607 .to_offset(&buffer)
11608 .saturating_sub_usize(end_suffix_len);
11609 new_selections.push(open_offset..open_offset);
11610 new_selections.push(close_offset..close_offset);
11611 }
11612
11613 this.change_selections(Default::default(), window, cx, |s| {
11614 s.select_ranges(new_selections);
11615 });
11616
11617 this.request_autoscroll(Autoscroll::fit(), cx);
11618 });
11619 }
11620
11621 pub fn toggle_read_only(
11622 &mut self,
11623 _: &workspace::ToggleReadOnlyFile,
11624 _: &mut Window,
11625 cx: &mut Context<Self>,
11626 ) {
11627 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
11628 buffer.update(cx, |buffer, cx| {
11629 buffer.set_capability(
11630 match buffer.capability() {
11631 Capability::ReadWrite => Capability::Read,
11632 Capability::Read => Capability::ReadWrite,
11633 Capability::ReadOnly => Capability::ReadOnly,
11634 },
11635 cx,
11636 );
11637 })
11638 }
11639 }
11640
11641 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11642 let Some(project) = self.project.clone() else {
11643 return;
11644 };
11645 let task = self.reload(project, window, cx);
11646 self.detach_and_notify_err(task, window, cx);
11647 }
11648
11649 pub fn restore_file(
11650 &mut self,
11651 _: &::git::RestoreFile,
11652 window: &mut Window,
11653 cx: &mut Context<Self>,
11654 ) {
11655 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11656 let mut buffer_ids = HashSet::default();
11657 let snapshot = self.buffer().read(cx).snapshot(cx);
11658 for selection in self
11659 .selections
11660 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11661 {
11662 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11663 }
11664
11665 let buffer = self.buffer().read(cx);
11666 let ranges = buffer_ids
11667 .into_iter()
11668 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11669 .collect::<Vec<_>>();
11670
11671 self.restore_hunks_in_ranges(ranges, window, cx);
11672 }
11673
11674 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11675 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11676 let selections = self
11677 .selections
11678 .all(&self.display_snapshot(cx))
11679 .into_iter()
11680 .map(|s| s.range())
11681 .collect();
11682 self.restore_hunks_in_ranges(selections, window, cx);
11683 }
11684
11685 /// Restores the diff hunks in the editor's selections and moves the cursor
11686 /// to the next diff hunk. Wraps around to the beginning of the buffer if
11687 /// not all diff hunks are expanded.
11688 pub fn restore_and_next(
11689 &mut self,
11690 _: &::git::RestoreAndNext,
11691 window: &mut Window,
11692 cx: &mut Context<Self>,
11693 ) {
11694 let selections = self
11695 .selections
11696 .all(&self.display_snapshot(cx))
11697 .into_iter()
11698 .map(|selection| selection.range())
11699 .collect();
11700
11701 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11702 self.restore_hunks_in_ranges(selections, window, cx);
11703
11704 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
11705 let wrap_around = !all_diff_hunks_expanded;
11706 let snapshot = self.snapshot(window, cx);
11707 let position = self
11708 .selections
11709 .newest::<Point>(&snapshot.display_snapshot)
11710 .head();
11711
11712 self.go_to_hunk_before_or_after_position(
11713 &snapshot,
11714 position,
11715 Direction::Next,
11716 wrap_around,
11717 window,
11718 cx,
11719 );
11720 }
11721
11722 pub fn restore_hunks_in_ranges(
11723 &mut self,
11724 ranges: Vec<Range<Point>>,
11725 window: &mut Window,
11726 cx: &mut Context<Editor>,
11727 ) {
11728 if self.delegate_stage_and_restore {
11729 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11730 if !hunks.is_empty() {
11731 cx.emit(EditorEvent::RestoreRequested { hunks });
11732 }
11733 return;
11734 }
11735 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11736 self.transact(window, cx, |editor, window, cx| {
11737 editor.restore_diff_hunks(hunks, cx);
11738 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
11739 selections.refresh()
11740 });
11741 });
11742 }
11743
11744 pub(crate) fn restore_diff_hunks(&self, hunks: Vec<MultiBufferDiffHunk>, cx: &mut App) {
11745 let mut revert_changes = HashMap::default();
11746 let chunk_by = hunks.into_iter().chunk_by(|hunk| hunk.buffer_id);
11747 for (buffer_id, hunks) in &chunk_by {
11748 let hunks = hunks.collect::<Vec<_>>();
11749 for hunk in &hunks {
11750 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11751 }
11752 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11753 }
11754 if !revert_changes.is_empty() {
11755 self.buffer().update(cx, |multi_buffer, cx| {
11756 for (buffer_id, changes) in revert_changes {
11757 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
11758 buffer.update(cx, |buffer, cx| {
11759 buffer.edit(
11760 changes
11761 .into_iter()
11762 .map(|(range, text)| (range, text.to_string())),
11763 None,
11764 cx,
11765 );
11766 });
11767 }
11768 }
11769 });
11770 }
11771 }
11772
11773 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11774 if let Some(status) = self
11775 .addons
11776 .iter()
11777 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11778 {
11779 return Some(status);
11780 }
11781 self.project
11782 .as_ref()?
11783 .read(cx)
11784 .status_for_buffer_id(buffer_id, cx)
11785 }
11786
11787 pub fn open_active_item_in_terminal(
11788 &mut self,
11789 _: &OpenInTerminal,
11790 window: &mut Window,
11791 cx: &mut Context<Self>,
11792 ) {
11793 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11794 let project_path = buffer.read(cx).project_path(cx)?;
11795 let project = self.project()?.read(cx);
11796 let entry = project.entry_for_path(&project_path, cx)?;
11797 let parent = match &entry.canonical_path {
11798 Some(canonical_path) => canonical_path.to_path_buf(),
11799 None => project.absolute_path(&project_path, cx)?,
11800 }
11801 .parent()?
11802 .to_path_buf();
11803 Some(parent)
11804 }) {
11805 window.dispatch_action(
11806 OpenTerminal {
11807 working_directory,
11808 local: false,
11809 }
11810 .boxed_clone(),
11811 cx,
11812 );
11813 }
11814 }
11815
11816 fn set_breakpoint_context_menu(
11817 &mut self,
11818 display_row: DisplayRow,
11819 position: Option<Anchor>,
11820 clicked_point: gpui::Point<Pixels>,
11821 window: &mut Window,
11822 cx: &mut Context<Self>,
11823 ) {
11824 let source = self
11825 .buffer
11826 .read(cx)
11827 .snapshot(cx)
11828 .anchor_before(Point::new(display_row.0, 0u32));
11829
11830 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11831
11832 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11833 self,
11834 source,
11835 clicked_point,
11836 context_menu,
11837 window,
11838 cx,
11839 );
11840 }
11841
11842 fn add_edit_breakpoint_block(
11843 &mut self,
11844 anchor: Anchor,
11845 breakpoint: &Breakpoint,
11846 edit_action: BreakpointPromptEditAction,
11847 window: &mut Window,
11848 cx: &mut Context<Self>,
11849 ) {
11850 let weak_editor = cx.weak_entity();
11851 let bp_prompt = cx.new(|cx| {
11852 BreakpointPromptEditor::new(
11853 weak_editor,
11854 anchor,
11855 breakpoint.clone(),
11856 edit_action,
11857 window,
11858 cx,
11859 )
11860 });
11861
11862 let height = bp_prompt.update(cx, |this, cx| {
11863 this.prompt
11864 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11865 });
11866 let cloned_prompt = bp_prompt.clone();
11867 let blocks = vec![BlockProperties {
11868 style: BlockStyle::Sticky,
11869 placement: BlockPlacement::Above(anchor),
11870 height: Some(height),
11871 render: Arc::new(move |cx| {
11872 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11873 cloned_prompt.clone().into_any_element()
11874 }),
11875 priority: 0,
11876 }];
11877
11878 let focus_handle = bp_prompt.focus_handle(cx);
11879 window.focus(&focus_handle, cx);
11880
11881 let block_ids = self.insert_blocks(blocks, None, cx);
11882 bp_prompt.update(cx, |prompt, _| {
11883 prompt.add_block_ids(block_ids);
11884 });
11885 }
11886
11887 pub(crate) fn breakpoint_at_row(
11888 &self,
11889 row: u32,
11890 window: &mut Window,
11891 cx: &mut Context<Self>,
11892 ) -> Option<(Anchor, Breakpoint)> {
11893 let snapshot = self.snapshot(window, cx);
11894 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11895
11896 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11897 }
11898
11899 pub(crate) fn breakpoint_at_anchor(
11900 &self,
11901 breakpoint_position: Anchor,
11902 snapshot: &EditorSnapshot,
11903 cx: &mut Context<Self>,
11904 ) -> Option<(Anchor, Breakpoint)> {
11905 let buffer = self
11906 .buffer
11907 .read(cx)
11908 .buffer_for_anchor(breakpoint_position, cx)?;
11909
11910 let enclosing_excerpt = breakpoint_position.excerpt_id;
11911 let buffer_snapshot = buffer.read(cx).snapshot();
11912
11913 let row = buffer_snapshot
11914 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11915 .row;
11916
11917 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11918 let anchor_end = snapshot
11919 .buffer_snapshot()
11920 .anchor_after(Point::new(row, line_len));
11921
11922 self.breakpoint_store
11923 .as_ref()?
11924 .read_with(cx, |breakpoint_store, cx| {
11925 breakpoint_store
11926 .breakpoints(
11927 &buffer,
11928 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11929 &buffer_snapshot,
11930 cx,
11931 )
11932 .next()
11933 .and_then(|(bp, _)| {
11934 let breakpoint_row = buffer_snapshot
11935 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11936 .row;
11937
11938 if breakpoint_row == row {
11939 snapshot
11940 .buffer_snapshot()
11941 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11942 .map(|position| (position, bp.bp.clone()))
11943 } else {
11944 None
11945 }
11946 })
11947 })
11948 }
11949
11950 pub fn edit_log_breakpoint(
11951 &mut self,
11952 _: &EditLogBreakpoint,
11953 window: &mut Window,
11954 cx: &mut Context<Self>,
11955 ) {
11956 if self.breakpoint_store.is_none() {
11957 return;
11958 }
11959
11960 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11961 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11962 message: None,
11963 state: BreakpointState::Enabled,
11964 condition: None,
11965 hit_condition: None,
11966 });
11967
11968 self.add_edit_breakpoint_block(
11969 anchor,
11970 &breakpoint,
11971 BreakpointPromptEditAction::Log,
11972 window,
11973 cx,
11974 );
11975 }
11976 }
11977
11978 fn breakpoints_at_cursors(
11979 &self,
11980 window: &mut Window,
11981 cx: &mut Context<Self>,
11982 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11983 let snapshot = self.snapshot(window, cx);
11984 let cursors = self
11985 .selections
11986 .disjoint_anchors_arc()
11987 .iter()
11988 .map(|selection| {
11989 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11990
11991 let breakpoint_position = self
11992 .breakpoint_at_row(cursor_position.row, window, cx)
11993 .map(|bp| bp.0)
11994 .unwrap_or_else(|| {
11995 snapshot
11996 .display_snapshot
11997 .buffer_snapshot()
11998 .anchor_after(Point::new(cursor_position.row, 0))
11999 });
12000
12001 let breakpoint = self
12002 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
12003 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
12004
12005 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
12006 })
12007 // 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.
12008 .collect::<HashMap<Anchor, _>>();
12009
12010 cursors.into_iter().collect()
12011 }
12012
12013 pub fn enable_breakpoint(
12014 &mut self,
12015 _: &crate::actions::EnableBreakpoint,
12016 window: &mut Window,
12017 cx: &mut Context<Self>,
12018 ) {
12019 if self.breakpoint_store.is_none() {
12020 return;
12021 }
12022
12023 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
12024 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
12025 continue;
12026 };
12027 self.edit_breakpoint_at_anchor(
12028 anchor,
12029 breakpoint,
12030 BreakpointEditAction::InvertState,
12031 cx,
12032 );
12033 }
12034 }
12035
12036 pub fn align_selections(
12037 &mut self,
12038 _: &crate::actions::AlignSelections,
12039 window: &mut Window,
12040 cx: &mut Context<Self>,
12041 ) {
12042 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12043
12044 let display_snapshot = self.display_snapshot(cx);
12045
12046 struct CursorData {
12047 anchor: Anchor,
12048 point: Point,
12049 }
12050 let cursor_data: Vec<CursorData> = self
12051 .selections
12052 .disjoint_anchors()
12053 .iter()
12054 .map(|selection| {
12055 let anchor = if selection.reversed {
12056 selection.head()
12057 } else {
12058 selection.tail()
12059 };
12060 CursorData {
12061 anchor: anchor,
12062 point: anchor.to_point(&display_snapshot.buffer_snapshot()),
12063 }
12064 })
12065 .collect();
12066
12067 let rows_anchors_count: Vec<usize> = cursor_data
12068 .iter()
12069 .map(|cursor| cursor.point.row)
12070 .chunk_by(|&row| row)
12071 .into_iter()
12072 .map(|(_, group)| group.count())
12073 .collect();
12074 let max_columns = rows_anchors_count.iter().max().copied().unwrap_or(0);
12075 let mut rows_column_offset = vec![0; rows_anchors_count.len()];
12076 let mut edits = Vec::new();
12077
12078 for column_idx in 0..max_columns {
12079 let mut cursor_index = 0;
12080
12081 // Calculate target_column => position that the selections will go
12082 let mut target_column = 0;
12083 for (row_idx, cursor_count) in rows_anchors_count.iter().enumerate() {
12084 // Skip rows that don't have this column
12085 if column_idx >= *cursor_count {
12086 cursor_index += cursor_count;
12087 continue;
12088 }
12089
12090 let point = &cursor_data[cursor_index + column_idx].point;
12091 let adjusted_column = point.column + rows_column_offset[row_idx];
12092 if adjusted_column > target_column {
12093 target_column = adjusted_column;
12094 }
12095 cursor_index += cursor_count;
12096 }
12097
12098 // Collect edits for this column
12099 cursor_index = 0;
12100 for (row_idx, cursor_count) in rows_anchors_count.iter().enumerate() {
12101 // Skip rows that don't have this column
12102 if column_idx >= *cursor_count {
12103 cursor_index += *cursor_count;
12104 continue;
12105 }
12106
12107 let point = &cursor_data[cursor_index + column_idx].point;
12108 let spaces_needed = target_column - point.column - rows_column_offset[row_idx];
12109 if spaces_needed > 0 {
12110 let anchor = cursor_data[cursor_index + column_idx]
12111 .anchor
12112 .bias_left(&display_snapshot);
12113 edits.push((anchor..anchor, " ".repeat(spaces_needed as usize)));
12114 }
12115 rows_column_offset[row_idx] += spaces_needed;
12116
12117 cursor_index += *cursor_count;
12118 }
12119 }
12120
12121 if !edits.is_empty() {
12122 self.transact(window, cx, |editor, _window, cx| {
12123 editor.edit(edits, cx);
12124 });
12125 }
12126 }
12127
12128 pub fn disable_breakpoint(
12129 &mut self,
12130 _: &crate::actions::DisableBreakpoint,
12131 window: &mut Window,
12132 cx: &mut Context<Self>,
12133 ) {
12134 if self.breakpoint_store.is_none() {
12135 return;
12136 }
12137
12138 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
12139 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
12140 continue;
12141 };
12142 self.edit_breakpoint_at_anchor(
12143 anchor,
12144 breakpoint,
12145 BreakpointEditAction::InvertState,
12146 cx,
12147 );
12148 }
12149 }
12150
12151 pub fn toggle_breakpoint(
12152 &mut self,
12153 _: &crate::actions::ToggleBreakpoint,
12154 window: &mut Window,
12155 cx: &mut Context<Self>,
12156 ) {
12157 if self.breakpoint_store.is_none() {
12158 return;
12159 }
12160
12161 let snapshot = self.snapshot(window, cx);
12162 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
12163 if self.gutter_breakpoint_indicator.0.is_some() {
12164 let display_row = anchor
12165 .to_point(snapshot.buffer_snapshot())
12166 .to_display_point(&snapshot.display_snapshot)
12167 .row();
12168 self.update_breakpoint_collision_on_toggle(
12169 display_row,
12170 &BreakpointEditAction::Toggle,
12171 );
12172 }
12173
12174 if let Some(breakpoint) = breakpoint {
12175 self.edit_breakpoint_at_anchor(
12176 anchor,
12177 breakpoint,
12178 BreakpointEditAction::Toggle,
12179 cx,
12180 );
12181 } else {
12182 self.edit_breakpoint_at_anchor(
12183 anchor,
12184 Breakpoint::new_standard(),
12185 BreakpointEditAction::Toggle,
12186 cx,
12187 );
12188 }
12189 }
12190 }
12191
12192 fn update_breakpoint_collision_on_toggle(
12193 &mut self,
12194 display_row: DisplayRow,
12195 edit_action: &BreakpointEditAction,
12196 ) {
12197 if let Some(ref mut breakpoint_indicator) = self.gutter_breakpoint_indicator.0 {
12198 if breakpoint_indicator.display_row == display_row
12199 && matches!(edit_action, BreakpointEditAction::Toggle)
12200 {
12201 breakpoint_indicator.collides_with_existing_breakpoint =
12202 !breakpoint_indicator.collides_with_existing_breakpoint;
12203 }
12204 }
12205 }
12206
12207 pub fn edit_breakpoint_at_anchor(
12208 &mut self,
12209 breakpoint_position: Anchor,
12210 breakpoint: Breakpoint,
12211 edit_action: BreakpointEditAction,
12212 cx: &mut Context<Self>,
12213 ) {
12214 let Some(breakpoint_store) = &self.breakpoint_store else {
12215 return;
12216 };
12217
12218 let Some(buffer) = self
12219 .buffer
12220 .read(cx)
12221 .buffer_for_anchor(breakpoint_position, cx)
12222 else {
12223 return;
12224 };
12225
12226 breakpoint_store.update(cx, |breakpoint_store, cx| {
12227 breakpoint_store.toggle_breakpoint(
12228 buffer,
12229 BreakpointWithPosition {
12230 position: breakpoint_position.text_anchor,
12231 bp: breakpoint,
12232 },
12233 edit_action,
12234 cx,
12235 );
12236 });
12237
12238 cx.notify();
12239 }
12240
12241 #[cfg(any(test, feature = "test-support"))]
12242 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
12243 self.breakpoint_store.clone()
12244 }
12245
12246 pub fn prepare_restore_change(
12247 &self,
12248 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
12249 hunk: &MultiBufferDiffHunk,
12250 cx: &mut App,
12251 ) -> Option<()> {
12252 if hunk.is_created_file() {
12253 return None;
12254 }
12255 let buffer = self.buffer.read(cx);
12256 let diff = buffer.diff_for(hunk.buffer_id)?;
12257 let buffer = buffer.buffer(hunk.buffer_id)?;
12258 let buffer = buffer.read(cx);
12259 let original_text = diff
12260 .read(cx)
12261 .base_text(cx)
12262 .as_rope()
12263 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
12264 let buffer_snapshot = buffer.snapshot();
12265 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
12266 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
12267 probe
12268 .0
12269 .start
12270 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
12271 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
12272 }) {
12273 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
12274 Some(())
12275 } else {
12276 None
12277 }
12278 }
12279
12280 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
12281 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
12282 }
12283
12284 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
12285 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
12286 }
12287
12288 pub fn rotate_selections_forward(
12289 &mut self,
12290 _: &RotateSelectionsForward,
12291 window: &mut Window,
12292 cx: &mut Context<Self>,
12293 ) {
12294 self.rotate_selections(window, cx, false)
12295 }
12296
12297 pub fn rotate_selections_backward(
12298 &mut self,
12299 _: &RotateSelectionsBackward,
12300 window: &mut Window,
12301 cx: &mut Context<Self>,
12302 ) {
12303 self.rotate_selections(window, cx, true)
12304 }
12305
12306 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
12307 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12308 let display_snapshot = self.display_snapshot(cx);
12309 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
12310
12311 if selections.len() < 2 {
12312 return;
12313 }
12314
12315 let (edits, new_selections) = {
12316 let buffer = self.buffer.read(cx).read(cx);
12317 let has_selections = selections.iter().any(|s| !s.is_empty());
12318 if has_selections {
12319 let mut selected_texts: Vec<String> = selections
12320 .iter()
12321 .map(|selection| {
12322 buffer
12323 .text_for_range(selection.start..selection.end)
12324 .collect()
12325 })
12326 .collect();
12327
12328 if reverse {
12329 selected_texts.rotate_left(1);
12330 } else {
12331 selected_texts.rotate_right(1);
12332 }
12333
12334 let mut offset_delta: i64 = 0;
12335 let mut new_selections = Vec::new();
12336 let edits: Vec<_> = selections
12337 .iter()
12338 .zip(selected_texts.iter())
12339 .map(|(selection, new_text)| {
12340 let old_len = (selection.end.0 - selection.start.0) as i64;
12341 let new_len = new_text.len() as i64;
12342 let adjusted_start =
12343 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
12344 let adjusted_end =
12345 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
12346
12347 new_selections.push(Selection {
12348 id: selection.id,
12349 start: adjusted_start,
12350 end: adjusted_end,
12351 reversed: selection.reversed,
12352 goal: selection.goal,
12353 });
12354
12355 offset_delta += new_len - old_len;
12356 (selection.start..selection.end, new_text.clone())
12357 })
12358 .collect();
12359 (edits, new_selections)
12360 } else {
12361 let mut all_rows: Vec<u32> = selections
12362 .iter()
12363 .map(|selection| buffer.offset_to_point(selection.start).row)
12364 .collect();
12365 all_rows.sort_unstable();
12366 all_rows.dedup();
12367
12368 if all_rows.len() < 2 {
12369 return;
12370 }
12371
12372 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
12373 .iter()
12374 .map(|&row| {
12375 let start = Point::new(row, 0);
12376 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12377 buffer.point_to_offset(start)..buffer.point_to_offset(end)
12378 })
12379 .collect();
12380
12381 let mut line_texts: Vec<String> = line_ranges
12382 .iter()
12383 .map(|range| buffer.text_for_range(range.clone()).collect())
12384 .collect();
12385
12386 if reverse {
12387 line_texts.rotate_left(1);
12388 } else {
12389 line_texts.rotate_right(1);
12390 }
12391
12392 let edits = line_ranges
12393 .iter()
12394 .zip(line_texts.iter())
12395 .map(|(range, new_text)| (range.clone(), new_text.clone()))
12396 .collect();
12397
12398 let num_rows = all_rows.len();
12399 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
12400 .iter()
12401 .enumerate()
12402 .map(|(i, &row)| (row, i))
12403 .collect();
12404
12405 // Compute new line start offsets after rotation (handles CRLF)
12406 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
12407 let first_line_start = line_ranges[0].start.0;
12408 let mut new_line_starts: Vec<usize> = vec![first_line_start];
12409 for text in line_texts.iter().take(num_rows - 1) {
12410 let prev_start = *new_line_starts.last().unwrap();
12411 new_line_starts.push(prev_start + text.len() + newline_len);
12412 }
12413
12414 let new_selections = selections
12415 .iter()
12416 .map(|selection| {
12417 let point = buffer.offset_to_point(selection.start);
12418 let old_index = row_to_index[&point.row];
12419 let new_index = if reverse {
12420 (old_index + num_rows - 1) % num_rows
12421 } else {
12422 (old_index + 1) % num_rows
12423 };
12424 let new_offset =
12425 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
12426 Selection {
12427 id: selection.id,
12428 start: new_offset,
12429 end: new_offset,
12430 reversed: selection.reversed,
12431 goal: selection.goal,
12432 }
12433 })
12434 .collect();
12435
12436 (edits, new_selections)
12437 }
12438 };
12439
12440 self.transact(window, cx, |this, window, cx| {
12441 this.buffer.update(cx, |buffer, cx| {
12442 buffer.edit(edits, None, cx);
12443 });
12444 this.change_selections(Default::default(), window, cx, |s| {
12445 s.select(new_selections);
12446 });
12447 });
12448 }
12449
12450 fn manipulate_lines<M>(
12451 &mut self,
12452 window: &mut Window,
12453 cx: &mut Context<Self>,
12454 mut manipulate: M,
12455 ) where
12456 M: FnMut(&str) -> LineManipulationResult,
12457 {
12458 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12459
12460 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12461 let buffer = self.buffer.read(cx).snapshot(cx);
12462
12463 let mut edits = Vec::new();
12464
12465 let selections = self.selections.all::<Point>(&display_map);
12466 let mut selections = selections.iter().peekable();
12467 let mut contiguous_row_selections = Vec::new();
12468 let mut new_selections = Vec::new();
12469 let mut added_lines = 0;
12470 let mut removed_lines = 0;
12471
12472 while let Some(selection) = selections.next() {
12473 let (start_row, end_row) = consume_contiguous_rows(
12474 &mut contiguous_row_selections,
12475 selection,
12476 &display_map,
12477 &mut selections,
12478 );
12479
12480 let start_point = Point::new(start_row.0, 0);
12481 let end_point = Point::new(
12482 end_row.previous_row().0,
12483 buffer.line_len(end_row.previous_row()),
12484 );
12485 let text = buffer
12486 .text_for_range(start_point..end_point)
12487 .collect::<String>();
12488
12489 let LineManipulationResult {
12490 new_text,
12491 line_count_before,
12492 line_count_after,
12493 } = manipulate(&text);
12494
12495 edits.push((start_point..end_point, new_text));
12496
12497 // Selections must change based on added and removed line count
12498 let start_row =
12499 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
12500 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
12501 new_selections.push(Selection {
12502 id: selection.id,
12503 start: start_row,
12504 end: end_row,
12505 goal: SelectionGoal::None,
12506 reversed: selection.reversed,
12507 });
12508
12509 if line_count_after > line_count_before {
12510 added_lines += line_count_after - line_count_before;
12511 } else if line_count_before > line_count_after {
12512 removed_lines += line_count_before - line_count_after;
12513 }
12514 }
12515
12516 self.transact(window, cx, |this, window, cx| {
12517 let buffer = this.buffer.update(cx, |buffer, cx| {
12518 buffer.edit(edits, None, cx);
12519 buffer.snapshot(cx)
12520 });
12521
12522 // Recalculate offsets on newly edited buffer
12523 let new_selections = new_selections
12524 .iter()
12525 .map(|s| {
12526 let start_point = Point::new(s.start.0, 0);
12527 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
12528 Selection {
12529 id: s.id,
12530 start: buffer.point_to_offset(start_point),
12531 end: buffer.point_to_offset(end_point),
12532 goal: s.goal,
12533 reversed: s.reversed,
12534 }
12535 })
12536 .collect();
12537
12538 this.change_selections(Default::default(), window, cx, |s| {
12539 s.select(new_selections);
12540 });
12541
12542 this.request_autoscroll(Autoscroll::fit(), cx);
12543 });
12544 }
12545
12546 fn manipulate_immutable_lines<Fn>(
12547 &mut self,
12548 window: &mut Window,
12549 cx: &mut Context<Self>,
12550 mut callback: Fn,
12551 ) where
12552 Fn: FnMut(&mut Vec<&str>),
12553 {
12554 self.manipulate_lines(window, cx, |text| {
12555 let mut lines: Vec<&str> = text.split('\n').collect();
12556 let line_count_before = lines.len();
12557
12558 callback(&mut lines);
12559
12560 LineManipulationResult {
12561 new_text: lines.join("\n"),
12562 line_count_before,
12563 line_count_after: lines.len(),
12564 }
12565 });
12566 }
12567
12568 fn manipulate_mutable_lines<Fn>(
12569 &mut self,
12570 window: &mut Window,
12571 cx: &mut Context<Self>,
12572 mut callback: Fn,
12573 ) where
12574 Fn: FnMut(&mut Vec<Cow<'_, str>>),
12575 {
12576 self.manipulate_lines(window, cx, |text| {
12577 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
12578 let line_count_before = lines.len();
12579
12580 callback(&mut lines);
12581
12582 LineManipulationResult {
12583 new_text: lines.join("\n"),
12584 line_count_before,
12585 line_count_after: lines.len(),
12586 }
12587 });
12588 }
12589
12590 pub fn convert_indentation_to_spaces(
12591 &mut self,
12592 _: &ConvertIndentationToSpaces,
12593 window: &mut Window,
12594 cx: &mut Context<Self>,
12595 ) {
12596 let settings = self.buffer.read(cx).language_settings(cx);
12597 let tab_size = settings.tab_size.get() as usize;
12598
12599 self.manipulate_mutable_lines(window, cx, |lines| {
12600 // Allocates a reasonably sized scratch buffer once for the whole loop
12601 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12602 // Avoids recomputing spaces that could be inserted many times
12603 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12604 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12605 .collect();
12606
12607 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12608 let mut chars = line.as_ref().chars();
12609 let mut col = 0;
12610 let mut changed = false;
12611
12612 for ch in chars.by_ref() {
12613 match ch {
12614 ' ' => {
12615 reindented_line.push(' ');
12616 col += 1;
12617 }
12618 '\t' => {
12619 // \t are converted to spaces depending on the current column
12620 let spaces_len = tab_size - (col % tab_size);
12621 reindented_line.extend(&space_cache[spaces_len - 1]);
12622 col += spaces_len;
12623 changed = true;
12624 }
12625 _ => {
12626 // If we dont append before break, the character is consumed
12627 reindented_line.push(ch);
12628 break;
12629 }
12630 }
12631 }
12632
12633 if !changed {
12634 reindented_line.clear();
12635 continue;
12636 }
12637 // Append the rest of the line and replace old reference with new one
12638 reindented_line.extend(chars);
12639 *line = Cow::Owned(reindented_line.clone());
12640 reindented_line.clear();
12641 }
12642 });
12643 }
12644
12645 pub fn convert_indentation_to_tabs(
12646 &mut self,
12647 _: &ConvertIndentationToTabs,
12648 window: &mut Window,
12649 cx: &mut Context<Self>,
12650 ) {
12651 let settings = self.buffer.read(cx).language_settings(cx);
12652 let tab_size = settings.tab_size.get() as usize;
12653
12654 self.manipulate_mutable_lines(window, cx, |lines| {
12655 // Allocates a reasonably sized buffer once for the whole loop
12656 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12657 // Avoids recomputing spaces that could be inserted many times
12658 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12659 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12660 .collect();
12661
12662 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12663 let mut chars = line.chars();
12664 let mut spaces_count = 0;
12665 let mut first_non_indent_char = None;
12666 let mut changed = false;
12667
12668 for ch in chars.by_ref() {
12669 match ch {
12670 ' ' => {
12671 // Keep track of spaces. Append \t when we reach tab_size
12672 spaces_count += 1;
12673 changed = true;
12674 if spaces_count == tab_size {
12675 reindented_line.push('\t');
12676 spaces_count = 0;
12677 }
12678 }
12679 '\t' => {
12680 reindented_line.push('\t');
12681 spaces_count = 0;
12682 }
12683 _ => {
12684 // Dont append it yet, we might have remaining spaces
12685 first_non_indent_char = Some(ch);
12686 break;
12687 }
12688 }
12689 }
12690
12691 if !changed {
12692 reindented_line.clear();
12693 continue;
12694 }
12695 // Remaining spaces that didn't make a full tab stop
12696 if spaces_count > 0 {
12697 reindented_line.extend(&space_cache[spaces_count - 1]);
12698 }
12699 // If we consume an extra character that was not indentation, add it back
12700 if let Some(extra_char) = first_non_indent_char {
12701 reindented_line.push(extra_char);
12702 }
12703 // Append the rest of the line and replace old reference with new one
12704 reindented_line.extend(chars);
12705 *line = Cow::Owned(reindented_line.clone());
12706 reindented_line.clear();
12707 }
12708 });
12709 }
12710
12711 pub fn convert_to_upper_case(
12712 &mut self,
12713 _: &ConvertToUpperCase,
12714 window: &mut Window,
12715 cx: &mut Context<Self>,
12716 ) {
12717 self.manipulate_text(window, cx, |text| text.to_uppercase())
12718 }
12719
12720 pub fn convert_to_lower_case(
12721 &mut self,
12722 _: &ConvertToLowerCase,
12723 window: &mut Window,
12724 cx: &mut Context<Self>,
12725 ) {
12726 self.manipulate_text(window, cx, |text| text.to_lowercase())
12727 }
12728
12729 pub fn convert_to_title_case(
12730 &mut self,
12731 _: &ConvertToTitleCase,
12732 window: &mut Window,
12733 cx: &mut Context<Self>,
12734 ) {
12735 self.manipulate_text(window, cx, |text| {
12736 Self::convert_text_case(text, Case::Title)
12737 })
12738 }
12739
12740 pub fn convert_to_snake_case(
12741 &mut self,
12742 _: &ConvertToSnakeCase,
12743 window: &mut Window,
12744 cx: &mut Context<Self>,
12745 ) {
12746 self.manipulate_text(window, cx, |text| {
12747 Self::convert_text_case(text, Case::Snake)
12748 })
12749 }
12750
12751 pub fn convert_to_kebab_case(
12752 &mut self,
12753 _: &ConvertToKebabCase,
12754 window: &mut Window,
12755 cx: &mut Context<Self>,
12756 ) {
12757 self.manipulate_text(window, cx, |text| {
12758 Self::convert_text_case(text, Case::Kebab)
12759 })
12760 }
12761
12762 pub fn convert_to_upper_camel_case(
12763 &mut self,
12764 _: &ConvertToUpperCamelCase,
12765 window: &mut Window,
12766 cx: &mut Context<Self>,
12767 ) {
12768 self.manipulate_text(window, cx, |text| {
12769 Self::convert_text_case(text, Case::UpperCamel)
12770 })
12771 }
12772
12773 pub fn convert_to_lower_camel_case(
12774 &mut self,
12775 _: &ConvertToLowerCamelCase,
12776 window: &mut Window,
12777 cx: &mut Context<Self>,
12778 ) {
12779 self.manipulate_text(window, cx, |text| {
12780 Self::convert_text_case(text, Case::Camel)
12781 })
12782 }
12783
12784 pub fn convert_to_opposite_case(
12785 &mut self,
12786 _: &ConvertToOppositeCase,
12787 window: &mut Window,
12788 cx: &mut Context<Self>,
12789 ) {
12790 self.manipulate_text(window, cx, |text| {
12791 text.chars()
12792 .fold(String::with_capacity(text.len()), |mut t, c| {
12793 if c.is_uppercase() {
12794 t.extend(c.to_lowercase());
12795 } else {
12796 t.extend(c.to_uppercase());
12797 }
12798 t
12799 })
12800 })
12801 }
12802
12803 pub fn convert_to_sentence_case(
12804 &mut self,
12805 _: &ConvertToSentenceCase,
12806 window: &mut Window,
12807 cx: &mut Context<Self>,
12808 ) {
12809 self.manipulate_text(window, cx, |text| {
12810 Self::convert_text_case(text, Case::Sentence)
12811 })
12812 }
12813
12814 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
12815 self.manipulate_text(window, cx, |text| {
12816 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
12817 if has_upper_case_characters {
12818 text.to_lowercase()
12819 } else {
12820 text.to_uppercase()
12821 }
12822 })
12823 }
12824
12825 pub fn convert_to_rot13(
12826 &mut self,
12827 _: &ConvertToRot13,
12828 window: &mut Window,
12829 cx: &mut Context<Self>,
12830 ) {
12831 self.manipulate_text(window, cx, |text| {
12832 text.chars()
12833 .map(|c| match c {
12834 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
12835 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
12836 _ => c,
12837 })
12838 .collect()
12839 })
12840 }
12841
12842 fn convert_text_case(text: &str, case: Case) -> String {
12843 text.lines()
12844 .map(|line| {
12845 let trimmed_start = line.trim_start();
12846 let leading = &line[..line.len() - trimmed_start.len()];
12847 let trimmed = trimmed_start.trim_end();
12848 let trailing = &trimmed_start[trimmed.len()..];
12849 format!("{}{}{}", leading, trimmed.to_case(case), trailing)
12850 })
12851 .join("\n")
12852 }
12853
12854 pub fn convert_to_rot47(
12855 &mut self,
12856 _: &ConvertToRot47,
12857 window: &mut Window,
12858 cx: &mut Context<Self>,
12859 ) {
12860 self.manipulate_text(window, cx, |text| {
12861 text.chars()
12862 .map(|c| {
12863 let code_point = c as u32;
12864 if code_point >= 33 && code_point <= 126 {
12865 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
12866 }
12867 c
12868 })
12869 .collect()
12870 })
12871 }
12872
12873 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
12874 where
12875 Fn: FnMut(&str) -> String,
12876 {
12877 let buffer = self.buffer.read(cx).snapshot(cx);
12878
12879 let mut new_selections = Vec::new();
12880 let mut edits = Vec::new();
12881 let mut selection_adjustment = 0isize;
12882
12883 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
12884 let selection_is_empty = selection.is_empty();
12885
12886 let (start, end) = if selection_is_empty {
12887 let (word_range, _) = buffer.surrounding_word(selection.start, None);
12888 (word_range.start, word_range.end)
12889 } else {
12890 (
12891 buffer.point_to_offset(selection.start),
12892 buffer.point_to_offset(selection.end),
12893 )
12894 };
12895
12896 let text = buffer.text_for_range(start..end).collect::<String>();
12897 let old_length = text.len() as isize;
12898 let text = callback(&text);
12899
12900 new_selections.push(Selection {
12901 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
12902 end: MultiBufferOffset(
12903 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
12904 ),
12905 goal: SelectionGoal::None,
12906 id: selection.id,
12907 reversed: selection.reversed,
12908 });
12909
12910 selection_adjustment += old_length - text.len() as isize;
12911
12912 edits.push((start..end, text));
12913 }
12914
12915 self.transact(window, cx, |this, window, cx| {
12916 this.buffer.update(cx, |buffer, cx| {
12917 buffer.edit(edits, None, cx);
12918 });
12919
12920 this.change_selections(Default::default(), window, cx, |s| {
12921 s.select(new_selections);
12922 });
12923
12924 this.request_autoscroll(Autoscroll::fit(), cx);
12925 });
12926 }
12927
12928 pub fn move_selection_on_drop(
12929 &mut self,
12930 selection: &Selection<Anchor>,
12931 target: DisplayPoint,
12932 is_cut: bool,
12933 window: &mut Window,
12934 cx: &mut Context<Self>,
12935 ) {
12936 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12937 let buffer = display_map.buffer_snapshot();
12938 let mut edits = Vec::new();
12939 let insert_point = display_map
12940 .clip_point(target, Bias::Left)
12941 .to_point(&display_map);
12942 let text = buffer
12943 .text_for_range(selection.start..selection.end)
12944 .collect::<String>();
12945 if is_cut {
12946 edits.push(((selection.start..selection.end), String::new()));
12947 }
12948 let insert_anchor = buffer.anchor_before(insert_point);
12949 edits.push(((insert_anchor..insert_anchor), text));
12950 let last_edit_start = insert_anchor.bias_left(buffer);
12951 let last_edit_end = insert_anchor.bias_right(buffer);
12952 self.transact(window, cx, |this, window, cx| {
12953 this.buffer.update(cx, |buffer, cx| {
12954 buffer.edit(edits, None, cx);
12955 });
12956 this.change_selections(Default::default(), window, cx, |s| {
12957 s.select_anchor_ranges([last_edit_start..last_edit_end]);
12958 });
12959 });
12960 }
12961
12962 pub fn clear_selection_drag_state(&mut self) {
12963 self.selection_drag_state = SelectionDragState::None;
12964 }
12965
12966 pub fn duplicate(
12967 &mut self,
12968 upwards: bool,
12969 whole_lines: bool,
12970 window: &mut Window,
12971 cx: &mut Context<Self>,
12972 ) {
12973 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12974
12975 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12976 let buffer = display_map.buffer_snapshot();
12977 let selections = self.selections.all::<Point>(&display_map);
12978
12979 let mut edits = Vec::new();
12980 let mut selections_iter = selections.iter().peekable();
12981 while let Some(selection) = selections_iter.next() {
12982 let mut rows = selection.spanned_rows(false, &display_map);
12983 // duplicate line-wise
12984 if whole_lines || selection.start == selection.end {
12985 // Avoid duplicating the same lines twice.
12986 while let Some(next_selection) = selections_iter.peek() {
12987 let next_rows = next_selection.spanned_rows(false, &display_map);
12988 if next_rows.start < rows.end {
12989 rows.end = next_rows.end;
12990 selections_iter.next().unwrap();
12991 } else {
12992 break;
12993 }
12994 }
12995
12996 // Copy the text from the selected row region and splice it either at the start
12997 // or end of the region.
12998 let start = Point::new(rows.start.0, 0);
12999 let end = Point::new(
13000 rows.end.previous_row().0,
13001 buffer.line_len(rows.end.previous_row()),
13002 );
13003
13004 let mut text = buffer.text_for_range(start..end).collect::<String>();
13005
13006 let insert_location = if upwards {
13007 // When duplicating upward, we need to insert before the current line.
13008 // If we're on the last line and it doesn't end with a newline,
13009 // we need to add a newline before the duplicated content.
13010 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
13011 && buffer.max_point().column > 0
13012 && !text.ends_with('\n');
13013
13014 if needs_leading_newline {
13015 text.insert(0, '\n');
13016 end
13017 } else {
13018 text.push('\n');
13019 Point::new(rows.start.0, 0)
13020 }
13021 } else {
13022 text.push('\n');
13023 start
13024 };
13025 edits.push((insert_location..insert_location, text));
13026 } else {
13027 // duplicate character-wise
13028 let start = selection.start;
13029 let end = selection.end;
13030 let text = buffer.text_for_range(start..end).collect::<String>();
13031 edits.push((selection.end..selection.end, text));
13032 }
13033 }
13034
13035 self.transact(window, cx, |this, window, cx| {
13036 this.buffer.update(cx, |buffer, cx| {
13037 buffer.edit(edits, None, cx);
13038 });
13039
13040 // When duplicating upward with whole lines, move the cursor to the duplicated line
13041 if upwards && whole_lines {
13042 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
13043
13044 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13045 let mut new_ranges = Vec::new();
13046 let selections = s.all::<Point>(&display_map);
13047 let mut selections_iter = selections.iter().peekable();
13048
13049 while let Some(first_selection) = selections_iter.next() {
13050 // Group contiguous selections together to find the total row span
13051 let mut group_selections = vec![first_selection];
13052 let mut rows = first_selection.spanned_rows(false, &display_map);
13053
13054 while let Some(next_selection) = selections_iter.peek() {
13055 let next_rows = next_selection.spanned_rows(false, &display_map);
13056 if next_rows.start < rows.end {
13057 rows.end = next_rows.end;
13058 group_selections.push(selections_iter.next().unwrap());
13059 } else {
13060 break;
13061 }
13062 }
13063
13064 let row_count = rows.end.0 - rows.start.0;
13065
13066 // Move all selections in this group up by the total number of duplicated rows
13067 for selection in group_selections {
13068 let new_start = Point::new(
13069 selection.start.row.saturating_sub(row_count),
13070 selection.start.column,
13071 );
13072
13073 let new_end = Point::new(
13074 selection.end.row.saturating_sub(row_count),
13075 selection.end.column,
13076 );
13077
13078 new_ranges.push(new_start..new_end);
13079 }
13080 }
13081
13082 s.select_ranges(new_ranges);
13083 });
13084 }
13085
13086 this.request_autoscroll(Autoscroll::fit(), cx);
13087 });
13088 }
13089
13090 pub fn duplicate_line_up(
13091 &mut self,
13092 _: &DuplicateLineUp,
13093 window: &mut Window,
13094 cx: &mut Context<Self>,
13095 ) {
13096 self.duplicate(true, true, window, cx);
13097 }
13098
13099 pub fn duplicate_line_down(
13100 &mut self,
13101 _: &DuplicateLineDown,
13102 window: &mut Window,
13103 cx: &mut Context<Self>,
13104 ) {
13105 self.duplicate(false, true, window, cx);
13106 }
13107
13108 pub fn duplicate_selection(
13109 &mut self,
13110 _: &DuplicateSelection,
13111 window: &mut Window,
13112 cx: &mut Context<Self>,
13113 ) {
13114 self.duplicate(false, false, window, cx);
13115 }
13116
13117 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
13118 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13119 if self.mode.is_single_line() {
13120 cx.propagate();
13121 return;
13122 }
13123
13124 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13125 let buffer = self.buffer.read(cx).snapshot(cx);
13126
13127 let mut edits = Vec::new();
13128 let mut unfold_ranges = Vec::new();
13129 let mut refold_creases = Vec::new();
13130
13131 let selections = self.selections.all::<Point>(&display_map);
13132 let mut selections = selections.iter().peekable();
13133 let mut contiguous_row_selections = Vec::new();
13134 let mut new_selections = Vec::new();
13135
13136 while let Some(selection) = selections.next() {
13137 // Find all the selections that span a contiguous row range
13138 let (start_row, end_row) = consume_contiguous_rows(
13139 &mut contiguous_row_selections,
13140 selection,
13141 &display_map,
13142 &mut selections,
13143 );
13144
13145 // Move the text spanned by the row range to be before the line preceding the row range
13146 if start_row.0 > 0 {
13147 let range_to_move = Point::new(
13148 start_row.previous_row().0,
13149 buffer.line_len(start_row.previous_row()),
13150 )
13151 ..Point::new(
13152 end_row.previous_row().0,
13153 buffer.line_len(end_row.previous_row()),
13154 );
13155 let insertion_point = display_map
13156 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
13157 .0;
13158
13159 // Don't move lines across excerpts
13160 if buffer
13161 .excerpt_containing(insertion_point..range_to_move.end)
13162 .is_some()
13163 {
13164 let text = buffer
13165 .text_for_range(range_to_move.clone())
13166 .flat_map(|s| s.chars())
13167 .skip(1)
13168 .chain(['\n'])
13169 .collect::<String>();
13170
13171 edits.push((
13172 buffer.anchor_after(range_to_move.start)
13173 ..buffer.anchor_before(range_to_move.end),
13174 String::new(),
13175 ));
13176 let insertion_anchor = buffer.anchor_after(insertion_point);
13177 edits.push((insertion_anchor..insertion_anchor, text));
13178
13179 let row_delta = range_to_move.start.row - insertion_point.row + 1;
13180
13181 // Move selections up
13182 new_selections.extend(contiguous_row_selections.drain(..).map(
13183 |mut selection| {
13184 selection.start.row -= row_delta;
13185 selection.end.row -= row_delta;
13186 selection
13187 },
13188 ));
13189
13190 // Move folds up
13191 unfold_ranges.push(range_to_move.clone());
13192 for fold in display_map.folds_in_range(
13193 buffer.anchor_before(range_to_move.start)
13194 ..buffer.anchor_after(range_to_move.end),
13195 ) {
13196 let mut start = fold.range.start.to_point(&buffer);
13197 let mut end = fold.range.end.to_point(&buffer);
13198 start.row -= row_delta;
13199 end.row -= row_delta;
13200 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
13201 }
13202 }
13203 }
13204
13205 // If we didn't move line(s), preserve the existing selections
13206 new_selections.append(&mut contiguous_row_selections);
13207 }
13208
13209 self.transact(window, cx, |this, window, cx| {
13210 this.unfold_ranges(&unfold_ranges, true, true, cx);
13211 this.buffer.update(cx, |buffer, cx| {
13212 for (range, text) in edits {
13213 buffer.edit([(range, text)], None, cx);
13214 }
13215 });
13216 this.fold_creases(refold_creases, true, window, cx);
13217 this.change_selections(Default::default(), window, cx, |s| {
13218 s.select(new_selections);
13219 })
13220 });
13221 }
13222
13223 pub fn move_line_down(
13224 &mut self,
13225 _: &MoveLineDown,
13226 window: &mut Window,
13227 cx: &mut Context<Self>,
13228 ) {
13229 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13230 if self.mode.is_single_line() {
13231 cx.propagate();
13232 return;
13233 }
13234
13235 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13236 let buffer = self.buffer.read(cx).snapshot(cx);
13237
13238 let mut edits = Vec::new();
13239 let mut unfold_ranges = Vec::new();
13240 let mut refold_creases = Vec::new();
13241
13242 let selections = self.selections.all::<Point>(&display_map);
13243 let mut selections = selections.iter().peekable();
13244 let mut contiguous_row_selections = Vec::new();
13245 let mut new_selections = Vec::new();
13246
13247 while let Some(selection) = selections.next() {
13248 // Find all the selections that span a contiguous row range
13249 let (start_row, end_row) = consume_contiguous_rows(
13250 &mut contiguous_row_selections,
13251 selection,
13252 &display_map,
13253 &mut selections,
13254 );
13255
13256 // Move the text spanned by the row range to be after the last line of the row range
13257 if end_row.0 <= buffer.max_point().row {
13258 let range_to_move =
13259 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
13260 let insertion_point = display_map
13261 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
13262 .0;
13263
13264 // Don't move lines across excerpt boundaries
13265 if buffer
13266 .excerpt_containing(range_to_move.start..insertion_point)
13267 .is_some()
13268 {
13269 let mut text = String::from("\n");
13270 text.extend(buffer.text_for_range(range_to_move.clone()));
13271 text.pop(); // Drop trailing newline
13272 edits.push((
13273 buffer.anchor_after(range_to_move.start)
13274 ..buffer.anchor_before(range_to_move.end),
13275 String::new(),
13276 ));
13277 let insertion_anchor = buffer.anchor_after(insertion_point);
13278 edits.push((insertion_anchor..insertion_anchor, text));
13279
13280 let row_delta = insertion_point.row - range_to_move.end.row + 1;
13281
13282 // Move selections down
13283 new_selections.extend(contiguous_row_selections.drain(..).map(
13284 |mut selection| {
13285 selection.start.row += row_delta;
13286 selection.end.row += row_delta;
13287 selection
13288 },
13289 ));
13290
13291 // Move folds down
13292 unfold_ranges.push(range_to_move.clone());
13293 for fold in display_map.folds_in_range(
13294 buffer.anchor_before(range_to_move.start)
13295 ..buffer.anchor_after(range_to_move.end),
13296 ) {
13297 let mut start = fold.range.start.to_point(&buffer);
13298 let mut end = fold.range.end.to_point(&buffer);
13299 start.row += row_delta;
13300 end.row += row_delta;
13301 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
13302 }
13303 }
13304 }
13305
13306 // If we didn't move line(s), preserve the existing selections
13307 new_selections.append(&mut contiguous_row_selections);
13308 }
13309
13310 self.transact(window, cx, |this, window, cx| {
13311 this.unfold_ranges(&unfold_ranges, true, true, cx);
13312 this.buffer.update(cx, |buffer, cx| {
13313 for (range, text) in edits {
13314 buffer.edit([(range, text)], None, cx);
13315 }
13316 });
13317 this.fold_creases(refold_creases, true, window, cx);
13318 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
13319 });
13320 }
13321
13322 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
13323 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13324 let text_layout_details = &self.text_layout_details(window, cx);
13325 self.transact(window, cx, |this, window, cx| {
13326 let edits = this.change_selections(Default::default(), window, cx, |s| {
13327 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
13328 s.move_with(&mut |display_map, selection| {
13329 if !selection.is_empty() {
13330 return;
13331 }
13332
13333 let mut head = selection.head();
13334 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
13335 if head.column() == display_map.line_len(head.row()) {
13336 transpose_offset = display_map
13337 .buffer_snapshot()
13338 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13339 }
13340
13341 if transpose_offset == MultiBufferOffset(0) {
13342 return;
13343 }
13344
13345 *head.column_mut() += 1;
13346 head = display_map.clip_point(head, Bias::Right);
13347 let goal = SelectionGoal::HorizontalPosition(
13348 display_map
13349 .x_for_display_point(head, text_layout_details)
13350 .into(),
13351 );
13352 selection.collapse_to(head, goal);
13353
13354 let transpose_start = display_map
13355 .buffer_snapshot()
13356 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13357 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
13358 let transpose_end = display_map
13359 .buffer_snapshot()
13360 .clip_offset(transpose_offset + 1usize, Bias::Right);
13361 if let Some(ch) = display_map
13362 .buffer_snapshot()
13363 .chars_at(transpose_start)
13364 .next()
13365 {
13366 edits.push((transpose_start..transpose_offset, String::new()));
13367 edits.push((transpose_end..transpose_end, ch.to_string()));
13368 }
13369 }
13370 });
13371 edits
13372 });
13373 this.buffer
13374 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13375 let selections = this
13376 .selections
13377 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13378 this.change_selections(Default::default(), window, cx, |s| {
13379 s.select(selections);
13380 });
13381 });
13382 }
13383
13384 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
13385 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13386 if self.mode.is_single_line() {
13387 cx.propagate();
13388 return;
13389 }
13390
13391 self.rewrap_impl(RewrapOptions::default(), cx)
13392 }
13393
13394 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
13395 let buffer = self.buffer.read(cx).snapshot(cx);
13396 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13397
13398 #[derive(Clone, Debug, PartialEq)]
13399 enum CommentFormat {
13400 /// single line comment, with prefix for line
13401 Line(String),
13402 /// single line within a block comment, with prefix for line
13403 BlockLine(String),
13404 /// a single line of a block comment that includes the initial delimiter
13405 BlockCommentWithStart(BlockCommentConfig),
13406 /// a single line of a block comment that includes the ending delimiter
13407 BlockCommentWithEnd(BlockCommentConfig),
13408 }
13409
13410 // Split selections to respect paragraph, indent, and comment prefix boundaries.
13411 let wrap_ranges = selections.into_iter().flat_map(|selection| {
13412 let language_settings = buffer.language_settings_at(selection.head(), cx);
13413 let language_scope = buffer.language_scope_at(selection.head());
13414
13415 let indent_and_prefix_for_row =
13416 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
13417 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
13418 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
13419 &language_scope
13420 {
13421 let indent_end = Point::new(row, indent.len);
13422 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13423 let line_text_after_indent = buffer
13424 .text_for_range(indent_end..line_end)
13425 .collect::<String>();
13426
13427 let is_within_comment_override = buffer
13428 .language_scope_at(indent_end)
13429 .is_some_and(|scope| scope.override_name() == Some("comment"));
13430 let comment_delimiters = if is_within_comment_override {
13431 // we are within a comment syntax node, but we don't
13432 // yet know what kind of comment: block, doc or line
13433 match (
13434 language_scope.documentation_comment(),
13435 language_scope.block_comment(),
13436 ) {
13437 (Some(config), _) | (_, Some(config))
13438 if buffer.contains_str_at(indent_end, &config.start) =>
13439 {
13440 Some(CommentFormat::BlockCommentWithStart(config.clone()))
13441 }
13442 (Some(config), _) | (_, Some(config))
13443 if line_text_after_indent.ends_with(config.end.as_ref()) =>
13444 {
13445 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
13446 }
13447 (Some(config), _) | (_, Some(config))
13448 if buffer.contains_str_at(indent_end, &config.prefix) =>
13449 {
13450 Some(CommentFormat::BlockLine(config.prefix.to_string()))
13451 }
13452 (_, _) => language_scope
13453 .line_comment_prefixes()
13454 .iter()
13455 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13456 .map(|prefix| CommentFormat::Line(prefix.to_string())),
13457 }
13458 } else {
13459 // we not in an overridden comment node, but we may
13460 // be within a non-overridden line comment node
13461 language_scope
13462 .line_comment_prefixes()
13463 .iter()
13464 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13465 .map(|prefix| CommentFormat::Line(prefix.to_string()))
13466 };
13467
13468 let rewrap_prefix = language_scope
13469 .rewrap_prefixes()
13470 .iter()
13471 .find_map(|prefix_regex| {
13472 prefix_regex.find(&line_text_after_indent).map(|mat| {
13473 if mat.start() == 0 {
13474 Some(mat.as_str().to_string())
13475 } else {
13476 None
13477 }
13478 })
13479 })
13480 .flatten();
13481 (comment_delimiters, rewrap_prefix)
13482 } else {
13483 (None, None)
13484 };
13485 (indent, comment_prefix, rewrap_prefix)
13486 };
13487
13488 let mut start_row = selection.start.row;
13489 let mut end_row = selection.end.row;
13490
13491 if selection.is_empty() {
13492 let cursor_row = selection.start.row;
13493
13494 let (mut indent_size, comment_prefix, _) = indent_and_prefix_for_row(cursor_row);
13495 let line_prefix = match &comment_prefix {
13496 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
13497 Some(prefix.as_str())
13498 }
13499 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
13500 prefix, ..
13501 })) => Some(prefix.as_ref()),
13502 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13503 start: _,
13504 end: _,
13505 prefix,
13506 tab_size,
13507 })) => {
13508 indent_size.len += tab_size;
13509 Some(prefix.as_ref())
13510 }
13511 None => None,
13512 };
13513 let indent_prefix = indent_size.chars().collect::<String>();
13514 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
13515
13516 'expand_upwards: while start_row > 0 {
13517 let prev_row = start_row - 1;
13518 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
13519 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
13520 && !buffer.is_line_blank(MultiBufferRow(prev_row))
13521 {
13522 start_row = prev_row;
13523 } else {
13524 break 'expand_upwards;
13525 }
13526 }
13527
13528 'expand_downwards: while end_row < buffer.max_point().row {
13529 let next_row = end_row + 1;
13530 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
13531 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
13532 && !buffer.is_line_blank(MultiBufferRow(next_row))
13533 {
13534 end_row = next_row;
13535 } else {
13536 break 'expand_downwards;
13537 }
13538 }
13539 }
13540
13541 let mut non_blank_rows_iter = (start_row..=end_row)
13542 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
13543 .peekable();
13544
13545 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
13546 row
13547 } else {
13548 return Vec::new();
13549 };
13550
13551 let mut ranges = Vec::new();
13552
13553 let mut current_range_start = first_row;
13554 let mut prev_row = first_row;
13555 let (
13556 mut current_range_indent,
13557 mut current_range_comment_delimiters,
13558 mut current_range_rewrap_prefix,
13559 ) = indent_and_prefix_for_row(first_row);
13560
13561 for row in non_blank_rows_iter.skip(1) {
13562 let has_paragraph_break = row > prev_row + 1;
13563
13564 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
13565 indent_and_prefix_for_row(row);
13566
13567 let has_indent_change = row_indent != current_range_indent;
13568 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
13569
13570 let has_boundary_change = has_comment_change
13571 || row_rewrap_prefix.is_some()
13572 || (has_indent_change && current_range_comment_delimiters.is_some());
13573
13574 if has_paragraph_break || has_boundary_change {
13575 ranges.push((
13576 language_settings.clone(),
13577 Point::new(current_range_start, 0)
13578 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13579 current_range_indent,
13580 current_range_comment_delimiters.clone(),
13581 current_range_rewrap_prefix.clone(),
13582 ));
13583 current_range_start = row;
13584 current_range_indent = row_indent;
13585 current_range_comment_delimiters = row_comment_delimiters;
13586 current_range_rewrap_prefix = row_rewrap_prefix;
13587 }
13588 prev_row = row;
13589 }
13590
13591 ranges.push((
13592 language_settings.clone(),
13593 Point::new(current_range_start, 0)
13594 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13595 current_range_indent,
13596 current_range_comment_delimiters,
13597 current_range_rewrap_prefix,
13598 ));
13599
13600 ranges
13601 });
13602
13603 let mut edits = Vec::new();
13604 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
13605
13606 for (language_settings, wrap_range, mut indent_size, comment_prefix, rewrap_prefix) in
13607 wrap_ranges
13608 {
13609 let start_row = wrap_range.start.row;
13610 let end_row = wrap_range.end.row;
13611
13612 // Skip selections that overlap with a range that has already been rewrapped.
13613 let selection_range = start_row..end_row;
13614 if rewrapped_row_ranges
13615 .iter()
13616 .any(|range| range.overlaps(&selection_range))
13617 {
13618 continue;
13619 }
13620
13621 let tab_size = language_settings.tab_size;
13622
13623 let (line_prefix, inside_comment) = match &comment_prefix {
13624 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
13625 (Some(prefix.as_str()), true)
13626 }
13627 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
13628 (Some(prefix.as_ref()), true)
13629 }
13630 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13631 start: _,
13632 end: _,
13633 prefix,
13634 tab_size,
13635 })) => {
13636 indent_size.len += tab_size;
13637 (Some(prefix.as_ref()), true)
13638 }
13639 None => (None, false),
13640 };
13641 let indent_prefix = indent_size.chars().collect::<String>();
13642 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
13643
13644 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
13645 RewrapBehavior::InComments => inside_comment,
13646 RewrapBehavior::InSelections => !wrap_range.is_empty(),
13647 RewrapBehavior::Anywhere => true,
13648 };
13649
13650 let should_rewrap = options.override_language_settings
13651 || allow_rewrap_based_on_language
13652 || self.hard_wrap.is_some();
13653 if !should_rewrap {
13654 continue;
13655 }
13656
13657 let start = Point::new(start_row, 0);
13658 let start_offset = ToOffset::to_offset(&start, &buffer);
13659 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
13660 let selection_text = buffer.text_for_range(start..end).collect::<String>();
13661 let mut first_line_delimiter = None;
13662 let mut last_line_delimiter = None;
13663 let Some(lines_without_prefixes) = selection_text
13664 .lines()
13665 .enumerate()
13666 .map(|(ix, line)| {
13667 let line_trimmed = line.trim_start();
13668 if rewrap_prefix.is_some() && ix > 0 {
13669 Ok(line_trimmed)
13670 } else if let Some(
13671 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13672 start,
13673 prefix,
13674 end,
13675 tab_size,
13676 })
13677 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
13678 start,
13679 prefix,
13680 end,
13681 tab_size,
13682 }),
13683 ) = &comment_prefix
13684 {
13685 let line_trimmed = line_trimmed
13686 .strip_prefix(start.as_ref())
13687 .map(|s| {
13688 let mut indent_size = indent_size;
13689 indent_size.len -= tab_size;
13690 let indent_prefix: String = indent_size.chars().collect();
13691 first_line_delimiter = Some((indent_prefix, start));
13692 s.trim_start()
13693 })
13694 .unwrap_or(line_trimmed);
13695 let line_trimmed = line_trimmed
13696 .strip_suffix(end.as_ref())
13697 .map(|s| {
13698 last_line_delimiter = Some(end);
13699 s.trim_end()
13700 })
13701 .unwrap_or(line_trimmed);
13702 let line_trimmed = line_trimmed
13703 .strip_prefix(prefix.as_ref())
13704 .unwrap_or(line_trimmed);
13705 Ok(line_trimmed)
13706 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
13707 line_trimmed.strip_prefix(prefix).with_context(|| {
13708 format!("line did not start with prefix {prefix:?}: {line:?}")
13709 })
13710 } else {
13711 line_trimmed
13712 .strip_prefix(&line_prefix.trim_start())
13713 .with_context(|| {
13714 format!("line did not start with prefix {line_prefix:?}: {line:?}")
13715 })
13716 }
13717 })
13718 .collect::<Result<Vec<_>, _>>()
13719 .log_err()
13720 else {
13721 continue;
13722 };
13723
13724 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
13725 buffer
13726 .language_settings_at(Point::new(start_row, 0), cx)
13727 .preferred_line_length as usize
13728 });
13729
13730 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
13731 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
13732 } else {
13733 line_prefix.clone()
13734 };
13735
13736 let wrapped_text = {
13737 let mut wrapped_text = wrap_with_prefix(
13738 line_prefix,
13739 subsequent_lines_prefix,
13740 lines_without_prefixes.join("\n"),
13741 wrap_column,
13742 tab_size,
13743 options.preserve_existing_whitespace,
13744 );
13745
13746 if let Some((indent, delimiter)) = first_line_delimiter {
13747 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
13748 }
13749 if let Some(last_line) = last_line_delimiter {
13750 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
13751 }
13752
13753 wrapped_text
13754 };
13755
13756 // TODO: should always use char-based diff while still supporting cursor behavior that
13757 // matches vim.
13758 let mut diff_options = DiffOptions::default();
13759 if options.override_language_settings {
13760 diff_options.max_word_diff_len = 0;
13761 diff_options.max_word_diff_line_count = 0;
13762 } else {
13763 diff_options.max_word_diff_len = usize::MAX;
13764 diff_options.max_word_diff_line_count = usize::MAX;
13765 }
13766
13767 for (old_range, new_text) in
13768 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
13769 {
13770 let edit_start = buffer.anchor_after(start_offset + old_range.start);
13771 let edit_end = buffer.anchor_after(start_offset + old_range.end);
13772 edits.push((edit_start..edit_end, new_text));
13773 }
13774
13775 rewrapped_row_ranges.push(start_row..=end_row);
13776 }
13777
13778 self.buffer
13779 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13780 }
13781
13782 pub fn cut_common(
13783 &mut self,
13784 cut_no_selection_line: bool,
13785 window: &mut Window,
13786 cx: &mut Context<Self>,
13787 ) -> ClipboardItem {
13788 let mut text = String::new();
13789 let buffer = self.buffer.read(cx).snapshot(cx);
13790 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13791 let mut clipboard_selections = Vec::with_capacity(selections.len());
13792 {
13793 let max_point = buffer.max_point();
13794 let mut is_first = true;
13795 let mut prev_selection_was_entire_line = false;
13796 for selection in &mut selections {
13797 let is_entire_line =
13798 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
13799 if is_entire_line {
13800 selection.start = Point::new(selection.start.row, 0);
13801 if !selection.is_empty() && selection.end.column == 0 {
13802 selection.end = cmp::min(max_point, selection.end);
13803 } else {
13804 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
13805 }
13806 selection.goal = SelectionGoal::None;
13807 }
13808 if is_first {
13809 is_first = false;
13810 } else if !prev_selection_was_entire_line {
13811 text += "\n";
13812 }
13813 prev_selection_was_entire_line = is_entire_line;
13814 let mut len = 0;
13815 for chunk in buffer.text_for_range(selection.start..selection.end) {
13816 text.push_str(chunk);
13817 len += chunk.len();
13818 }
13819
13820 clipboard_selections.push(ClipboardSelection::for_buffer(
13821 len,
13822 is_entire_line,
13823 selection.range(),
13824 &buffer,
13825 self.project.as_ref(),
13826 cx,
13827 ));
13828 }
13829 }
13830
13831 self.transact(window, cx, |this, window, cx| {
13832 this.change_selections(Default::default(), window, cx, |s| {
13833 s.select(selections);
13834 });
13835 this.insert("", window, cx);
13836 });
13837 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
13838 }
13839
13840 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
13841 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13842 let item = self.cut_common(true, window, cx);
13843 cx.write_to_clipboard(item);
13844 }
13845
13846 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
13847 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13848 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13849 s.move_with(&mut |snapshot, sel| {
13850 if sel.is_empty() {
13851 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
13852 }
13853 if sel.is_empty() {
13854 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13855 }
13856 });
13857 });
13858 let item = self.cut_common(false, window, cx);
13859 cx.set_global(KillRing(item))
13860 }
13861
13862 pub fn kill_ring_yank(
13863 &mut self,
13864 _: &KillRingYank,
13865 window: &mut Window,
13866 cx: &mut Context<Self>,
13867 ) {
13868 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13869 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
13870 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
13871 (kill_ring.text().to_string(), kill_ring.metadata_json())
13872 } else {
13873 return;
13874 }
13875 } else {
13876 return;
13877 };
13878 self.do_paste(&text, metadata, false, window, cx);
13879 }
13880
13881 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
13882 self.do_copy(true, cx);
13883 }
13884
13885 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
13886 self.do_copy(false, cx);
13887 }
13888
13889 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
13890 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13891 let buffer = self.buffer.read(cx).read(cx);
13892 let mut text = String::new();
13893 let mut clipboard_selections = Vec::with_capacity(selections.len());
13894
13895 let max_point = buffer.max_point();
13896 let mut is_first = true;
13897 for selection in &selections {
13898 let mut start = selection.start;
13899 let mut end = selection.end;
13900 let is_entire_line = selection.is_empty() || self.selections.line_mode();
13901 let mut add_trailing_newline = false;
13902 if is_entire_line {
13903 start = Point::new(start.row, 0);
13904 let next_line_start = Point::new(end.row + 1, 0);
13905 if next_line_start <= max_point {
13906 end = next_line_start;
13907 } else {
13908 // We're on the last line without a trailing newline.
13909 // Copy to the end of the line and add a newline afterwards.
13910 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
13911 add_trailing_newline = true;
13912 }
13913 }
13914
13915 let mut trimmed_selections = Vec::new();
13916 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
13917 let row = MultiBufferRow(start.row);
13918 let first_indent = buffer.indent_size_for_line(row);
13919 if first_indent.len == 0 || start.column > first_indent.len {
13920 trimmed_selections.push(start..end);
13921 } else {
13922 trimmed_selections.push(
13923 Point::new(row.0, first_indent.len)
13924 ..Point::new(row.0, buffer.line_len(row)),
13925 );
13926 for row in start.row + 1..=end.row {
13927 let mut line_len = buffer.line_len(MultiBufferRow(row));
13928 if row == end.row {
13929 line_len = end.column;
13930 }
13931 if line_len == 0 {
13932 trimmed_selections.push(Point::new(row, 0)..Point::new(row, line_len));
13933 continue;
13934 }
13935 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
13936 if row_indent_size.len >= first_indent.len {
13937 trimmed_selections
13938 .push(Point::new(row, first_indent.len)..Point::new(row, line_len));
13939 } else {
13940 trimmed_selections.clear();
13941 trimmed_selections.push(start..end);
13942 break;
13943 }
13944 }
13945 }
13946 } else {
13947 trimmed_selections.push(start..end);
13948 }
13949
13950 let is_multiline_trim = trimmed_selections.len() > 1;
13951 let mut selection_len: usize = 0;
13952 let prev_selection_was_entire_line = is_entire_line && !is_multiline_trim;
13953
13954 for trimmed_range in trimmed_selections {
13955 if is_first {
13956 is_first = false;
13957 } else if is_multiline_trim || !prev_selection_was_entire_line {
13958 text.push('\n');
13959 if is_multiline_trim {
13960 selection_len += 1;
13961 }
13962 }
13963 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
13964 text.push_str(chunk);
13965 selection_len += chunk.len();
13966 }
13967 if add_trailing_newline {
13968 text.push('\n');
13969 selection_len += 1;
13970 }
13971 }
13972
13973 clipboard_selections.push(ClipboardSelection::for_buffer(
13974 selection_len,
13975 is_entire_line,
13976 start..end,
13977 &buffer,
13978 self.project.as_ref(),
13979 cx,
13980 ));
13981 }
13982
13983 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
13984 text,
13985 clipboard_selections,
13986 ));
13987 }
13988
13989 pub fn do_paste(
13990 &mut self,
13991 text: &String,
13992 clipboard_selections: Option<Vec<ClipboardSelection>>,
13993 handle_entire_lines: bool,
13994 window: &mut Window,
13995 cx: &mut Context<Self>,
13996 ) {
13997 if self.read_only(cx) {
13998 return;
13999 }
14000
14001 let clipboard_text = Cow::Borrowed(text.as_str());
14002
14003 self.transact(window, cx, |this, window, cx| {
14004 let had_active_edit_prediction = this.has_active_edit_prediction();
14005 let display_map = this.display_snapshot(cx);
14006 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
14007 let cursor_offset = this
14008 .selections
14009 .last::<MultiBufferOffset>(&display_map)
14010 .head();
14011
14012 if let Some(mut clipboard_selections) = clipboard_selections {
14013 let all_selections_were_entire_line =
14014 clipboard_selections.iter().all(|s| s.is_entire_line);
14015 let first_selection_indent_column =
14016 clipboard_selections.first().map(|s| s.first_line_indent);
14017 if clipboard_selections.len() != old_selections.len() {
14018 clipboard_selections.drain(..);
14019 }
14020 let mut auto_indent_on_paste = true;
14021
14022 this.buffer.update(cx, |buffer, cx| {
14023 let snapshot = buffer.read(cx);
14024 auto_indent_on_paste = snapshot
14025 .language_settings_at(cursor_offset, cx)
14026 .auto_indent_on_paste;
14027
14028 let mut start_offset = 0;
14029 let mut edits = Vec::new();
14030 let mut original_indent_columns = Vec::new();
14031 for (ix, selection) in old_selections.iter().enumerate() {
14032 let to_insert;
14033 let entire_line;
14034 let original_indent_column;
14035 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
14036 let end_offset = start_offset + clipboard_selection.len;
14037 to_insert = &clipboard_text[start_offset..end_offset];
14038 entire_line = clipboard_selection.is_entire_line;
14039 start_offset = if entire_line {
14040 end_offset
14041 } else {
14042 end_offset + 1
14043 };
14044 original_indent_column = Some(clipboard_selection.first_line_indent);
14045 } else {
14046 to_insert = &*clipboard_text;
14047 entire_line = all_selections_were_entire_line;
14048 original_indent_column = first_selection_indent_column
14049 }
14050
14051 let (range, to_insert) =
14052 if selection.is_empty() && handle_entire_lines && entire_line {
14053 // If the corresponding selection was empty when this slice of the
14054 // clipboard text was written, then the entire line containing the
14055 // selection was copied. If this selection is also currently empty,
14056 // then paste the line before the current line of the buffer.
14057 let column = selection.start.to_point(&snapshot).column as usize;
14058 let line_start = selection.start - column;
14059 (line_start..line_start, Cow::Borrowed(to_insert))
14060 } else {
14061 let language = snapshot.language_at(selection.head());
14062 let range = selection.range();
14063 if let Some(language) = language
14064 && language.name() == "Markdown"
14065 {
14066 edit_for_markdown_paste(
14067 &snapshot,
14068 range,
14069 to_insert,
14070 url::Url::parse(to_insert).ok(),
14071 )
14072 } else {
14073 (range, Cow::Borrowed(to_insert))
14074 }
14075 };
14076
14077 edits.push((range, to_insert));
14078 original_indent_columns.push(original_indent_column);
14079 }
14080 drop(snapshot);
14081
14082 buffer.edit(
14083 edits,
14084 if auto_indent_on_paste {
14085 Some(AutoindentMode::Block {
14086 original_indent_columns,
14087 })
14088 } else {
14089 None
14090 },
14091 cx,
14092 );
14093 });
14094
14095 let selections = this
14096 .selections
14097 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
14098 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14099 } else {
14100 let url = url::Url::parse(&clipboard_text).ok();
14101
14102 let auto_indent_mode = if !clipboard_text.is_empty() {
14103 Some(AutoindentMode::Block {
14104 original_indent_columns: Vec::new(),
14105 })
14106 } else {
14107 None
14108 };
14109
14110 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
14111 let snapshot = buffer.snapshot(cx);
14112
14113 let anchors = old_selections
14114 .iter()
14115 .map(|s| {
14116 let anchor = snapshot.anchor_after(s.head());
14117 s.map(|_| anchor)
14118 })
14119 .collect::<Vec<_>>();
14120
14121 let mut edits = Vec::new();
14122
14123 // When pasting text without metadata (e.g. copied from an
14124 // external editor using multiple cursors) and the number of
14125 // lines matches the number of selections, distribute one
14126 // line per cursor instead of pasting the whole text at each.
14127 let lines: Vec<&str> = clipboard_text.split('\n').collect();
14128 let distribute_lines =
14129 old_selections.len() > 1 && lines.len() == old_selections.len();
14130
14131 for (ix, selection) in old_selections.iter().enumerate() {
14132 let language = snapshot.language_at(selection.head());
14133 let range = selection.range();
14134
14135 let text_for_cursor: &str = if distribute_lines {
14136 lines[ix]
14137 } else {
14138 &clipboard_text
14139 };
14140
14141 let (edit_range, edit_text) = if let Some(language) = language
14142 && language.name() == "Markdown"
14143 {
14144 edit_for_markdown_paste(&snapshot, range, text_for_cursor, url.clone())
14145 } else {
14146 (range, Cow::Borrowed(text_for_cursor))
14147 };
14148
14149 edits.push((edit_range, edit_text));
14150 }
14151
14152 drop(snapshot);
14153 buffer.edit(edits, auto_indent_mode, cx);
14154
14155 anchors
14156 });
14157
14158 this.change_selections(Default::default(), window, cx, |s| {
14159 s.select_anchors(selection_anchors);
14160 });
14161 }
14162
14163 // 🤔 | .. | show_in_menu |
14164 // | .. | true true
14165 // | had_edit_prediction | false true
14166
14167 let trigger_in_words =
14168 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
14169
14170 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
14171 });
14172 }
14173
14174 pub fn diff_clipboard_with_selection(
14175 &mut self,
14176 _: &DiffClipboardWithSelection,
14177 window: &mut Window,
14178 cx: &mut Context<Self>,
14179 ) {
14180 let selections = self
14181 .selections
14182 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
14183
14184 if selections.is_empty() {
14185 log::warn!("There should always be at least one selection in Zed. This is a bug.");
14186 return;
14187 };
14188
14189 let clipboard_text = cx.read_from_clipboard().and_then(|item| {
14190 item.entries().iter().find_map(|entry| match entry {
14191 ClipboardEntry::String(text) => Some(text.text().to_string()),
14192 _ => None,
14193 })
14194 });
14195
14196 let Some(clipboard_text) = clipboard_text else {
14197 log::warn!("Clipboard doesn't contain text.");
14198 return;
14199 };
14200
14201 window.dispatch_action(
14202 Box::new(DiffClipboardWithSelectionData {
14203 clipboard_text,
14204 editor: cx.entity(),
14205 }),
14206 cx,
14207 );
14208 }
14209
14210 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
14211 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14212 if let Some(item) = cx.read_from_clipboard() {
14213 let clipboard_string = item.entries().iter().find_map(|entry| match entry {
14214 ClipboardEntry::String(s) => Some(s),
14215 _ => None,
14216 });
14217 match clipboard_string {
14218 Some(clipboard_string) => self.do_paste(
14219 clipboard_string.text(),
14220 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
14221 true,
14222 window,
14223 cx,
14224 ),
14225 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
14226 }
14227 }
14228 }
14229
14230 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
14231 if self.read_only(cx) {
14232 return;
14233 }
14234
14235 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14236
14237 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
14238 if let Some((selections, _)) =
14239 self.selection_history.transaction(transaction_id).cloned()
14240 {
14241 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14242 s.select_anchors(selections.to_vec());
14243 });
14244 } else {
14245 log::error!(
14246 "No entry in selection_history found for undo. \
14247 This may correspond to a bug where undo does not update the selection. \
14248 If this is occurring, please add details to \
14249 https://github.com/zed-industries/zed/issues/22692"
14250 );
14251 }
14252 self.request_autoscroll(Autoscroll::fit(), cx);
14253 self.unmark_text(window, cx);
14254 self.refresh_edit_prediction(true, false, window, cx);
14255 cx.emit(EditorEvent::Edited { transaction_id });
14256 cx.emit(EditorEvent::TransactionUndone { transaction_id });
14257 }
14258 }
14259
14260 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
14261 if self.read_only(cx) {
14262 return;
14263 }
14264
14265 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14266
14267 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
14268 if let Some((_, Some(selections))) =
14269 self.selection_history.transaction(transaction_id).cloned()
14270 {
14271 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14272 s.select_anchors(selections.to_vec());
14273 });
14274 } else {
14275 log::error!(
14276 "No entry in selection_history found for redo. \
14277 This may correspond to a bug where undo does not update the selection. \
14278 If this is occurring, please add details to \
14279 https://github.com/zed-industries/zed/issues/22692"
14280 );
14281 }
14282 self.request_autoscroll(Autoscroll::fit(), cx);
14283 self.unmark_text(window, cx);
14284 self.refresh_edit_prediction(true, false, window, cx);
14285 cx.emit(EditorEvent::Edited { transaction_id });
14286 }
14287 }
14288
14289 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
14290 self.buffer
14291 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
14292 }
14293
14294 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
14295 self.buffer
14296 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
14297 }
14298
14299 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
14300 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14301 self.change_selections(Default::default(), window, cx, |s| {
14302 s.move_with(&mut |map, selection| {
14303 let cursor = if selection.is_empty() {
14304 movement::left(map, selection.start)
14305 } else {
14306 selection.start
14307 };
14308 selection.collapse_to(cursor, SelectionGoal::None);
14309 });
14310 })
14311 }
14312
14313 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
14314 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14315 self.change_selections(Default::default(), window, cx, |s| {
14316 s.move_heads_with(&mut |map, head, _| (movement::left(map, head), SelectionGoal::None));
14317 })
14318 }
14319
14320 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
14321 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14322 self.change_selections(Default::default(), window, cx, |s| {
14323 s.move_with(&mut |map, selection| {
14324 let cursor = if selection.is_empty() {
14325 movement::right(map, selection.end)
14326 } else {
14327 selection.end
14328 };
14329 selection.collapse_to(cursor, SelectionGoal::None)
14330 });
14331 })
14332 }
14333
14334 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
14335 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14336 self.change_selections(Default::default(), window, cx, |s| {
14337 s.move_heads_with(&mut |map, head, _| {
14338 (movement::right(map, head), SelectionGoal::None)
14339 });
14340 });
14341 }
14342
14343 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
14344 if self.take_rename(true, window, cx).is_some() {
14345 return;
14346 }
14347
14348 if self.mode.is_single_line() {
14349 cx.propagate();
14350 return;
14351 }
14352
14353 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14354
14355 let text_layout_details = &self.text_layout_details(window, cx);
14356 let selection_count = self.selections.count();
14357 let first_selection = self.selections.first_anchor();
14358
14359 self.change_selections(Default::default(), window, cx, |s| {
14360 s.move_with(&mut |map, selection| {
14361 if !selection.is_empty() {
14362 selection.goal = SelectionGoal::None;
14363 }
14364 let (cursor, goal) = movement::up(
14365 map,
14366 selection.start,
14367 selection.goal,
14368 false,
14369 text_layout_details,
14370 );
14371 selection.collapse_to(cursor, goal);
14372 });
14373 });
14374
14375 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14376 {
14377 cx.propagate();
14378 }
14379 }
14380
14381 pub fn move_up_by_lines(
14382 &mut self,
14383 action: &MoveUpByLines,
14384 window: &mut Window,
14385 cx: &mut Context<Self>,
14386 ) {
14387 if self.take_rename(true, window, cx).is_some() {
14388 return;
14389 }
14390
14391 if self.mode.is_single_line() {
14392 cx.propagate();
14393 return;
14394 }
14395
14396 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14397
14398 let text_layout_details = &self.text_layout_details(window, cx);
14399
14400 self.change_selections(Default::default(), window, cx, |s| {
14401 s.move_with(&mut |map, selection| {
14402 if !selection.is_empty() {
14403 selection.goal = SelectionGoal::None;
14404 }
14405 let (cursor, goal) = movement::up_by_rows(
14406 map,
14407 selection.start,
14408 action.lines,
14409 selection.goal,
14410 false,
14411 text_layout_details,
14412 );
14413 selection.collapse_to(cursor, goal);
14414 });
14415 })
14416 }
14417
14418 pub fn move_down_by_lines(
14419 &mut self,
14420 action: &MoveDownByLines,
14421 window: &mut Window,
14422 cx: &mut Context<Self>,
14423 ) {
14424 if self.take_rename(true, window, cx).is_some() {
14425 return;
14426 }
14427
14428 if self.mode.is_single_line() {
14429 cx.propagate();
14430 return;
14431 }
14432
14433 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14434
14435 let text_layout_details = &self.text_layout_details(window, cx);
14436
14437 self.change_selections(Default::default(), window, cx, |s| {
14438 s.move_with(&mut |map, selection| {
14439 if !selection.is_empty() {
14440 selection.goal = SelectionGoal::None;
14441 }
14442 let (cursor, goal) = movement::down_by_rows(
14443 map,
14444 selection.start,
14445 action.lines,
14446 selection.goal,
14447 false,
14448 text_layout_details,
14449 );
14450 selection.collapse_to(cursor, goal);
14451 });
14452 })
14453 }
14454
14455 pub fn select_down_by_lines(
14456 &mut self,
14457 action: &SelectDownByLines,
14458 window: &mut Window,
14459 cx: &mut Context<Self>,
14460 ) {
14461 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14462 let text_layout_details = &self.text_layout_details(window, cx);
14463 self.change_selections(Default::default(), window, cx, |s| {
14464 s.move_heads_with(&mut |map, head, goal| {
14465 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
14466 })
14467 })
14468 }
14469
14470 pub fn select_up_by_lines(
14471 &mut self,
14472 action: &SelectUpByLines,
14473 window: &mut Window,
14474 cx: &mut Context<Self>,
14475 ) {
14476 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14477 let text_layout_details = &self.text_layout_details(window, cx);
14478 self.change_selections(Default::default(), window, cx, |s| {
14479 s.move_heads_with(&mut |map, head, goal| {
14480 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
14481 })
14482 })
14483 }
14484
14485 pub fn select_page_up(
14486 &mut self,
14487 _: &SelectPageUp,
14488 window: &mut Window,
14489 cx: &mut Context<Self>,
14490 ) {
14491 let Some(row_count) = self.visible_row_count() else {
14492 return;
14493 };
14494
14495 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14496
14497 let text_layout_details = &self.text_layout_details(window, cx);
14498
14499 self.change_selections(Default::default(), window, cx, |s| {
14500 s.move_heads_with(&mut |map, head, goal| {
14501 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
14502 })
14503 })
14504 }
14505
14506 pub fn move_page_up(
14507 &mut self,
14508 action: &MovePageUp,
14509 window: &mut Window,
14510 cx: &mut Context<Self>,
14511 ) {
14512 if self.take_rename(true, window, cx).is_some() {
14513 return;
14514 }
14515
14516 if self
14517 .context_menu
14518 .borrow_mut()
14519 .as_mut()
14520 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
14521 .unwrap_or(false)
14522 {
14523 return;
14524 }
14525
14526 if matches!(self.mode, EditorMode::SingleLine) {
14527 cx.propagate();
14528 return;
14529 }
14530
14531 let Some(row_count) = self.visible_row_count() else {
14532 return;
14533 };
14534
14535 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14536
14537 let effects = if action.center_cursor {
14538 SelectionEffects::scroll(Autoscroll::center())
14539 } else {
14540 SelectionEffects::default()
14541 };
14542
14543 let text_layout_details = &self.text_layout_details(window, cx);
14544
14545 self.change_selections(effects, window, cx, |s| {
14546 s.move_with(&mut |map, selection| {
14547 if !selection.is_empty() {
14548 selection.goal = SelectionGoal::None;
14549 }
14550 let (cursor, goal) = movement::up_by_rows(
14551 map,
14552 selection.end,
14553 row_count,
14554 selection.goal,
14555 false,
14556 text_layout_details,
14557 );
14558 selection.collapse_to(cursor, goal);
14559 });
14560 });
14561 }
14562
14563 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
14564 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14565 let text_layout_details = &self.text_layout_details(window, cx);
14566 self.change_selections(Default::default(), window, cx, |s| {
14567 s.move_heads_with(&mut |map, head, goal| {
14568 movement::up(map, head, goal, false, text_layout_details)
14569 })
14570 })
14571 }
14572
14573 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
14574 self.take_rename(true, window, cx);
14575
14576 if self.mode.is_single_line() {
14577 cx.propagate();
14578 return;
14579 }
14580
14581 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14582
14583 let text_layout_details = &self.text_layout_details(window, cx);
14584 let selection_count = self.selections.count();
14585 let first_selection = self.selections.first_anchor();
14586
14587 self.change_selections(Default::default(), window, cx, |s| {
14588 s.move_with(&mut |map, selection| {
14589 if !selection.is_empty() {
14590 selection.goal = SelectionGoal::None;
14591 }
14592 let (cursor, goal) = movement::down(
14593 map,
14594 selection.end,
14595 selection.goal,
14596 false,
14597 text_layout_details,
14598 );
14599 selection.collapse_to(cursor, goal);
14600 });
14601 });
14602
14603 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14604 {
14605 cx.propagate();
14606 }
14607 }
14608
14609 pub fn select_page_down(
14610 &mut self,
14611 _: &SelectPageDown,
14612 window: &mut Window,
14613 cx: &mut Context<Self>,
14614 ) {
14615 let Some(row_count) = self.visible_row_count() else {
14616 return;
14617 };
14618
14619 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14620
14621 let text_layout_details = &self.text_layout_details(window, cx);
14622
14623 self.change_selections(Default::default(), window, cx, |s| {
14624 s.move_heads_with(&mut |map, head, goal| {
14625 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
14626 })
14627 })
14628 }
14629
14630 pub fn move_page_down(
14631 &mut self,
14632 action: &MovePageDown,
14633 window: &mut Window,
14634 cx: &mut Context<Self>,
14635 ) {
14636 if self.take_rename(true, window, cx).is_some() {
14637 return;
14638 }
14639
14640 if self
14641 .context_menu
14642 .borrow_mut()
14643 .as_mut()
14644 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
14645 .unwrap_or(false)
14646 {
14647 return;
14648 }
14649
14650 if matches!(self.mode, EditorMode::SingleLine) {
14651 cx.propagate();
14652 return;
14653 }
14654
14655 let Some(row_count) = self.visible_row_count() else {
14656 return;
14657 };
14658
14659 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14660
14661 let effects = if action.center_cursor {
14662 SelectionEffects::scroll(Autoscroll::center())
14663 } else {
14664 SelectionEffects::default()
14665 };
14666
14667 let text_layout_details = &self.text_layout_details(window, cx);
14668 self.change_selections(effects, window, cx, |s| {
14669 s.move_with(&mut |map, selection| {
14670 if !selection.is_empty() {
14671 selection.goal = SelectionGoal::None;
14672 }
14673 let (cursor, goal) = movement::down_by_rows(
14674 map,
14675 selection.end,
14676 row_count,
14677 selection.goal,
14678 false,
14679 text_layout_details,
14680 );
14681 selection.collapse_to(cursor, goal);
14682 });
14683 });
14684 }
14685
14686 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
14687 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14688 let text_layout_details = &self.text_layout_details(window, cx);
14689 self.change_selections(Default::default(), window, cx, |s| {
14690 s.move_heads_with(&mut |map, head, goal| {
14691 movement::down(map, head, goal, false, text_layout_details)
14692 })
14693 });
14694 }
14695
14696 pub fn context_menu_first(
14697 &mut self,
14698 _: &ContextMenuFirst,
14699 window: &mut Window,
14700 cx: &mut Context<Self>,
14701 ) {
14702 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14703 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
14704 }
14705 }
14706
14707 pub fn context_menu_prev(
14708 &mut self,
14709 _: &ContextMenuPrevious,
14710 window: &mut Window,
14711 cx: &mut Context<Self>,
14712 ) {
14713 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14714 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
14715 }
14716 }
14717
14718 pub fn context_menu_next(
14719 &mut self,
14720 _: &ContextMenuNext,
14721 window: &mut Window,
14722 cx: &mut Context<Self>,
14723 ) {
14724 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14725 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
14726 }
14727 }
14728
14729 pub fn context_menu_last(
14730 &mut self,
14731 _: &ContextMenuLast,
14732 window: &mut Window,
14733 cx: &mut Context<Self>,
14734 ) {
14735 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14736 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
14737 }
14738 }
14739
14740 pub fn signature_help_prev(
14741 &mut self,
14742 _: &SignatureHelpPrevious,
14743 _: &mut Window,
14744 cx: &mut Context<Self>,
14745 ) {
14746 if let Some(popover) = self.signature_help_state.popover_mut() {
14747 if popover.current_signature == 0 {
14748 popover.current_signature = popover.signatures.len() - 1;
14749 } else {
14750 popover.current_signature -= 1;
14751 }
14752 cx.notify();
14753 }
14754 }
14755
14756 pub fn signature_help_next(
14757 &mut self,
14758 _: &SignatureHelpNext,
14759 _: &mut Window,
14760 cx: &mut Context<Self>,
14761 ) {
14762 if let Some(popover) = self.signature_help_state.popover_mut() {
14763 if popover.current_signature + 1 == popover.signatures.len() {
14764 popover.current_signature = 0;
14765 } else {
14766 popover.current_signature += 1;
14767 }
14768 cx.notify();
14769 }
14770 }
14771
14772 pub fn move_to_previous_word_start(
14773 &mut self,
14774 _: &MoveToPreviousWordStart,
14775 window: &mut Window,
14776 cx: &mut Context<Self>,
14777 ) {
14778 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14779 self.change_selections(Default::default(), window, cx, |s| {
14780 s.move_cursors_with(&mut |map, head, _| {
14781 (
14782 movement::previous_word_start(map, head),
14783 SelectionGoal::None,
14784 )
14785 });
14786 })
14787 }
14788
14789 pub fn move_to_previous_subword_start(
14790 &mut self,
14791 _: &MoveToPreviousSubwordStart,
14792 window: &mut Window,
14793 cx: &mut Context<Self>,
14794 ) {
14795 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14796 self.change_selections(Default::default(), window, cx, |s| {
14797 s.move_cursors_with(&mut |map, head, _| {
14798 (
14799 movement::previous_subword_start(map, head),
14800 SelectionGoal::None,
14801 )
14802 });
14803 })
14804 }
14805
14806 pub fn select_to_previous_word_start(
14807 &mut self,
14808 _: &SelectToPreviousWordStart,
14809 window: &mut Window,
14810 cx: &mut Context<Self>,
14811 ) {
14812 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14813 self.change_selections(Default::default(), window, cx, |s| {
14814 s.move_heads_with(&mut |map, head, _| {
14815 (
14816 movement::previous_word_start(map, head),
14817 SelectionGoal::None,
14818 )
14819 });
14820 })
14821 }
14822
14823 pub fn select_to_previous_subword_start(
14824 &mut self,
14825 _: &SelectToPreviousSubwordStart,
14826 window: &mut Window,
14827 cx: &mut Context<Self>,
14828 ) {
14829 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14830 self.change_selections(Default::default(), window, cx, |s| {
14831 s.move_heads_with(&mut |map, head, _| {
14832 (
14833 movement::previous_subword_start(map, head),
14834 SelectionGoal::None,
14835 )
14836 });
14837 })
14838 }
14839
14840 pub fn delete_to_previous_word_start(
14841 &mut self,
14842 action: &DeleteToPreviousWordStart,
14843 window: &mut Window,
14844 cx: &mut Context<Self>,
14845 ) {
14846 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14847 self.transact(window, cx, |this, window, cx| {
14848 this.select_autoclose_pair(window, cx);
14849 this.change_selections(Default::default(), window, cx, |s| {
14850 s.move_with(&mut |map, selection| {
14851 if selection.is_empty() {
14852 let mut cursor = if action.ignore_newlines {
14853 movement::previous_word_start(map, selection.head())
14854 } else {
14855 movement::previous_word_start_or_newline(map, selection.head())
14856 };
14857 cursor = movement::adjust_greedy_deletion(
14858 map,
14859 selection.head(),
14860 cursor,
14861 action.ignore_brackets,
14862 );
14863 selection.set_head(cursor, SelectionGoal::None);
14864 }
14865 });
14866 });
14867 this.insert("", window, cx);
14868 });
14869 }
14870
14871 pub fn delete_to_previous_subword_start(
14872 &mut self,
14873 action: &DeleteToPreviousSubwordStart,
14874 window: &mut Window,
14875 cx: &mut Context<Self>,
14876 ) {
14877 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14878 self.transact(window, cx, |this, window, cx| {
14879 this.select_autoclose_pair(window, cx);
14880 this.change_selections(Default::default(), window, cx, |s| {
14881 s.move_with(&mut |map, selection| {
14882 if selection.is_empty() {
14883 let mut cursor = if action.ignore_newlines {
14884 movement::previous_subword_start(map, selection.head())
14885 } else {
14886 movement::previous_subword_start_or_newline(map, selection.head())
14887 };
14888 cursor = movement::adjust_greedy_deletion(
14889 map,
14890 selection.head(),
14891 cursor,
14892 action.ignore_brackets,
14893 );
14894 selection.set_head(cursor, SelectionGoal::None);
14895 }
14896 });
14897 });
14898 this.insert("", window, cx);
14899 });
14900 }
14901
14902 pub fn move_to_next_word_end(
14903 &mut self,
14904 _: &MoveToNextWordEnd,
14905 window: &mut Window,
14906 cx: &mut Context<Self>,
14907 ) {
14908 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14909 self.change_selections(Default::default(), window, cx, |s| {
14910 s.move_cursors_with(&mut |map, head, _| {
14911 (movement::next_word_end(map, head), SelectionGoal::None)
14912 });
14913 })
14914 }
14915
14916 pub fn move_to_next_subword_end(
14917 &mut self,
14918 _: &MoveToNextSubwordEnd,
14919 window: &mut Window,
14920 cx: &mut Context<Self>,
14921 ) {
14922 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14923 self.change_selections(Default::default(), window, cx, |s| {
14924 s.move_cursors_with(&mut |map, head, _| {
14925 (movement::next_subword_end(map, head), SelectionGoal::None)
14926 });
14927 })
14928 }
14929
14930 pub fn select_to_next_word_end(
14931 &mut self,
14932 _: &SelectToNextWordEnd,
14933 window: &mut Window,
14934 cx: &mut Context<Self>,
14935 ) {
14936 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14937 self.change_selections(Default::default(), window, cx, |s| {
14938 s.move_heads_with(&mut |map, head, _| {
14939 (movement::next_word_end(map, head), SelectionGoal::None)
14940 });
14941 })
14942 }
14943
14944 pub fn select_to_next_subword_end(
14945 &mut self,
14946 _: &SelectToNextSubwordEnd,
14947 window: &mut Window,
14948 cx: &mut Context<Self>,
14949 ) {
14950 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14951 self.change_selections(Default::default(), window, cx, |s| {
14952 s.move_heads_with(&mut |map, head, _| {
14953 (movement::next_subword_end(map, head), SelectionGoal::None)
14954 });
14955 })
14956 }
14957
14958 pub fn delete_to_next_word_end(
14959 &mut self,
14960 action: &DeleteToNextWordEnd,
14961 window: &mut Window,
14962 cx: &mut Context<Self>,
14963 ) {
14964 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14965 self.transact(window, cx, |this, window, cx| {
14966 this.change_selections(Default::default(), window, cx, |s| {
14967 s.move_with(&mut |map, selection| {
14968 if selection.is_empty() {
14969 let mut cursor = if action.ignore_newlines {
14970 movement::next_word_end(map, selection.head())
14971 } else {
14972 movement::next_word_end_or_newline(map, selection.head())
14973 };
14974 cursor = movement::adjust_greedy_deletion(
14975 map,
14976 selection.head(),
14977 cursor,
14978 action.ignore_brackets,
14979 );
14980 selection.set_head(cursor, SelectionGoal::None);
14981 }
14982 });
14983 });
14984 this.insert("", window, cx);
14985 });
14986 }
14987
14988 pub fn delete_to_next_subword_end(
14989 &mut self,
14990 action: &DeleteToNextSubwordEnd,
14991 window: &mut Window,
14992 cx: &mut Context<Self>,
14993 ) {
14994 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14995 self.transact(window, cx, |this, window, cx| {
14996 this.change_selections(Default::default(), window, cx, |s| {
14997 s.move_with(&mut |map, selection| {
14998 if selection.is_empty() {
14999 let mut cursor = if action.ignore_newlines {
15000 movement::next_subword_end(map, selection.head())
15001 } else {
15002 movement::next_subword_end_or_newline(map, selection.head())
15003 };
15004 cursor = movement::adjust_greedy_deletion(
15005 map,
15006 selection.head(),
15007 cursor,
15008 action.ignore_brackets,
15009 );
15010 selection.set_head(cursor, SelectionGoal::None);
15011 }
15012 });
15013 });
15014 this.insert("", window, cx);
15015 });
15016 }
15017
15018 pub fn move_to_beginning_of_line(
15019 &mut self,
15020 action: &MoveToBeginningOfLine,
15021 window: &mut Window,
15022 cx: &mut Context<Self>,
15023 ) {
15024 let stop_at_indent = action.stop_at_indent && !self.mode.is_single_line();
15025 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15026 self.change_selections(Default::default(), window, cx, |s| {
15027 s.move_cursors_with(&mut |map, head, _| {
15028 (
15029 movement::indented_line_beginning(
15030 map,
15031 head,
15032 action.stop_at_soft_wraps,
15033 stop_at_indent,
15034 ),
15035 SelectionGoal::None,
15036 )
15037 });
15038 })
15039 }
15040
15041 pub fn select_to_beginning_of_line(
15042 &mut self,
15043 action: &SelectToBeginningOfLine,
15044 window: &mut Window,
15045 cx: &mut Context<Self>,
15046 ) {
15047 let stop_at_indent = action.stop_at_indent && !self.mode.is_single_line();
15048 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15049 self.change_selections(Default::default(), window, cx, |s| {
15050 s.move_heads_with(&mut |map, head, _| {
15051 (
15052 movement::indented_line_beginning(
15053 map,
15054 head,
15055 action.stop_at_soft_wraps,
15056 stop_at_indent,
15057 ),
15058 SelectionGoal::None,
15059 )
15060 });
15061 });
15062 }
15063
15064 pub fn delete_to_beginning_of_line(
15065 &mut self,
15066 action: &DeleteToBeginningOfLine,
15067 window: &mut Window,
15068 cx: &mut Context<Self>,
15069 ) {
15070 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15071 self.transact(window, cx, |this, window, cx| {
15072 this.change_selections(Default::default(), window, cx, |s| {
15073 s.move_with(&mut |_, selection| {
15074 selection.reversed = true;
15075 });
15076 });
15077
15078 this.select_to_beginning_of_line(
15079 &SelectToBeginningOfLine {
15080 stop_at_soft_wraps: false,
15081 stop_at_indent: action.stop_at_indent,
15082 },
15083 window,
15084 cx,
15085 );
15086 this.backspace(&Backspace, window, cx);
15087 });
15088 }
15089
15090 pub fn move_to_end_of_line(
15091 &mut self,
15092 action: &MoveToEndOfLine,
15093 window: &mut Window,
15094 cx: &mut Context<Self>,
15095 ) {
15096 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15097 self.change_selections(Default::default(), window, cx, |s| {
15098 s.move_cursors_with(&mut |map, head, _| {
15099 (
15100 movement::line_end(map, head, action.stop_at_soft_wraps),
15101 SelectionGoal::None,
15102 )
15103 });
15104 })
15105 }
15106
15107 pub fn select_to_end_of_line(
15108 &mut self,
15109 action: &SelectToEndOfLine,
15110 window: &mut Window,
15111 cx: &mut Context<Self>,
15112 ) {
15113 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15114 self.change_selections(Default::default(), window, cx, |s| {
15115 s.move_heads_with(&mut |map, head, _| {
15116 (
15117 movement::line_end(map, head, action.stop_at_soft_wraps),
15118 SelectionGoal::None,
15119 )
15120 });
15121 })
15122 }
15123
15124 pub fn delete_to_end_of_line(
15125 &mut self,
15126 _: &DeleteToEndOfLine,
15127 window: &mut Window,
15128 cx: &mut Context<Self>,
15129 ) {
15130 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15131 self.transact(window, cx, |this, window, cx| {
15132 this.select_to_end_of_line(
15133 &SelectToEndOfLine {
15134 stop_at_soft_wraps: false,
15135 },
15136 window,
15137 cx,
15138 );
15139 this.delete(&Delete, window, cx);
15140 });
15141 }
15142
15143 pub fn cut_to_end_of_line(
15144 &mut self,
15145 action: &CutToEndOfLine,
15146 window: &mut Window,
15147 cx: &mut Context<Self>,
15148 ) {
15149 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15150 self.transact(window, cx, |this, window, cx| {
15151 this.select_to_end_of_line(
15152 &SelectToEndOfLine {
15153 stop_at_soft_wraps: false,
15154 },
15155 window,
15156 cx,
15157 );
15158 if !action.stop_at_newlines {
15159 this.change_selections(Default::default(), window, cx, |s| {
15160 s.move_with(&mut |_, sel| {
15161 if sel.is_empty() {
15162 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
15163 }
15164 });
15165 });
15166 }
15167 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15168 let item = this.cut_common(false, window, cx);
15169 cx.write_to_clipboard(item);
15170 });
15171 }
15172
15173 pub fn move_to_start_of_paragraph(
15174 &mut self,
15175 _: &MoveToStartOfParagraph,
15176 window: &mut Window,
15177 cx: &mut Context<Self>,
15178 ) {
15179 if matches!(self.mode, EditorMode::SingleLine) {
15180 cx.propagate();
15181 return;
15182 }
15183 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15184 self.change_selections(Default::default(), window, cx, |s| {
15185 s.move_with(&mut |map, selection| {
15186 selection.collapse_to(
15187 movement::start_of_paragraph(map, selection.head(), 1),
15188 SelectionGoal::None,
15189 )
15190 });
15191 })
15192 }
15193
15194 pub fn move_to_end_of_paragraph(
15195 &mut self,
15196 _: &MoveToEndOfParagraph,
15197 window: &mut Window,
15198 cx: &mut Context<Self>,
15199 ) {
15200 if matches!(self.mode, EditorMode::SingleLine) {
15201 cx.propagate();
15202 return;
15203 }
15204 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15205 self.change_selections(Default::default(), window, cx, |s| {
15206 s.move_with(&mut |map, selection| {
15207 selection.collapse_to(
15208 movement::end_of_paragraph(map, selection.head(), 1),
15209 SelectionGoal::None,
15210 )
15211 });
15212 })
15213 }
15214
15215 pub fn select_to_start_of_paragraph(
15216 &mut self,
15217 _: &SelectToStartOfParagraph,
15218 window: &mut Window,
15219 cx: &mut Context<Self>,
15220 ) {
15221 if matches!(self.mode, EditorMode::SingleLine) {
15222 cx.propagate();
15223 return;
15224 }
15225 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15226 self.change_selections(Default::default(), window, cx, |s| {
15227 s.move_heads_with(&mut |map, head, _| {
15228 (
15229 movement::start_of_paragraph(map, head, 1),
15230 SelectionGoal::None,
15231 )
15232 });
15233 })
15234 }
15235
15236 pub fn select_to_end_of_paragraph(
15237 &mut self,
15238 _: &SelectToEndOfParagraph,
15239 window: &mut Window,
15240 cx: &mut Context<Self>,
15241 ) {
15242 if matches!(self.mode, EditorMode::SingleLine) {
15243 cx.propagate();
15244 return;
15245 }
15246 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15247 self.change_selections(Default::default(), window, cx, |s| {
15248 s.move_heads_with(&mut |map, head, _| {
15249 (
15250 movement::end_of_paragraph(map, head, 1),
15251 SelectionGoal::None,
15252 )
15253 });
15254 })
15255 }
15256
15257 pub fn move_to_start_of_excerpt(
15258 &mut self,
15259 _: &MoveToStartOfExcerpt,
15260 window: &mut Window,
15261 cx: &mut Context<Self>,
15262 ) {
15263 if matches!(self.mode, EditorMode::SingleLine) {
15264 cx.propagate();
15265 return;
15266 }
15267 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15268 self.change_selections(Default::default(), window, cx, |s| {
15269 s.move_with(&mut |map, selection| {
15270 selection.collapse_to(
15271 movement::start_of_excerpt(
15272 map,
15273 selection.head(),
15274 workspace::searchable::Direction::Prev,
15275 ),
15276 SelectionGoal::None,
15277 )
15278 });
15279 })
15280 }
15281
15282 pub fn move_to_start_of_next_excerpt(
15283 &mut self,
15284 _: &MoveToStartOfNextExcerpt,
15285 window: &mut Window,
15286 cx: &mut Context<Self>,
15287 ) {
15288 if matches!(self.mode, EditorMode::SingleLine) {
15289 cx.propagate();
15290 return;
15291 }
15292
15293 self.change_selections(Default::default(), window, cx, |s| {
15294 s.move_with(&mut |map, selection| {
15295 selection.collapse_to(
15296 movement::start_of_excerpt(
15297 map,
15298 selection.head(),
15299 workspace::searchable::Direction::Next,
15300 ),
15301 SelectionGoal::None,
15302 )
15303 });
15304 })
15305 }
15306
15307 pub fn move_to_end_of_excerpt(
15308 &mut self,
15309 _: &MoveToEndOfExcerpt,
15310 window: &mut Window,
15311 cx: &mut Context<Self>,
15312 ) {
15313 if matches!(self.mode, EditorMode::SingleLine) {
15314 cx.propagate();
15315 return;
15316 }
15317 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15318 self.change_selections(Default::default(), window, cx, |s| {
15319 s.move_with(&mut |map, selection| {
15320 selection.collapse_to(
15321 movement::end_of_excerpt(
15322 map,
15323 selection.head(),
15324 workspace::searchable::Direction::Next,
15325 ),
15326 SelectionGoal::None,
15327 )
15328 });
15329 })
15330 }
15331
15332 pub fn move_to_end_of_previous_excerpt(
15333 &mut self,
15334 _: &MoveToEndOfPreviousExcerpt,
15335 window: &mut Window,
15336 cx: &mut Context<Self>,
15337 ) {
15338 if matches!(self.mode, EditorMode::SingleLine) {
15339 cx.propagate();
15340 return;
15341 }
15342 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15343 self.change_selections(Default::default(), window, cx, |s| {
15344 s.move_with(&mut |map, selection| {
15345 selection.collapse_to(
15346 movement::end_of_excerpt(
15347 map,
15348 selection.head(),
15349 workspace::searchable::Direction::Prev,
15350 ),
15351 SelectionGoal::None,
15352 )
15353 });
15354 })
15355 }
15356
15357 pub fn select_to_start_of_excerpt(
15358 &mut self,
15359 _: &SelectToStartOfExcerpt,
15360 window: &mut Window,
15361 cx: &mut Context<Self>,
15362 ) {
15363 if matches!(self.mode, EditorMode::SingleLine) {
15364 cx.propagate();
15365 return;
15366 }
15367 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15368 self.change_selections(Default::default(), window, cx, |s| {
15369 s.move_heads_with(&mut |map, head, _| {
15370 (
15371 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15372 SelectionGoal::None,
15373 )
15374 });
15375 })
15376 }
15377
15378 pub fn select_to_start_of_next_excerpt(
15379 &mut self,
15380 _: &SelectToStartOfNextExcerpt,
15381 window: &mut Window,
15382 cx: &mut Context<Self>,
15383 ) {
15384 if matches!(self.mode, EditorMode::SingleLine) {
15385 cx.propagate();
15386 return;
15387 }
15388 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15389 self.change_selections(Default::default(), window, cx, |s| {
15390 s.move_heads_with(&mut |map, head, _| {
15391 (
15392 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
15393 SelectionGoal::None,
15394 )
15395 });
15396 })
15397 }
15398
15399 pub fn select_to_end_of_excerpt(
15400 &mut self,
15401 _: &SelectToEndOfExcerpt,
15402 window: &mut Window,
15403 cx: &mut Context<Self>,
15404 ) {
15405 if matches!(self.mode, EditorMode::SingleLine) {
15406 cx.propagate();
15407 return;
15408 }
15409 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15410 self.change_selections(Default::default(), window, cx, |s| {
15411 s.move_heads_with(&mut |map, head, _| {
15412 (
15413 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
15414 SelectionGoal::None,
15415 )
15416 });
15417 })
15418 }
15419
15420 pub fn select_to_end_of_previous_excerpt(
15421 &mut self,
15422 _: &SelectToEndOfPreviousExcerpt,
15423 window: &mut Window,
15424 cx: &mut Context<Self>,
15425 ) {
15426 if matches!(self.mode, EditorMode::SingleLine) {
15427 cx.propagate();
15428 return;
15429 }
15430 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15431 self.change_selections(Default::default(), window, cx, |s| {
15432 s.move_heads_with(&mut |map, head, _| {
15433 (
15434 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15435 SelectionGoal::None,
15436 )
15437 });
15438 })
15439 }
15440
15441 pub fn move_to_beginning(
15442 &mut self,
15443 _: &MoveToBeginning,
15444 window: &mut Window,
15445 cx: &mut Context<Self>,
15446 ) {
15447 if matches!(self.mode, EditorMode::SingleLine) {
15448 cx.propagate();
15449 return;
15450 }
15451 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15452 self.change_selections(Default::default(), window, cx, |s| {
15453 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
15454 });
15455 }
15456
15457 pub fn select_to_beginning(
15458 &mut self,
15459 _: &SelectToBeginning,
15460 window: &mut Window,
15461 cx: &mut Context<Self>,
15462 ) {
15463 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
15464 selection.set_head(Point::zero(), SelectionGoal::None);
15465 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15466 self.change_selections(Default::default(), window, cx, |s| {
15467 s.select(vec![selection]);
15468 });
15469 }
15470
15471 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
15472 if matches!(self.mode, EditorMode::SingleLine) {
15473 cx.propagate();
15474 return;
15475 }
15476 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15477 let cursor = self.buffer.read(cx).read(cx).len();
15478 self.change_selections(Default::default(), window, cx, |s| {
15479 s.select_ranges(vec![cursor..cursor])
15480 });
15481 }
15482
15483 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
15484 self.nav_history = nav_history;
15485 }
15486
15487 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
15488 self.nav_history.as_ref()
15489 }
15490
15491 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
15492 self.push_to_nav_history(
15493 self.selections.newest_anchor().head(),
15494 None,
15495 false,
15496 true,
15497 cx,
15498 );
15499 }
15500
15501 fn navigation_data(&self, cursor_anchor: Anchor, cx: &mut Context<Self>) -> NavigationData {
15502 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15503 let buffer = self.buffer.read(cx).read(cx);
15504 let cursor_position = cursor_anchor.to_point(&buffer);
15505 let scroll_anchor = self.scroll_manager.native_anchor(&display_snapshot, cx);
15506 let scroll_top_row = scroll_anchor.top_row(&buffer);
15507 drop(buffer);
15508
15509 NavigationData {
15510 cursor_anchor,
15511 cursor_position,
15512 scroll_anchor,
15513 scroll_top_row,
15514 }
15515 }
15516
15517 fn navigation_entry(
15518 &self,
15519 cursor_anchor: Anchor,
15520 cx: &mut Context<Self>,
15521 ) -> Option<NavigationEntry> {
15522 let Some(history) = self.nav_history.clone() else {
15523 return None;
15524 };
15525 let data = self.navigation_data(cursor_anchor, cx);
15526 Some(history.navigation_entry(Some(Arc::new(data) as Arc<dyn Any + Send + Sync>)))
15527 }
15528
15529 fn push_to_nav_history(
15530 &mut self,
15531 cursor_anchor: Anchor,
15532 new_position: Option<Point>,
15533 is_deactivate: bool,
15534 always: bool,
15535 cx: &mut Context<Self>,
15536 ) {
15537 let data = self.navigation_data(cursor_anchor, cx);
15538 if let Some(nav_history) = self.nav_history.as_mut() {
15539 if let Some(new_position) = new_position {
15540 let row_delta = (new_position.row as i64 - data.cursor_position.row as i64).abs();
15541 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
15542 return;
15543 }
15544 }
15545
15546 nav_history.push(Some(data), cx);
15547 cx.emit(EditorEvent::PushedToNavHistory {
15548 anchor: cursor_anchor,
15549 is_deactivate,
15550 })
15551 }
15552 }
15553
15554 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
15555 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15556 let buffer = self.buffer.read(cx).snapshot(cx);
15557 let mut selection = self
15558 .selections
15559 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
15560 selection.set_head(buffer.len(), SelectionGoal::None);
15561 self.change_selections(Default::default(), window, cx, |s| {
15562 s.select(vec![selection]);
15563 });
15564 }
15565
15566 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
15567 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15568 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15569 s.select_ranges([Anchor::min()..Anchor::max()]);
15570 });
15571 }
15572
15573 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
15574 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15575 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15576 let mut selections = self.selections.all::<Point>(&display_map);
15577 let max_point = display_map.buffer_snapshot().max_point();
15578 for selection in &mut selections {
15579 let rows = selection.spanned_rows(true, &display_map);
15580 selection.start = Point::new(rows.start.0, 0);
15581 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
15582 selection.reversed = false;
15583 }
15584 self.change_selections(Default::default(), window, cx, |s| {
15585 s.select(selections);
15586 });
15587 }
15588
15589 pub fn split_selection_into_lines(
15590 &mut self,
15591 action: &SplitSelectionIntoLines,
15592 window: &mut Window,
15593 cx: &mut Context<Self>,
15594 ) {
15595 let selections = self
15596 .selections
15597 .all::<Point>(&self.display_snapshot(cx))
15598 .into_iter()
15599 .map(|selection| selection.start..selection.end)
15600 .collect::<Vec<_>>();
15601 self.unfold_ranges(&selections, true, false, cx);
15602
15603 let mut new_selection_ranges = Vec::new();
15604 {
15605 let buffer = self.buffer.read(cx).read(cx);
15606 for selection in selections {
15607 for row in selection.start.row..selection.end.row {
15608 let line_start = Point::new(row, 0);
15609 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
15610
15611 if action.keep_selections {
15612 // Keep the selection range for each line
15613 let selection_start = if row == selection.start.row {
15614 selection.start
15615 } else {
15616 line_start
15617 };
15618 new_selection_ranges.push(selection_start..line_end);
15619 } else {
15620 // Collapse to cursor at end of line
15621 new_selection_ranges.push(line_end..line_end);
15622 }
15623 }
15624
15625 let is_multiline_selection = selection.start.row != selection.end.row;
15626 // Don't insert last one if it's a multi-line selection ending at the start of a line,
15627 // so this action feels more ergonomic when paired with other selection operations
15628 let should_skip_last = is_multiline_selection && selection.end.column == 0;
15629 if !should_skip_last {
15630 if action.keep_selections {
15631 if is_multiline_selection {
15632 let line_start = Point::new(selection.end.row, 0);
15633 new_selection_ranges.push(line_start..selection.end);
15634 } else {
15635 new_selection_ranges.push(selection.start..selection.end);
15636 }
15637 } else {
15638 new_selection_ranges.push(selection.end..selection.end);
15639 }
15640 }
15641 }
15642 }
15643 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15644 s.select_ranges(new_selection_ranges);
15645 });
15646 }
15647
15648 pub fn add_selection_above(
15649 &mut self,
15650 action: &AddSelectionAbove,
15651 window: &mut Window,
15652 cx: &mut Context<Self>,
15653 ) {
15654 self.add_selection(true, action.skip_soft_wrap, window, cx);
15655 }
15656
15657 pub fn add_selection_below(
15658 &mut self,
15659 action: &AddSelectionBelow,
15660 window: &mut Window,
15661 cx: &mut Context<Self>,
15662 ) {
15663 self.add_selection(false, action.skip_soft_wrap, window, cx);
15664 }
15665
15666 fn add_selection(
15667 &mut self,
15668 above: bool,
15669 skip_soft_wrap: bool,
15670 window: &mut Window,
15671 cx: &mut Context<Self>,
15672 ) {
15673 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15674
15675 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15676 let all_selections = self.selections.all::<Point>(&display_map);
15677 let text_layout_details = self.text_layout_details(window, cx);
15678
15679 let (mut columnar_selections, new_selections_to_columnarize) = {
15680 if let Some(state) = self.add_selections_state.as_ref() {
15681 let columnar_selection_ids: HashSet<_> = state
15682 .groups
15683 .iter()
15684 .flat_map(|group| group.stack.iter())
15685 .copied()
15686 .collect();
15687
15688 all_selections
15689 .into_iter()
15690 .partition(|s| columnar_selection_ids.contains(&s.id))
15691 } else {
15692 (Vec::new(), all_selections)
15693 }
15694 };
15695
15696 let mut state = self
15697 .add_selections_state
15698 .take()
15699 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
15700
15701 for selection in new_selections_to_columnarize {
15702 let range = selection.display_range(&display_map).sorted();
15703 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
15704 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
15705 let positions = start_x.min(end_x)..start_x.max(end_x);
15706 let mut stack = Vec::new();
15707 for row in range.start.row().0..=range.end.row().0 {
15708 if let Some(selection) = self.selections.build_columnar_selection(
15709 &display_map,
15710 DisplayRow(row),
15711 &positions,
15712 selection.reversed,
15713 &text_layout_details,
15714 ) {
15715 stack.push(selection.id);
15716 columnar_selections.push(selection);
15717 }
15718 }
15719 if !stack.is_empty() {
15720 if above {
15721 stack.reverse();
15722 }
15723 state.groups.push(AddSelectionsGroup { above, stack });
15724 }
15725 }
15726
15727 let mut final_selections = Vec::new();
15728 let end_row = if above {
15729 DisplayRow(0)
15730 } else {
15731 display_map.max_point().row()
15732 };
15733
15734 // When `skip_soft_wrap` is true, we use UTF-16 columns instead of pixel
15735 // positions to place new selections, so we need to keep track of the
15736 // column range of the oldest selection in each group, because
15737 // intermediate selections may have been clamped to shorter lines.
15738 let mut goal_columns_by_selection_id = if skip_soft_wrap {
15739 let mut map = HashMap::default();
15740 for group in state.groups.iter() {
15741 if let Some(oldest_id) = group.stack.first() {
15742 if let Some(oldest_selection) =
15743 columnar_selections.iter().find(|s| s.id == *oldest_id)
15744 {
15745 let snapshot = display_map.buffer_snapshot();
15746 let start_col =
15747 snapshot.point_to_point_utf16(oldest_selection.start).column;
15748 let end_col = snapshot.point_to_point_utf16(oldest_selection.end).column;
15749 let goal_columns = start_col.min(end_col)..start_col.max(end_col);
15750 for id in &group.stack {
15751 map.insert(*id, goal_columns.clone());
15752 }
15753 }
15754 }
15755 }
15756 map
15757 } else {
15758 HashMap::default()
15759 };
15760
15761 let mut last_added_item_per_group = HashMap::default();
15762 for group in state.groups.iter_mut() {
15763 if let Some(last_id) = group.stack.last() {
15764 last_added_item_per_group.insert(*last_id, group);
15765 }
15766 }
15767
15768 for selection in columnar_selections {
15769 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
15770 if above == group.above {
15771 let range = selection.display_range(&display_map).sorted();
15772 debug_assert_eq!(range.start.row(), range.end.row());
15773 let row = range.start.row();
15774 let positions =
15775 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
15776 Pixels::from(start)..Pixels::from(end)
15777 } else {
15778 let start_x =
15779 display_map.x_for_display_point(range.start, &text_layout_details);
15780 let end_x =
15781 display_map.x_for_display_point(range.end, &text_layout_details);
15782 start_x.min(end_x)..start_x.max(end_x)
15783 };
15784
15785 let maybe_new_selection = if skip_soft_wrap {
15786 let goal_columns = goal_columns_by_selection_id
15787 .remove(&selection.id)
15788 .unwrap_or_else(|| {
15789 let snapshot = display_map.buffer_snapshot();
15790 let start_col =
15791 snapshot.point_to_point_utf16(selection.start).column;
15792 let end_col = snapshot.point_to_point_utf16(selection.end).column;
15793 start_col.min(end_col)..start_col.max(end_col)
15794 });
15795 self.selections.find_next_columnar_selection_by_buffer_row(
15796 &display_map,
15797 row,
15798 end_row,
15799 above,
15800 &goal_columns,
15801 selection.reversed,
15802 &text_layout_details,
15803 )
15804 } else {
15805 self.selections.find_next_columnar_selection_by_display_row(
15806 &display_map,
15807 row,
15808 end_row,
15809 above,
15810 &positions,
15811 selection.reversed,
15812 &text_layout_details,
15813 )
15814 };
15815
15816 if let Some(new_selection) = maybe_new_selection {
15817 group.stack.push(new_selection.id);
15818 if above {
15819 final_selections.push(new_selection);
15820 final_selections.push(selection);
15821 } else {
15822 final_selections.push(selection);
15823 final_selections.push(new_selection);
15824 }
15825 } else {
15826 final_selections.push(selection);
15827 }
15828 } else {
15829 group.stack.pop();
15830 }
15831 } else {
15832 final_selections.push(selection);
15833 }
15834 }
15835
15836 self.change_selections(Default::default(), window, cx, |s| {
15837 s.select(final_selections);
15838 });
15839
15840 let final_selection_ids: HashSet<_> = self
15841 .selections
15842 .all::<Point>(&display_map)
15843 .iter()
15844 .map(|s| s.id)
15845 .collect();
15846 state.groups.retain_mut(|group| {
15847 // selections might get merged above so we remove invalid items from stacks
15848 group.stack.retain(|id| final_selection_ids.contains(id));
15849
15850 // single selection in stack can be treated as initial state
15851 group.stack.len() > 1
15852 });
15853
15854 if !state.groups.is_empty() {
15855 self.add_selections_state = Some(state);
15856 }
15857 }
15858
15859 pub fn insert_snippet_at_selections(
15860 &mut self,
15861 action: &InsertSnippet,
15862 window: &mut Window,
15863 cx: &mut Context<Self>,
15864 ) {
15865 self.try_insert_snippet_at_selections(action, window, cx)
15866 .log_err();
15867 }
15868
15869 fn try_insert_snippet_at_selections(
15870 &mut self,
15871 action: &InsertSnippet,
15872 window: &mut Window,
15873 cx: &mut Context<Self>,
15874 ) -> Result<()> {
15875 let insertion_ranges = self
15876 .selections
15877 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15878 .into_iter()
15879 .map(|selection| selection.range())
15880 .collect_vec();
15881
15882 let snippet = if let Some(snippet_body) = &action.snippet {
15883 if action.language.is_none() && action.name.is_none() {
15884 Snippet::parse(snippet_body)?
15885 } else {
15886 bail!("`snippet` is mutually exclusive with `language` and `name`")
15887 }
15888 } else if let Some(name) = &action.name {
15889 let project = self.project().context("no project")?;
15890 let snippet_store = project.read(cx).snippets().read(cx);
15891 let snippet = snippet_store
15892 .snippets_for(action.language.clone(), cx)
15893 .into_iter()
15894 .find(|snippet| snippet.name == *name)
15895 .context("snippet not found")?;
15896 Snippet::parse(&snippet.body)?
15897 } else {
15898 // todo(andrew): open modal to select snippet
15899 bail!("`name` or `snippet` is required")
15900 };
15901
15902 self.insert_snippet(&insertion_ranges, snippet, window, cx)
15903 }
15904
15905 fn select_match_ranges(
15906 &mut self,
15907 range: Range<MultiBufferOffset>,
15908 reversed: bool,
15909 replace_newest: bool,
15910 auto_scroll: Option<Autoscroll>,
15911 window: &mut Window,
15912 cx: &mut Context<Editor>,
15913 ) {
15914 self.unfold_ranges(
15915 std::slice::from_ref(&range),
15916 false,
15917 auto_scroll.is_some(),
15918 cx,
15919 );
15920 let effects = if let Some(scroll) = auto_scroll {
15921 SelectionEffects::scroll(scroll)
15922 } else {
15923 SelectionEffects::no_scroll()
15924 };
15925 self.change_selections(effects, window, cx, |s| {
15926 if replace_newest {
15927 s.delete(s.newest_anchor().id);
15928 }
15929 if reversed {
15930 s.insert_range(range.end..range.start);
15931 } else {
15932 s.insert_range(range);
15933 }
15934 });
15935 }
15936
15937 pub fn select_next_match_internal(
15938 &mut self,
15939 display_map: &DisplaySnapshot,
15940 replace_newest: bool,
15941 autoscroll: Option<Autoscroll>,
15942 window: &mut Window,
15943 cx: &mut Context<Self>,
15944 ) -> Result<()> {
15945 let buffer = display_map.buffer_snapshot();
15946 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15947 if let Some(mut select_next_state) = self.select_next_state.take() {
15948 let query = &select_next_state.query;
15949 if !select_next_state.done {
15950 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15951 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15952 let mut next_selected_range = None;
15953
15954 let bytes_after_last_selection =
15955 buffer.bytes_in_range(last_selection.end..buffer.len());
15956 let bytes_before_first_selection =
15957 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
15958 let query_matches = query
15959 .stream_find_iter(bytes_after_last_selection)
15960 .map(|result| (last_selection.end, result))
15961 .chain(
15962 query
15963 .stream_find_iter(bytes_before_first_selection)
15964 .map(|result| (MultiBufferOffset(0), result)),
15965 );
15966
15967 for (start_offset, query_match) in query_matches {
15968 let query_match = query_match.unwrap(); // can only fail due to I/O
15969 let offset_range =
15970 start_offset + query_match.start()..start_offset + query_match.end();
15971
15972 if !select_next_state.wordwise
15973 || (!buffer.is_inside_word(offset_range.start, None)
15974 && !buffer.is_inside_word(offset_range.end, None))
15975 {
15976 let idx = selections
15977 .partition_point(|selection| selection.end <= offset_range.start);
15978 let overlaps = selections
15979 .get(idx)
15980 .map_or(false, |selection| selection.start < offset_range.end);
15981
15982 if !overlaps {
15983 next_selected_range = Some(offset_range);
15984 break;
15985 }
15986 }
15987 }
15988
15989 if let Some(next_selected_range) = next_selected_range {
15990 self.select_match_ranges(
15991 next_selected_range,
15992 last_selection.reversed,
15993 replace_newest,
15994 autoscroll,
15995 window,
15996 cx,
15997 );
15998 } else {
15999 select_next_state.done = true;
16000 }
16001 }
16002
16003 self.select_next_state = Some(select_next_state);
16004 } else {
16005 let mut only_carets = true;
16006 let mut same_text_selected = true;
16007 let mut selected_text = None;
16008
16009 let mut selections_iter = selections.iter().peekable();
16010 while let Some(selection) = selections_iter.next() {
16011 if selection.start != selection.end {
16012 only_carets = false;
16013 }
16014
16015 if same_text_selected {
16016 if selected_text.is_none() {
16017 selected_text =
16018 Some(buffer.text_for_range(selection.range()).collect::<String>());
16019 }
16020
16021 if let Some(next_selection) = selections_iter.peek() {
16022 if next_selection.len() == selection.len() {
16023 let next_selected_text = buffer
16024 .text_for_range(next_selection.range())
16025 .collect::<String>();
16026 if Some(next_selected_text) != selected_text {
16027 same_text_selected = false;
16028 selected_text = None;
16029 }
16030 } else {
16031 same_text_selected = false;
16032 selected_text = None;
16033 }
16034 }
16035 }
16036 }
16037
16038 if only_carets {
16039 for selection in &mut selections {
16040 let (word_range, _) = buffer.surrounding_word(selection.start, None);
16041 selection.start = word_range.start;
16042 selection.end = word_range.end;
16043 selection.goal = SelectionGoal::None;
16044 selection.reversed = false;
16045 self.select_match_ranges(
16046 selection.start..selection.end,
16047 selection.reversed,
16048 replace_newest,
16049 autoscroll,
16050 window,
16051 cx,
16052 );
16053 }
16054
16055 if selections.len() == 1 {
16056 let selection = selections
16057 .last()
16058 .expect("ensured that there's only one selection");
16059 let query = buffer
16060 .text_for_range(selection.start..selection.end)
16061 .collect::<String>();
16062 let is_empty = query.is_empty();
16063 let select_state = SelectNextState {
16064 query: self.build_query(&[query], cx)?,
16065 wordwise: true,
16066 done: is_empty,
16067 };
16068 self.select_next_state = Some(select_state);
16069 } else {
16070 self.select_next_state = None;
16071 }
16072 } else if let Some(selected_text) = selected_text {
16073 self.select_next_state = Some(SelectNextState {
16074 query: self.build_query(&[selected_text], cx)?,
16075 wordwise: false,
16076 done: false,
16077 });
16078 self.select_next_match_internal(
16079 display_map,
16080 replace_newest,
16081 autoscroll,
16082 window,
16083 cx,
16084 )?;
16085 }
16086 }
16087 Ok(())
16088 }
16089
16090 pub fn select_all_matches(
16091 &mut self,
16092 _action: &SelectAllMatches,
16093 window: &mut Window,
16094 cx: &mut Context<Self>,
16095 ) -> Result<()> {
16096 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16097
16098 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16099
16100 self.select_next_match_internal(&display_map, false, None, window, cx)?;
16101 let Some(select_next_state) = self.select_next_state.as_mut().filter(|state| !state.done)
16102 else {
16103 return Ok(());
16104 };
16105
16106 let mut new_selections = Vec::new();
16107
16108 let reversed = self
16109 .selections
16110 .oldest::<MultiBufferOffset>(&display_map)
16111 .reversed;
16112 let buffer = display_map.buffer_snapshot();
16113 let query_matches = select_next_state
16114 .query
16115 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
16116
16117 for query_match in query_matches.into_iter() {
16118 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
16119 let offset_range = if reversed {
16120 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
16121 } else {
16122 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
16123 };
16124
16125 if !select_next_state.wordwise
16126 || (!buffer.is_inside_word(offset_range.start, None)
16127 && !buffer.is_inside_word(offset_range.end, None))
16128 {
16129 new_selections.push(offset_range.start..offset_range.end);
16130 }
16131 }
16132
16133 select_next_state.done = true;
16134
16135 if new_selections.is_empty() {
16136 log::error!("bug: new_selections is empty in select_all_matches");
16137 return Ok(());
16138 }
16139
16140 self.unfold_ranges(&new_selections, false, false, cx);
16141 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
16142 selections.select_ranges(new_selections)
16143 });
16144
16145 Ok(())
16146 }
16147
16148 pub fn select_next(
16149 &mut self,
16150 action: &SelectNext,
16151 window: &mut Window,
16152 cx: &mut Context<Self>,
16153 ) -> Result<()> {
16154 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16155 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16156 self.select_next_match_internal(
16157 &display_map,
16158 action.replace_newest,
16159 Some(Autoscroll::newest()),
16160 window,
16161 cx,
16162 )
16163 }
16164
16165 pub fn select_previous(
16166 &mut self,
16167 action: &SelectPrevious,
16168 window: &mut Window,
16169 cx: &mut Context<Self>,
16170 ) -> Result<()> {
16171 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16172 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16173 let buffer = display_map.buffer_snapshot();
16174 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
16175 if let Some(mut select_prev_state) = self.select_prev_state.take() {
16176 let query = &select_prev_state.query;
16177 if !select_prev_state.done {
16178 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
16179 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
16180 let mut next_selected_range = None;
16181 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
16182 let bytes_before_last_selection =
16183 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
16184 let bytes_after_first_selection =
16185 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
16186 let query_matches = query
16187 .stream_find_iter(bytes_before_last_selection)
16188 .map(|result| (last_selection.start, result))
16189 .chain(
16190 query
16191 .stream_find_iter(bytes_after_first_selection)
16192 .map(|result| (buffer.len(), result)),
16193 );
16194 for (end_offset, query_match) in query_matches {
16195 let query_match = query_match.unwrap(); // can only fail due to I/O
16196 let offset_range =
16197 end_offset - query_match.end()..end_offset - query_match.start();
16198
16199 if !select_prev_state.wordwise
16200 || (!buffer.is_inside_word(offset_range.start, None)
16201 && !buffer.is_inside_word(offset_range.end, None))
16202 {
16203 next_selected_range = Some(offset_range);
16204 break;
16205 }
16206 }
16207
16208 if let Some(next_selected_range) = next_selected_range {
16209 self.select_match_ranges(
16210 next_selected_range,
16211 last_selection.reversed,
16212 action.replace_newest,
16213 Some(Autoscroll::newest()),
16214 window,
16215 cx,
16216 );
16217 } else {
16218 select_prev_state.done = true;
16219 }
16220 }
16221
16222 self.select_prev_state = Some(select_prev_state);
16223 } else {
16224 let mut only_carets = true;
16225 let mut same_text_selected = true;
16226 let mut selected_text = None;
16227
16228 let mut selections_iter = selections.iter().peekable();
16229 while let Some(selection) = selections_iter.next() {
16230 if selection.start != selection.end {
16231 only_carets = false;
16232 }
16233
16234 if same_text_selected {
16235 if selected_text.is_none() {
16236 selected_text =
16237 Some(buffer.text_for_range(selection.range()).collect::<String>());
16238 }
16239
16240 if let Some(next_selection) = selections_iter.peek() {
16241 if next_selection.len() == selection.len() {
16242 let next_selected_text = buffer
16243 .text_for_range(next_selection.range())
16244 .collect::<String>();
16245 if Some(next_selected_text) != selected_text {
16246 same_text_selected = false;
16247 selected_text = None;
16248 }
16249 } else {
16250 same_text_selected = false;
16251 selected_text = None;
16252 }
16253 }
16254 }
16255 }
16256
16257 if only_carets {
16258 for selection in &mut selections {
16259 let (word_range, _) = buffer.surrounding_word(selection.start, None);
16260 selection.start = word_range.start;
16261 selection.end = word_range.end;
16262 selection.goal = SelectionGoal::None;
16263 selection.reversed = false;
16264 self.select_match_ranges(
16265 selection.start..selection.end,
16266 selection.reversed,
16267 action.replace_newest,
16268 Some(Autoscroll::newest()),
16269 window,
16270 cx,
16271 );
16272 }
16273 if selections.len() == 1 {
16274 let selection = selections
16275 .last()
16276 .expect("ensured that there's only one selection");
16277 let query = buffer
16278 .text_for_range(selection.start..selection.end)
16279 .collect::<String>();
16280 let is_empty = query.is_empty();
16281 let select_state = SelectNextState {
16282 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
16283 wordwise: true,
16284 done: is_empty,
16285 };
16286 self.select_prev_state = Some(select_state);
16287 } else {
16288 self.select_prev_state = None;
16289 }
16290 } else if let Some(selected_text) = selected_text {
16291 self.select_prev_state = Some(SelectNextState {
16292 query: self
16293 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
16294 wordwise: false,
16295 done: false,
16296 });
16297 self.select_previous(action, window, cx)?;
16298 }
16299 }
16300 Ok(())
16301 }
16302
16303 /// Builds an `AhoCorasick` automaton from the provided patterns, while
16304 /// setting the case sensitivity based on the global
16305 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
16306 /// editor's settings.
16307 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
16308 where
16309 I: IntoIterator<Item = P>,
16310 P: AsRef<[u8]>,
16311 {
16312 let case_sensitive = self
16313 .select_next_is_case_sensitive
16314 .unwrap_or_else(|| EditorSettings::get_global(cx).search.case_sensitive);
16315
16316 let mut builder = AhoCorasickBuilder::new();
16317 builder.ascii_case_insensitive(!case_sensitive);
16318 builder.build(patterns)
16319 }
16320
16321 pub fn find_next_match(
16322 &mut self,
16323 _: &FindNextMatch,
16324 window: &mut Window,
16325 cx: &mut Context<Self>,
16326 ) -> Result<()> {
16327 let selections = self.selections.disjoint_anchors_arc();
16328 match selections.first() {
16329 Some(first) if selections.len() >= 2 => {
16330 self.change_selections(Default::default(), window, cx, |s| {
16331 s.select_ranges([first.range()]);
16332 });
16333 }
16334 _ => self.select_next(
16335 &SelectNext {
16336 replace_newest: true,
16337 },
16338 window,
16339 cx,
16340 )?,
16341 }
16342 Ok(())
16343 }
16344
16345 pub fn find_previous_match(
16346 &mut self,
16347 _: &FindPreviousMatch,
16348 window: &mut Window,
16349 cx: &mut Context<Self>,
16350 ) -> Result<()> {
16351 let selections = self.selections.disjoint_anchors_arc();
16352 match selections.last() {
16353 Some(last) if selections.len() >= 2 => {
16354 self.change_selections(Default::default(), window, cx, |s| {
16355 s.select_ranges([last.range()]);
16356 });
16357 }
16358 _ => self.select_previous(
16359 &SelectPrevious {
16360 replace_newest: true,
16361 },
16362 window,
16363 cx,
16364 )?,
16365 }
16366 Ok(())
16367 }
16368
16369 pub fn toggle_comments(
16370 &mut self,
16371 action: &ToggleComments,
16372 window: &mut Window,
16373 cx: &mut Context<Self>,
16374 ) {
16375 if self.read_only(cx) {
16376 return;
16377 }
16378 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16379 let text_layout_details = &self.text_layout_details(window, cx);
16380 self.transact(window, cx, |this, window, cx| {
16381 let mut selections = this
16382 .selections
16383 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
16384 let mut edits = Vec::new();
16385 let mut selection_edit_ranges = Vec::new();
16386 let mut last_toggled_row = None;
16387 let snapshot = this.buffer.read(cx).read(cx);
16388 let empty_str: Arc<str> = Arc::default();
16389 let mut suffixes_inserted = Vec::new();
16390 let ignore_indent = action.ignore_indent;
16391
16392 fn comment_prefix_range(
16393 snapshot: &MultiBufferSnapshot,
16394 row: MultiBufferRow,
16395 comment_prefix: &str,
16396 comment_prefix_whitespace: &str,
16397 ignore_indent: bool,
16398 ) -> Range<Point> {
16399 let indent_size = if ignore_indent {
16400 0
16401 } else {
16402 snapshot.indent_size_for_line(row).len
16403 };
16404
16405 let start = Point::new(row.0, indent_size);
16406
16407 let mut line_bytes = snapshot
16408 .bytes_in_range(start..snapshot.max_point())
16409 .flatten()
16410 .copied();
16411
16412 // If this line currently begins with the line comment prefix, then record
16413 // the range containing the prefix.
16414 if line_bytes
16415 .by_ref()
16416 .take(comment_prefix.len())
16417 .eq(comment_prefix.bytes())
16418 {
16419 // Include any whitespace that matches the comment prefix.
16420 let matching_whitespace_len = line_bytes
16421 .zip(comment_prefix_whitespace.bytes())
16422 .take_while(|(a, b)| a == b)
16423 .count() as u32;
16424 let end = Point::new(
16425 start.row,
16426 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
16427 );
16428 start..end
16429 } else {
16430 start..start
16431 }
16432 }
16433
16434 fn comment_suffix_range(
16435 snapshot: &MultiBufferSnapshot,
16436 row: MultiBufferRow,
16437 comment_suffix: &str,
16438 comment_suffix_has_leading_space: bool,
16439 ) -> Range<Point> {
16440 let end = Point::new(row.0, snapshot.line_len(row));
16441 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
16442
16443 let mut line_end_bytes = snapshot
16444 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
16445 .flatten()
16446 .copied();
16447
16448 let leading_space_len = if suffix_start_column > 0
16449 && line_end_bytes.next() == Some(b' ')
16450 && comment_suffix_has_leading_space
16451 {
16452 1
16453 } else {
16454 0
16455 };
16456
16457 // If this line currently begins with the line comment prefix, then record
16458 // the range containing the prefix.
16459 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
16460 let start = Point::new(end.row, suffix_start_column - leading_space_len);
16461 start..end
16462 } else {
16463 end..end
16464 }
16465 }
16466
16467 // TODO: Handle selections that cross excerpts
16468 for selection in &mut selections {
16469 let start_column = snapshot
16470 .indent_size_for_line(MultiBufferRow(selection.start.row))
16471 .len;
16472 let language = if let Some(language) =
16473 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
16474 {
16475 language
16476 } else {
16477 continue;
16478 };
16479
16480 selection_edit_ranges.clear();
16481
16482 // If multiple selections contain a given row, avoid processing that
16483 // row more than once.
16484 let mut start_row = MultiBufferRow(selection.start.row);
16485 if last_toggled_row == Some(start_row) {
16486 start_row = start_row.next_row();
16487 }
16488 let end_row =
16489 if selection.end.row > selection.start.row && selection.end.column == 0 {
16490 MultiBufferRow(selection.end.row - 1)
16491 } else {
16492 MultiBufferRow(selection.end.row)
16493 };
16494 last_toggled_row = Some(end_row);
16495
16496 if start_row > end_row {
16497 continue;
16498 }
16499
16500 // If the language has line comments, toggle those.
16501 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
16502
16503 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
16504 if ignore_indent {
16505 full_comment_prefixes = full_comment_prefixes
16506 .into_iter()
16507 .map(|s| Arc::from(s.trim_end()))
16508 .collect();
16509 }
16510
16511 if !full_comment_prefixes.is_empty() {
16512 let first_prefix = full_comment_prefixes
16513 .first()
16514 .expect("prefixes is non-empty");
16515 let prefix_trimmed_lengths = full_comment_prefixes
16516 .iter()
16517 .map(|p| p.trim_end_matches(' ').len())
16518 .collect::<SmallVec<[usize; 4]>>();
16519
16520 let mut all_selection_lines_are_comments = true;
16521
16522 for row in start_row.0..=end_row.0 {
16523 let row = MultiBufferRow(row);
16524 if start_row < end_row && snapshot.is_line_blank(row) {
16525 continue;
16526 }
16527
16528 let prefix_range = full_comment_prefixes
16529 .iter()
16530 .zip(prefix_trimmed_lengths.iter().copied())
16531 .map(|(prefix, trimmed_prefix_len)| {
16532 comment_prefix_range(
16533 snapshot.deref(),
16534 row,
16535 &prefix[..trimmed_prefix_len],
16536 &prefix[trimmed_prefix_len..],
16537 ignore_indent,
16538 )
16539 })
16540 .max_by_key(|range| range.end.column - range.start.column)
16541 .expect("prefixes is non-empty");
16542
16543 if prefix_range.is_empty() {
16544 all_selection_lines_are_comments = false;
16545 }
16546
16547 selection_edit_ranges.push(prefix_range);
16548 }
16549
16550 if all_selection_lines_are_comments {
16551 edits.extend(
16552 selection_edit_ranges
16553 .iter()
16554 .cloned()
16555 .map(|range| (range, empty_str.clone())),
16556 );
16557 } else {
16558 let min_column = selection_edit_ranges
16559 .iter()
16560 .map(|range| range.start.column)
16561 .min()
16562 .unwrap_or(0);
16563 edits.extend(selection_edit_ranges.iter().map(|range| {
16564 let position = Point::new(range.start.row, min_column);
16565 (position..position, first_prefix.clone())
16566 }));
16567 }
16568 } else if let Some(BlockCommentConfig {
16569 start: full_comment_prefix,
16570 end: comment_suffix,
16571 ..
16572 }) = language.block_comment()
16573 {
16574 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
16575 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
16576 let prefix_range = comment_prefix_range(
16577 snapshot.deref(),
16578 start_row,
16579 comment_prefix,
16580 comment_prefix_whitespace,
16581 ignore_indent,
16582 );
16583 let suffix_range = comment_suffix_range(
16584 snapshot.deref(),
16585 end_row,
16586 comment_suffix.trim_start_matches(' '),
16587 comment_suffix.starts_with(' '),
16588 );
16589
16590 if prefix_range.is_empty() || suffix_range.is_empty() {
16591 edits.push((
16592 prefix_range.start..prefix_range.start,
16593 full_comment_prefix.clone(),
16594 ));
16595 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
16596 suffixes_inserted.push((end_row, comment_suffix.len()));
16597 } else {
16598 edits.push((prefix_range, empty_str.clone()));
16599 edits.push((suffix_range, empty_str.clone()));
16600 }
16601 } else {
16602 continue;
16603 }
16604 }
16605
16606 drop(snapshot);
16607 this.buffer.update(cx, |buffer, cx| {
16608 buffer.edit(edits, None, cx);
16609 });
16610
16611 // Adjust selections so that they end before any comment suffixes that
16612 // were inserted.
16613 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
16614 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16615 let snapshot = this.buffer.read(cx).read(cx);
16616 for selection in &mut selections {
16617 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
16618 match row.cmp(&MultiBufferRow(selection.end.row)) {
16619 Ordering::Less => {
16620 suffixes_inserted.next();
16621 continue;
16622 }
16623 Ordering::Greater => break,
16624 Ordering::Equal => {
16625 if selection.end.column == snapshot.line_len(row) {
16626 if selection.is_empty() {
16627 selection.start.column -= suffix_len as u32;
16628 }
16629 selection.end.column -= suffix_len as u32;
16630 }
16631 break;
16632 }
16633 }
16634 }
16635 }
16636
16637 drop(snapshot);
16638 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
16639
16640 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16641 let selections_on_single_row = selections.windows(2).all(|selections| {
16642 selections[0].start.row == selections[1].start.row
16643 && selections[0].end.row == selections[1].end.row
16644 && selections[0].start.row == selections[0].end.row
16645 });
16646 let selections_selecting = selections
16647 .iter()
16648 .any(|selection| selection.start != selection.end);
16649 let advance_downwards = action.advance_downwards
16650 && selections_on_single_row
16651 && !selections_selecting
16652 && !matches!(this.mode, EditorMode::SingleLine);
16653
16654 if advance_downwards {
16655 let snapshot = this.buffer.read(cx).snapshot(cx);
16656
16657 this.change_selections(Default::default(), window, cx, |s| {
16658 s.move_cursors_with(&mut |display_snapshot, display_point, _| {
16659 let mut point = display_point.to_point(display_snapshot);
16660 point.row += 1;
16661 point = snapshot.clip_point(point, Bias::Left);
16662 let display_point = point.to_display_point(display_snapshot);
16663 let goal = SelectionGoal::HorizontalPosition(
16664 display_snapshot
16665 .x_for_display_point(display_point, text_layout_details)
16666 .into(),
16667 );
16668 (display_point, goal)
16669 })
16670 });
16671 }
16672 });
16673 }
16674
16675 pub fn select_enclosing_symbol(
16676 &mut self,
16677 _: &SelectEnclosingSymbol,
16678 window: &mut Window,
16679 cx: &mut Context<Self>,
16680 ) {
16681 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16682
16683 let buffer = self.buffer.read(cx).snapshot(cx);
16684 let old_selections = self
16685 .selections
16686 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16687 .into_boxed_slice();
16688
16689 fn update_selection(
16690 selection: &Selection<MultiBufferOffset>,
16691 buffer_snap: &MultiBufferSnapshot,
16692 ) -> Option<Selection<MultiBufferOffset>> {
16693 let cursor = selection.head();
16694 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
16695 for symbol in symbols.iter().rev() {
16696 let start = symbol.range.start.to_offset(buffer_snap);
16697 let end = symbol.range.end.to_offset(buffer_snap);
16698 let new_range = start..end;
16699 if start < selection.start || end > selection.end {
16700 return Some(Selection {
16701 id: selection.id,
16702 start: new_range.start,
16703 end: new_range.end,
16704 goal: SelectionGoal::None,
16705 reversed: selection.reversed,
16706 });
16707 }
16708 }
16709 None
16710 }
16711
16712 let mut selected_larger_symbol = false;
16713 let new_selections = old_selections
16714 .iter()
16715 .map(|selection| match update_selection(selection, &buffer) {
16716 Some(new_selection) => {
16717 if new_selection.range() != selection.range() {
16718 selected_larger_symbol = true;
16719 }
16720 new_selection
16721 }
16722 None => selection.clone(),
16723 })
16724 .collect::<Vec<_>>();
16725
16726 if selected_larger_symbol {
16727 self.change_selections(Default::default(), window, cx, |s| {
16728 s.select(new_selections);
16729 });
16730 }
16731 }
16732
16733 pub fn select_larger_syntax_node(
16734 &mut self,
16735 _: &SelectLargerSyntaxNode,
16736 window: &mut Window,
16737 cx: &mut Context<Self>,
16738 ) {
16739 let Some(visible_row_count) = self.visible_row_count() else {
16740 return;
16741 };
16742 let old_selections: Box<[_]> = self
16743 .selections
16744 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16745 .into();
16746 if old_selections.is_empty() {
16747 return;
16748 }
16749
16750 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16751
16752 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16753 let buffer = self.buffer.read(cx).snapshot(cx);
16754
16755 let mut selected_larger_node = false;
16756 let mut new_selections = old_selections
16757 .iter()
16758 .map(|selection| {
16759 let old_range = selection.start..selection.end;
16760
16761 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
16762 // manually select word at selection
16763 if ["string_content", "inline"].contains(&node.kind()) {
16764 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
16765 // ignore if word is already selected
16766 if !word_range.is_empty() && old_range != word_range {
16767 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
16768 // only select word if start and end point belongs to same word
16769 if word_range == last_word_range {
16770 selected_larger_node = true;
16771 return Selection {
16772 id: selection.id,
16773 start: word_range.start,
16774 end: word_range.end,
16775 goal: SelectionGoal::None,
16776 reversed: selection.reversed,
16777 };
16778 }
16779 }
16780 }
16781 }
16782
16783 let mut new_range = old_range.clone();
16784 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
16785 new_range = range;
16786 if !node.is_named() {
16787 continue;
16788 }
16789 if !display_map.intersects_fold(new_range.start)
16790 && !display_map.intersects_fold(new_range.end)
16791 {
16792 break;
16793 }
16794 }
16795
16796 selected_larger_node |= new_range != old_range;
16797 Selection {
16798 id: selection.id,
16799 start: new_range.start,
16800 end: new_range.end,
16801 goal: SelectionGoal::None,
16802 reversed: selection.reversed,
16803 }
16804 })
16805 .collect::<Vec<_>>();
16806
16807 if !selected_larger_node {
16808 return; // don't put this call in the history
16809 }
16810
16811 // scroll based on transformation done to the last selection created by the user
16812 let (last_old, last_new) = old_selections
16813 .last()
16814 .zip(new_selections.last().cloned())
16815 .expect("old_selections isn't empty");
16816
16817 let is_selection_reversed = if new_selections.len() == 1 {
16818 let should_be_reversed = last_old.start != last_new.start;
16819 new_selections.last_mut().expect("checked above").reversed = should_be_reversed;
16820 should_be_reversed
16821 } else {
16822 last_new.reversed
16823 };
16824
16825 if selected_larger_node {
16826 self.select_syntax_node_history.disable_clearing = true;
16827 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16828 s.select(new_selections.clone());
16829 });
16830 self.select_syntax_node_history.disable_clearing = false;
16831 }
16832
16833 let start_row = last_new.start.to_display_point(&display_map).row().0;
16834 let end_row = last_new.end.to_display_point(&display_map).row().0;
16835 let selection_height = end_row - start_row + 1;
16836 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
16837
16838 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
16839 let scroll_behavior = if fits_on_the_screen {
16840 self.request_autoscroll(Autoscroll::fit(), cx);
16841 SelectSyntaxNodeScrollBehavior::FitSelection
16842 } else if is_selection_reversed {
16843 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16844 SelectSyntaxNodeScrollBehavior::CursorTop
16845 } else {
16846 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16847 SelectSyntaxNodeScrollBehavior::CursorBottom
16848 };
16849
16850 let old_selections: Box<[Selection<Anchor>]> = old_selections
16851 .iter()
16852 .map(|s| s.map(|offset| buffer.anchor_before(offset)))
16853 .collect();
16854 self.select_syntax_node_history.push((
16855 old_selections,
16856 scroll_behavior,
16857 is_selection_reversed,
16858 ));
16859 }
16860
16861 pub fn select_smaller_syntax_node(
16862 &mut self,
16863 _: &SelectSmallerSyntaxNode,
16864 window: &mut Window,
16865 cx: &mut Context<Self>,
16866 ) {
16867 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16868
16869 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
16870 self.select_syntax_node_history.pop()
16871 {
16872 if let Some(selection) = selections.last_mut() {
16873 selection.reversed = is_selection_reversed;
16874 }
16875
16876 let snapshot = self.buffer.read(cx).snapshot(cx);
16877 let selections: Vec<Selection<MultiBufferOffset>> = selections
16878 .iter()
16879 .map(|s| s.map(|anchor| anchor.to_offset(&snapshot)))
16880 .collect();
16881
16882 self.select_syntax_node_history.disable_clearing = true;
16883 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16884 s.select(selections);
16885 });
16886 self.select_syntax_node_history.disable_clearing = false;
16887
16888 match scroll_behavior {
16889 SelectSyntaxNodeScrollBehavior::CursorTop => {
16890 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16891 }
16892 SelectSyntaxNodeScrollBehavior::FitSelection => {
16893 self.request_autoscroll(Autoscroll::fit(), cx);
16894 }
16895 SelectSyntaxNodeScrollBehavior::CursorBottom => {
16896 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16897 }
16898 }
16899 }
16900 }
16901
16902 pub fn unwrap_syntax_node(
16903 &mut self,
16904 _: &UnwrapSyntaxNode,
16905 window: &mut Window,
16906 cx: &mut Context<Self>,
16907 ) {
16908 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16909
16910 let buffer = self.buffer.read(cx).snapshot(cx);
16911 let selections = self
16912 .selections
16913 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16914 .into_iter()
16915 // subtracting the offset requires sorting
16916 .sorted_by_key(|i| i.start);
16917
16918 let full_edits = selections
16919 .into_iter()
16920 .filter_map(|selection| {
16921 let child = if selection.is_empty()
16922 && let Some((_, ancestor_range)) =
16923 buffer.syntax_ancestor(selection.start..selection.end)
16924 {
16925 ancestor_range
16926 } else {
16927 selection.range()
16928 };
16929
16930 let mut parent = child.clone();
16931 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
16932 parent = ancestor_range;
16933 if parent.start < child.start || parent.end > child.end {
16934 break;
16935 }
16936 }
16937
16938 if parent == child {
16939 return None;
16940 }
16941 let text = buffer.text_for_range(child).collect::<String>();
16942 Some((selection.id, parent, text))
16943 })
16944 .collect::<Vec<_>>();
16945 if full_edits.is_empty() {
16946 return;
16947 }
16948
16949 self.transact(window, cx, |this, window, cx| {
16950 this.buffer.update(cx, |buffer, cx| {
16951 buffer.edit(
16952 full_edits
16953 .iter()
16954 .map(|(_, p, t)| (p.clone(), t.clone()))
16955 .collect::<Vec<_>>(),
16956 None,
16957 cx,
16958 );
16959 });
16960 this.change_selections(Default::default(), window, cx, |s| {
16961 let mut offset = 0;
16962 let mut selections = vec![];
16963 for (id, parent, text) in full_edits {
16964 let start = parent.start - offset;
16965 offset += (parent.end - parent.start) - text.len();
16966 selections.push(Selection {
16967 id,
16968 start,
16969 end: start + text.len(),
16970 reversed: false,
16971 goal: Default::default(),
16972 });
16973 }
16974 s.select(selections);
16975 });
16976 });
16977 }
16978
16979 pub fn select_next_syntax_node(
16980 &mut self,
16981 _: &SelectNextSyntaxNode,
16982 window: &mut Window,
16983 cx: &mut Context<Self>,
16984 ) {
16985 let old_selections: Box<[_]> = self
16986 .selections
16987 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16988 .into();
16989 if old_selections.is_empty() {
16990 return;
16991 }
16992
16993 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16994
16995 let buffer = self.buffer.read(cx).snapshot(cx);
16996 let mut selected_sibling = false;
16997
16998 let new_selections = old_selections
16999 .iter()
17000 .map(|selection| {
17001 let old_range = selection.start..selection.end;
17002
17003 let old_range =
17004 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
17005 let excerpt = buffer.excerpt_containing(old_range.clone());
17006
17007 if let Some(mut excerpt) = excerpt
17008 && let Some(node) = excerpt
17009 .buffer()
17010 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
17011 {
17012 let new_range = excerpt.map_range_from_buffer(
17013 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
17014 );
17015 selected_sibling = true;
17016 Selection {
17017 id: selection.id,
17018 start: new_range.start,
17019 end: new_range.end,
17020 goal: SelectionGoal::None,
17021 reversed: selection.reversed,
17022 }
17023 } else {
17024 selection.clone()
17025 }
17026 })
17027 .collect::<Vec<_>>();
17028
17029 if selected_sibling {
17030 self.change_selections(
17031 SelectionEffects::scroll(Autoscroll::fit()),
17032 window,
17033 cx,
17034 |s| {
17035 s.select(new_selections);
17036 },
17037 );
17038 }
17039 }
17040
17041 pub fn select_prev_syntax_node(
17042 &mut self,
17043 _: &SelectPreviousSyntaxNode,
17044 window: &mut Window,
17045 cx: &mut Context<Self>,
17046 ) {
17047 let old_selections: Box<[_]> = self
17048 .selections
17049 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
17050 .into();
17051 if old_selections.is_empty() {
17052 return;
17053 }
17054
17055 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17056
17057 let buffer = self.buffer.read(cx).snapshot(cx);
17058 let mut selected_sibling = false;
17059
17060 let new_selections = old_selections
17061 .iter()
17062 .map(|selection| {
17063 let old_range = selection.start..selection.end;
17064 let old_range =
17065 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
17066 let excerpt = buffer.excerpt_containing(old_range.clone());
17067
17068 if let Some(mut excerpt) = excerpt
17069 && let Some(node) = excerpt
17070 .buffer()
17071 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
17072 {
17073 let new_range = excerpt.map_range_from_buffer(
17074 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
17075 );
17076 selected_sibling = true;
17077 Selection {
17078 id: selection.id,
17079 start: new_range.start,
17080 end: new_range.end,
17081 goal: SelectionGoal::None,
17082 reversed: selection.reversed,
17083 }
17084 } else {
17085 selection.clone()
17086 }
17087 })
17088 .collect::<Vec<_>>();
17089
17090 if selected_sibling {
17091 self.change_selections(
17092 SelectionEffects::scroll(Autoscroll::fit()),
17093 window,
17094 cx,
17095 |s| {
17096 s.select(new_selections);
17097 },
17098 );
17099 }
17100 }
17101
17102 pub fn move_to_start_of_larger_syntax_node(
17103 &mut self,
17104 _: &MoveToStartOfLargerSyntaxNode,
17105 window: &mut Window,
17106 cx: &mut Context<Self>,
17107 ) {
17108 self.move_cursors_to_syntax_nodes(window, cx, false);
17109 }
17110
17111 pub fn move_to_end_of_larger_syntax_node(
17112 &mut self,
17113 _: &MoveToEndOfLargerSyntaxNode,
17114 window: &mut Window,
17115 cx: &mut Context<Self>,
17116 ) {
17117 self.move_cursors_to_syntax_nodes(window, cx, true);
17118 }
17119
17120 fn find_syntax_node_boundary(
17121 &self,
17122 selection_pos: MultiBufferOffset,
17123 move_to_end: bool,
17124 display_map: &DisplaySnapshot,
17125 buffer: &MultiBufferSnapshot,
17126 ) -> MultiBufferOffset {
17127 let old_range = selection_pos..selection_pos;
17128 let mut new_pos = selection_pos;
17129 let mut search_range = old_range;
17130 while let Some((node, range)) = buffer.syntax_ancestor(search_range.clone()) {
17131 search_range = range.clone();
17132 if !node.is_named()
17133 || display_map.intersects_fold(range.start)
17134 || display_map.intersects_fold(range.end)
17135 // If cursor is already at the end of the syntax node, continue searching
17136 || (move_to_end && range.end == selection_pos)
17137 // If cursor is already at the start of the syntax node, continue searching
17138 || (!move_to_end && range.start == selection_pos)
17139 {
17140 continue;
17141 }
17142
17143 // If we found a string_content node, find the largest parent that is still string_content
17144 // Enables us to skip to the end of strings without taking multiple steps inside the string
17145 let (_, final_range) = if node.kind() == "string_content" {
17146 let mut current_node = node;
17147 let mut current_range = range;
17148 while let Some((parent, parent_range)) =
17149 buffer.syntax_ancestor(current_range.clone())
17150 {
17151 if parent.kind() == "string_content" {
17152 current_node = parent;
17153 current_range = parent_range;
17154 } else {
17155 break;
17156 }
17157 }
17158
17159 (current_node, current_range)
17160 } else {
17161 (node, range)
17162 };
17163
17164 new_pos = if move_to_end {
17165 final_range.end
17166 } else {
17167 final_range.start
17168 };
17169
17170 break;
17171 }
17172
17173 new_pos
17174 }
17175
17176 fn move_cursors_to_syntax_nodes(
17177 &mut self,
17178 window: &mut Window,
17179 cx: &mut Context<Self>,
17180 move_to_end: bool,
17181 ) -> bool {
17182 let old_selections: Box<[_]> = self
17183 .selections
17184 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
17185 .into();
17186 if old_selections.is_empty() {
17187 return false;
17188 }
17189
17190 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17191
17192 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17193 let buffer = self.buffer.read(cx).snapshot(cx);
17194
17195 let mut any_cursor_moved = false;
17196 let new_selections = old_selections
17197 .iter()
17198 .map(|selection| {
17199 if !selection.is_empty() {
17200 return selection.clone();
17201 }
17202
17203 let selection_pos = selection.head();
17204 let new_pos = self.find_syntax_node_boundary(
17205 selection_pos,
17206 move_to_end,
17207 &display_map,
17208 &buffer,
17209 );
17210
17211 any_cursor_moved |= new_pos != selection_pos;
17212
17213 Selection {
17214 id: selection.id,
17215 start: new_pos,
17216 end: new_pos,
17217 goal: SelectionGoal::None,
17218 reversed: false,
17219 }
17220 })
17221 .collect::<Vec<_>>();
17222
17223 self.change_selections(Default::default(), window, cx, |s| {
17224 s.select(new_selections);
17225 });
17226 self.request_autoscroll(Autoscroll::newest(), cx);
17227
17228 any_cursor_moved
17229 }
17230
17231 pub fn select_to_start_of_larger_syntax_node(
17232 &mut self,
17233 _: &SelectToStartOfLargerSyntaxNode,
17234 window: &mut Window,
17235 cx: &mut Context<Self>,
17236 ) {
17237 self.select_to_syntax_nodes(window, cx, false);
17238 }
17239
17240 pub fn select_to_end_of_larger_syntax_node(
17241 &mut self,
17242 _: &SelectToEndOfLargerSyntaxNode,
17243 window: &mut Window,
17244 cx: &mut Context<Self>,
17245 ) {
17246 self.select_to_syntax_nodes(window, cx, true);
17247 }
17248
17249 fn select_to_syntax_nodes(
17250 &mut self,
17251 window: &mut Window,
17252 cx: &mut Context<Self>,
17253 move_to_end: bool,
17254 ) {
17255 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17256
17257 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17258 let buffer = self.buffer.read(cx).snapshot(cx);
17259 let old_selections = self.selections.all::<MultiBufferOffset>(&display_map);
17260
17261 let new_selections = old_selections
17262 .iter()
17263 .map(|selection| {
17264 let new_pos = self.find_syntax_node_boundary(
17265 selection.head(),
17266 move_to_end,
17267 &display_map,
17268 &buffer,
17269 );
17270
17271 let mut new_selection = selection.clone();
17272 new_selection.set_head(new_pos, SelectionGoal::None);
17273 new_selection
17274 })
17275 .collect::<Vec<_>>();
17276
17277 self.change_selections(Default::default(), window, cx, |s| {
17278 s.select(new_selections);
17279 });
17280 }
17281
17282 pub fn move_to_enclosing_bracket(
17283 &mut self,
17284 _: &MoveToEnclosingBracket,
17285 window: &mut Window,
17286 cx: &mut Context<Self>,
17287 ) {
17288 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17289 self.change_selections(Default::default(), window, cx, |s| {
17290 s.move_offsets_with(&mut |snapshot, selection| {
17291 let Some(enclosing_bracket_ranges) =
17292 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
17293 else {
17294 return;
17295 };
17296
17297 let mut best_length = usize::MAX;
17298 let mut best_inside = false;
17299 let mut best_in_bracket_range = false;
17300 let mut best_destination = None;
17301 for (open, close) in enclosing_bracket_ranges {
17302 let close = close.to_inclusive();
17303 let length = *close.end() - open.start;
17304 let inside = selection.start >= open.end && selection.end <= *close.start();
17305 let in_bracket_range = open.to_inclusive().contains(&selection.head())
17306 || close.contains(&selection.head());
17307
17308 // If best is next to a bracket and current isn't, skip
17309 if !in_bracket_range && best_in_bracket_range {
17310 continue;
17311 }
17312
17313 // Prefer smaller lengths unless best is inside and current isn't
17314 if length > best_length && (best_inside || !inside) {
17315 continue;
17316 }
17317
17318 best_length = length;
17319 best_inside = inside;
17320 best_in_bracket_range = in_bracket_range;
17321 best_destination = Some(
17322 if close.contains(&selection.start) && close.contains(&selection.end) {
17323 if inside { open.end } else { open.start }
17324 } else if inside {
17325 *close.start()
17326 } else {
17327 *close.end()
17328 },
17329 );
17330 }
17331
17332 if let Some(destination) = best_destination {
17333 selection.collapse_to(destination, SelectionGoal::None);
17334 }
17335 })
17336 });
17337 }
17338
17339 pub fn undo_selection(
17340 &mut self,
17341 _: &UndoSelection,
17342 window: &mut Window,
17343 cx: &mut Context<Self>,
17344 ) {
17345 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17346 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
17347 self.selection_history.mode = SelectionHistoryMode::Undoing;
17348 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17349 this.end_selection(window, cx);
17350 this.change_selections(
17351 SelectionEffects::scroll(Autoscroll::newest()),
17352 window,
17353 cx,
17354 |s| s.select_anchors(entry.selections.to_vec()),
17355 );
17356 });
17357 self.selection_history.mode = SelectionHistoryMode::Normal;
17358
17359 self.select_next_state = entry.select_next_state;
17360 self.select_prev_state = entry.select_prev_state;
17361 self.add_selections_state = entry.add_selections_state;
17362 }
17363 }
17364
17365 pub fn redo_selection(
17366 &mut self,
17367 _: &RedoSelection,
17368 window: &mut Window,
17369 cx: &mut Context<Self>,
17370 ) {
17371 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17372 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
17373 self.selection_history.mode = SelectionHistoryMode::Redoing;
17374 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17375 this.end_selection(window, cx);
17376 this.change_selections(
17377 SelectionEffects::scroll(Autoscroll::newest()),
17378 window,
17379 cx,
17380 |s| s.select_anchors(entry.selections.to_vec()),
17381 );
17382 });
17383 self.selection_history.mode = SelectionHistoryMode::Normal;
17384
17385 self.select_next_state = entry.select_next_state;
17386 self.select_prev_state = entry.select_prev_state;
17387 self.add_selections_state = entry.add_selections_state;
17388 }
17389 }
17390
17391 pub fn expand_excerpts(
17392 &mut self,
17393 action: &ExpandExcerpts,
17394 _: &mut Window,
17395 cx: &mut Context<Self>,
17396 ) {
17397 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
17398 }
17399
17400 pub fn expand_excerpts_down(
17401 &mut self,
17402 action: &ExpandExcerptsDown,
17403 _: &mut Window,
17404 cx: &mut Context<Self>,
17405 ) {
17406 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
17407 }
17408
17409 pub fn expand_excerpts_up(
17410 &mut self,
17411 action: &ExpandExcerptsUp,
17412 _: &mut Window,
17413 cx: &mut Context<Self>,
17414 ) {
17415 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
17416 }
17417
17418 pub fn expand_excerpts_for_direction(
17419 &mut self,
17420 lines: u32,
17421 direction: ExpandExcerptDirection,
17422 cx: &mut Context<Self>,
17423 ) {
17424 let selections = self.selections.disjoint_anchors_arc();
17425
17426 let lines = if lines == 0 {
17427 EditorSettings::get_global(cx).expand_excerpt_lines
17428 } else {
17429 lines
17430 };
17431
17432 let snapshot = self.buffer.read(cx).snapshot(cx);
17433 let excerpt_ids = selections
17434 .iter()
17435 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
17436 .unique()
17437 .sorted()
17438 .collect::<Vec<_>>();
17439
17440 if self.delegate_expand_excerpts {
17441 cx.emit(EditorEvent::ExpandExcerptsRequested {
17442 excerpt_ids,
17443 lines,
17444 direction,
17445 });
17446 return;
17447 }
17448
17449 self.buffer.update(cx, |buffer, cx| {
17450 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
17451 })
17452 }
17453
17454 pub fn expand_excerpt(
17455 &mut self,
17456 excerpt: ExcerptId,
17457 direction: ExpandExcerptDirection,
17458 window: &mut Window,
17459 cx: &mut Context<Self>,
17460 ) {
17461 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
17462
17463 if self.delegate_expand_excerpts {
17464 cx.emit(EditorEvent::ExpandExcerptsRequested {
17465 excerpt_ids: vec![excerpt],
17466 lines: lines_to_expand,
17467 direction,
17468 });
17469 return;
17470 }
17471
17472 let current_scroll_position = self.scroll_position(cx);
17473 let mut scroll = None;
17474
17475 if direction == ExpandExcerptDirection::Down {
17476 let multi_buffer = self.buffer.read(cx);
17477 let snapshot = multi_buffer.snapshot(cx);
17478 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
17479 && let Some(buffer) = multi_buffer.buffer(buffer_id)
17480 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
17481 {
17482 let buffer_snapshot = buffer.read(cx).snapshot();
17483 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
17484 let last_row = buffer_snapshot.max_point().row;
17485 let lines_below = last_row.saturating_sub(excerpt_end_row);
17486 if lines_below >= lines_to_expand {
17487 scroll = Some(
17488 current_scroll_position
17489 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
17490 );
17491 }
17492 }
17493 }
17494 if direction == ExpandExcerptDirection::Up
17495 && self
17496 .buffer
17497 .read(cx)
17498 .snapshot(cx)
17499 .excerpt_before(excerpt)
17500 .is_none()
17501 {
17502 scroll = Some(current_scroll_position);
17503 }
17504
17505 self.buffer.update(cx, |buffer, cx| {
17506 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
17507 });
17508
17509 if let Some(new_scroll_position) = scroll {
17510 self.set_scroll_position(new_scroll_position, window, cx);
17511 }
17512 }
17513
17514 pub fn go_to_singleton_buffer_point(
17515 &mut self,
17516 point: Point,
17517 window: &mut Window,
17518 cx: &mut Context<Self>,
17519 ) {
17520 self.go_to_singleton_buffer_range(point..point, window, cx);
17521 }
17522
17523 pub fn go_to_singleton_buffer_range(
17524 &mut self,
17525 range: Range<Point>,
17526 window: &mut Window,
17527 cx: &mut Context<Self>,
17528 ) {
17529 let multibuffer = self.buffer().read(cx);
17530 let Some(buffer) = multibuffer.as_singleton() else {
17531 return;
17532 };
17533 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
17534 return;
17535 };
17536 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
17537 return;
17538 };
17539 self.change_selections(
17540 SelectionEffects::default().nav_history(true),
17541 window,
17542 cx,
17543 |s| s.select_anchor_ranges([start..end]),
17544 );
17545 }
17546
17547 pub fn go_to_diagnostic(
17548 &mut self,
17549 action: &GoToDiagnostic,
17550 window: &mut Window,
17551 cx: &mut Context<Self>,
17552 ) {
17553 if !self.diagnostics_enabled() {
17554 return;
17555 }
17556 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17557 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
17558 }
17559
17560 pub fn go_to_prev_diagnostic(
17561 &mut self,
17562 action: &GoToPreviousDiagnostic,
17563 window: &mut Window,
17564 cx: &mut Context<Self>,
17565 ) {
17566 if !self.diagnostics_enabled() {
17567 return;
17568 }
17569 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17570 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
17571 }
17572
17573 pub fn go_to_diagnostic_impl(
17574 &mut self,
17575 direction: Direction,
17576 severity: GoToDiagnosticSeverityFilter,
17577 window: &mut Window,
17578 cx: &mut Context<Self>,
17579 ) {
17580 let buffer = self.buffer.read(cx).snapshot(cx);
17581 let selection = self
17582 .selections
17583 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
17584
17585 let mut active_group_id = None;
17586 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
17587 && active_group.active_range.start.to_offset(&buffer) == selection.start
17588 {
17589 active_group_id = Some(active_group.group_id);
17590 }
17591
17592 fn filtered<'a>(
17593 severity: GoToDiagnosticSeverityFilter,
17594 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
17595 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
17596 diagnostics
17597 .filter(move |entry| severity.matches(entry.diagnostic.severity))
17598 .filter(|entry| entry.range.start != entry.range.end)
17599 .filter(|entry| !entry.diagnostic.is_unnecessary)
17600 }
17601
17602 let before = filtered(
17603 severity,
17604 buffer
17605 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
17606 .filter(|entry| entry.range.start <= selection.start),
17607 );
17608 let after = filtered(
17609 severity,
17610 buffer
17611 .diagnostics_in_range(selection.start..buffer.len())
17612 .filter(|entry| entry.range.start >= selection.start),
17613 );
17614
17615 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
17616 if direction == Direction::Prev {
17617 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
17618 {
17619 for diagnostic in prev_diagnostics.into_iter().rev() {
17620 if diagnostic.range.start != selection.start
17621 || active_group_id
17622 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
17623 {
17624 found = Some(diagnostic);
17625 break 'outer;
17626 }
17627 }
17628 }
17629 } else {
17630 for diagnostic in after.chain(before) {
17631 if diagnostic.range.start != selection.start
17632 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
17633 {
17634 found = Some(diagnostic);
17635 break;
17636 }
17637 }
17638 }
17639 let Some(next_diagnostic) = found else {
17640 return;
17641 };
17642
17643 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
17644 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
17645 return;
17646 };
17647 let snapshot = self.snapshot(window, cx);
17648 if snapshot.intersects_fold(next_diagnostic.range.start) {
17649 self.unfold_ranges(
17650 std::slice::from_ref(&next_diagnostic.range),
17651 true,
17652 false,
17653 cx,
17654 );
17655 }
17656 self.change_selections(Default::default(), window, cx, |s| {
17657 s.select_ranges(vec![
17658 next_diagnostic.range.start..next_diagnostic.range.start,
17659 ])
17660 });
17661 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
17662 self.refresh_edit_prediction(false, true, window, cx);
17663 }
17664
17665 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
17666 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17667 let snapshot = self.snapshot(window, cx);
17668 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
17669 self.go_to_hunk_before_or_after_position(
17670 &snapshot,
17671 selection.head(),
17672 Direction::Next,
17673 true,
17674 window,
17675 cx,
17676 );
17677 }
17678
17679 pub fn go_to_hunk_before_or_after_position(
17680 &mut self,
17681 snapshot: &EditorSnapshot,
17682 position: Point,
17683 direction: Direction,
17684 wrap_around: bool,
17685 window: &mut Window,
17686 cx: &mut Context<Editor>,
17687 ) {
17688 let row = if direction == Direction::Next {
17689 self.hunk_after_position(snapshot, position, wrap_around)
17690 .map(|hunk| hunk.row_range.start)
17691 } else {
17692 self.hunk_before_position(snapshot, position, wrap_around)
17693 };
17694
17695 if let Some(row) = row {
17696 let destination = Point::new(row.0, 0);
17697 let autoscroll = Autoscroll::center();
17698
17699 self.unfold_ranges(&[destination..destination], false, false, cx);
17700 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17701 s.select_ranges([destination..destination]);
17702 });
17703 }
17704 }
17705
17706 fn hunk_after_position(
17707 &mut self,
17708 snapshot: &EditorSnapshot,
17709 position: Point,
17710 wrap_around: bool,
17711 ) -> Option<MultiBufferDiffHunk> {
17712 let result = snapshot
17713 .buffer_snapshot()
17714 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
17715 .find(|hunk| hunk.row_range.start.0 > position.row);
17716
17717 if wrap_around {
17718 result.or_else(|| {
17719 snapshot
17720 .buffer_snapshot()
17721 .diff_hunks_in_range(Point::zero()..position)
17722 .find(|hunk| hunk.row_range.end.0 < position.row)
17723 })
17724 } else {
17725 result
17726 }
17727 }
17728
17729 fn go_to_prev_hunk(
17730 &mut self,
17731 _: &GoToPreviousHunk,
17732 window: &mut Window,
17733 cx: &mut Context<Self>,
17734 ) {
17735 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17736 let snapshot = self.snapshot(window, cx);
17737 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
17738 self.go_to_hunk_before_or_after_position(
17739 &snapshot,
17740 selection.head(),
17741 Direction::Prev,
17742 true,
17743 window,
17744 cx,
17745 );
17746 }
17747
17748 fn hunk_before_position(
17749 &mut self,
17750 snapshot: &EditorSnapshot,
17751 position: Point,
17752 wrap_around: bool,
17753 ) -> Option<MultiBufferRow> {
17754 let result = snapshot.buffer_snapshot().diff_hunk_before(position);
17755
17756 if wrap_around {
17757 result.or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
17758 } else {
17759 result
17760 }
17761 }
17762
17763 fn go_to_next_change(
17764 &mut self,
17765 _: &GoToNextChange,
17766 window: &mut Window,
17767 cx: &mut Context<Self>,
17768 ) {
17769 if let Some(selections) = self
17770 .change_list
17771 .next_change(1, Direction::Next)
17772 .map(|s| s.to_vec())
17773 {
17774 self.change_selections(Default::default(), window, cx, |s| {
17775 let map = s.display_snapshot();
17776 s.select_display_ranges(selections.iter().map(|a| {
17777 let point = a.to_display_point(&map);
17778 point..point
17779 }))
17780 })
17781 }
17782 }
17783
17784 fn go_to_previous_change(
17785 &mut self,
17786 _: &GoToPreviousChange,
17787 window: &mut Window,
17788 cx: &mut Context<Self>,
17789 ) {
17790 if let Some(selections) = self
17791 .change_list
17792 .next_change(1, Direction::Prev)
17793 .map(|s| s.to_vec())
17794 {
17795 self.change_selections(Default::default(), window, cx, |s| {
17796 let map = s.display_snapshot();
17797 s.select_display_ranges(selections.iter().map(|a| {
17798 let point = a.to_display_point(&map);
17799 point..point
17800 }))
17801 })
17802 }
17803 }
17804
17805 pub fn go_to_next_document_highlight(
17806 &mut self,
17807 _: &GoToNextDocumentHighlight,
17808 window: &mut Window,
17809 cx: &mut Context<Self>,
17810 ) {
17811 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
17812 }
17813
17814 pub fn go_to_prev_document_highlight(
17815 &mut self,
17816 _: &GoToPreviousDocumentHighlight,
17817 window: &mut Window,
17818 cx: &mut Context<Self>,
17819 ) {
17820 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
17821 }
17822
17823 pub fn go_to_document_highlight_before_or_after_position(
17824 &mut self,
17825 direction: Direction,
17826 window: &mut Window,
17827 cx: &mut Context<Editor>,
17828 ) {
17829 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17830 let snapshot = self.snapshot(window, cx);
17831 let buffer = &snapshot.buffer_snapshot();
17832 let position = self
17833 .selections
17834 .newest::<Point>(&snapshot.display_snapshot)
17835 .head();
17836 let anchor_position = buffer.anchor_after(position);
17837
17838 // Get all document highlights (both read and write)
17839 let mut all_highlights = Vec::new();
17840
17841 if let Some((_, read_highlights)) = self
17842 .background_highlights
17843 .get(&HighlightKey::DocumentHighlightRead)
17844 {
17845 all_highlights.extend(read_highlights.iter());
17846 }
17847
17848 if let Some((_, write_highlights)) = self
17849 .background_highlights
17850 .get(&HighlightKey::DocumentHighlightWrite)
17851 {
17852 all_highlights.extend(write_highlights.iter());
17853 }
17854
17855 if all_highlights.is_empty() {
17856 return;
17857 }
17858
17859 // Sort highlights by position
17860 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
17861
17862 let target_highlight = match direction {
17863 Direction::Next => {
17864 // Find the first highlight after the current position
17865 all_highlights
17866 .iter()
17867 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
17868 }
17869 Direction::Prev => {
17870 // Find the last highlight before the current position
17871 all_highlights
17872 .iter()
17873 .rev()
17874 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
17875 }
17876 };
17877
17878 if let Some(highlight) = target_highlight {
17879 let destination = highlight.start.to_point(buffer);
17880 let autoscroll = Autoscroll::center();
17881
17882 self.unfold_ranges(&[destination..destination], false, false, cx);
17883 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17884 s.select_ranges([destination..destination]);
17885 });
17886 }
17887 }
17888
17889 fn go_to_line<T: 'static>(
17890 &mut self,
17891 position: Anchor,
17892 highlight_color: Option<Hsla>,
17893 window: &mut Window,
17894 cx: &mut Context<Self>,
17895 ) {
17896 let snapshot = self.snapshot(window, cx).display_snapshot;
17897 let position = position.to_point(&snapshot.buffer_snapshot());
17898 let start = snapshot
17899 .buffer_snapshot()
17900 .clip_point(Point::new(position.row, 0), Bias::Left);
17901 let end = start + Point::new(1, 0);
17902 let start = snapshot.buffer_snapshot().anchor_before(start);
17903 let end = snapshot.buffer_snapshot().anchor_before(end);
17904
17905 self.highlight_rows::<T>(
17906 start..end,
17907 highlight_color
17908 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
17909 Default::default(),
17910 cx,
17911 );
17912
17913 if self.buffer.read(cx).is_singleton() {
17914 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
17915 }
17916 }
17917
17918 pub fn go_to_definition(
17919 &mut self,
17920 _: &GoToDefinition,
17921 window: &mut Window,
17922 cx: &mut Context<Self>,
17923 ) -> Task<Result<Navigated>> {
17924 let definition =
17925 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
17926 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
17927 cx.spawn_in(window, async move |editor, cx| {
17928 if definition.await? == Navigated::Yes {
17929 return Ok(Navigated::Yes);
17930 }
17931 match fallback_strategy {
17932 GoToDefinitionFallback::None => Ok(Navigated::No),
17933 GoToDefinitionFallback::FindAllReferences => {
17934 match editor.update_in(cx, |editor, window, cx| {
17935 editor.find_all_references(&FindAllReferences::default(), window, cx)
17936 })? {
17937 Some(references) => references.await,
17938 None => Ok(Navigated::No),
17939 }
17940 }
17941 }
17942 })
17943 }
17944
17945 pub fn go_to_declaration(
17946 &mut self,
17947 _: &GoToDeclaration,
17948 window: &mut Window,
17949 cx: &mut Context<Self>,
17950 ) -> Task<Result<Navigated>> {
17951 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
17952 }
17953
17954 pub fn go_to_declaration_split(
17955 &mut self,
17956 _: &GoToDeclaration,
17957 window: &mut Window,
17958 cx: &mut Context<Self>,
17959 ) -> Task<Result<Navigated>> {
17960 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
17961 }
17962
17963 pub fn go_to_implementation(
17964 &mut self,
17965 _: &GoToImplementation,
17966 window: &mut Window,
17967 cx: &mut Context<Self>,
17968 ) -> Task<Result<Navigated>> {
17969 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
17970 }
17971
17972 pub fn go_to_implementation_split(
17973 &mut self,
17974 _: &GoToImplementationSplit,
17975 window: &mut Window,
17976 cx: &mut Context<Self>,
17977 ) -> Task<Result<Navigated>> {
17978 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
17979 }
17980
17981 pub fn go_to_type_definition(
17982 &mut self,
17983 _: &GoToTypeDefinition,
17984 window: &mut Window,
17985 cx: &mut Context<Self>,
17986 ) -> Task<Result<Navigated>> {
17987 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
17988 }
17989
17990 pub fn go_to_definition_split(
17991 &mut self,
17992 _: &GoToDefinitionSplit,
17993 window: &mut Window,
17994 cx: &mut Context<Self>,
17995 ) -> Task<Result<Navigated>> {
17996 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
17997 }
17998
17999 pub fn go_to_type_definition_split(
18000 &mut self,
18001 _: &GoToTypeDefinitionSplit,
18002 window: &mut Window,
18003 cx: &mut Context<Self>,
18004 ) -> Task<Result<Navigated>> {
18005 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
18006 }
18007
18008 fn go_to_definition_of_kind(
18009 &mut self,
18010 kind: GotoDefinitionKind,
18011 split: bool,
18012 window: &mut Window,
18013 cx: &mut Context<Self>,
18014 ) -> Task<Result<Navigated>> {
18015 let Some(provider) = self.semantics_provider.clone() else {
18016 return Task::ready(Ok(Navigated::No));
18017 };
18018 let head = self
18019 .selections
18020 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
18021 .head();
18022 let buffer = self.buffer.read(cx);
18023 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
18024 return Task::ready(Ok(Navigated::No));
18025 };
18026 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
18027 return Task::ready(Ok(Navigated::No));
18028 };
18029
18030 let nav_entry = self.navigation_entry(self.selections.newest_anchor().head(), cx);
18031
18032 cx.spawn_in(window, async move |editor, cx| {
18033 let Some(definitions) = definitions.await? else {
18034 return Ok(Navigated::No);
18035 };
18036 let navigated = editor
18037 .update_in(cx, |editor, window, cx| {
18038 editor.navigate_to_hover_links(
18039 Some(kind),
18040 definitions
18041 .into_iter()
18042 .filter(|location| {
18043 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
18044 })
18045 .map(HoverLink::Text)
18046 .collect::<Vec<_>>(),
18047 nav_entry,
18048 split,
18049 window,
18050 cx,
18051 )
18052 })?
18053 .await?;
18054 anyhow::Ok(navigated)
18055 })
18056 }
18057
18058 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
18059 let selection = self.selections.newest_anchor();
18060 let head = selection.head();
18061 let tail = selection.tail();
18062
18063 let Some((buffer, start_position)) =
18064 self.buffer.read(cx).text_anchor_for_position(head, cx)
18065 else {
18066 return;
18067 };
18068
18069 let end_position = if head != tail {
18070 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
18071 return;
18072 };
18073 Some(pos)
18074 } else {
18075 None
18076 };
18077
18078 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
18079 let url = if let Some(end_pos) = end_position {
18080 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
18081 } else {
18082 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
18083 };
18084
18085 if let Some(url) = url {
18086 cx.update(|window, cx| {
18087 if parse_zed_link(&url, cx).is_some() {
18088 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
18089 } else {
18090 cx.open_url(&url);
18091 }
18092 })?;
18093 }
18094
18095 anyhow::Ok(())
18096 });
18097
18098 url_finder.detach();
18099 }
18100
18101 pub fn open_selected_filename(
18102 &mut self,
18103 _: &OpenSelectedFilename,
18104 window: &mut Window,
18105 cx: &mut Context<Self>,
18106 ) {
18107 let Some(workspace) = self.workspace() else {
18108 return;
18109 };
18110
18111 let position = self.selections.newest_anchor().head();
18112
18113 let Some((buffer, buffer_position)) =
18114 self.buffer.read(cx).text_anchor_for_position(position, cx)
18115 else {
18116 return;
18117 };
18118
18119 let project = self.project.clone();
18120
18121 cx.spawn_in(window, async move |_, cx| {
18122 let result = find_file(&buffer, project, buffer_position, cx).await;
18123
18124 if let Some((_, path)) = result {
18125 workspace
18126 .update_in(cx, |workspace, window, cx| {
18127 workspace.open_resolved_path(path, window, cx)
18128 })?
18129 .await?;
18130 }
18131 anyhow::Ok(())
18132 })
18133 .detach();
18134 }
18135
18136 pub(crate) fn navigate_to_hover_links(
18137 &mut self,
18138 kind: Option<GotoDefinitionKind>,
18139 definitions: Vec<HoverLink>,
18140 origin: Option<NavigationEntry>,
18141 split: bool,
18142 window: &mut Window,
18143 cx: &mut Context<Editor>,
18144 ) -> Task<Result<Navigated>> {
18145 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
18146 let mut first_url_or_file = None;
18147 let definitions: Vec<_> = definitions
18148 .into_iter()
18149 .filter_map(|def| match def {
18150 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
18151 HoverLink::InlayHint(lsp_location, server_id) => {
18152 let computation =
18153 self.compute_target_location(lsp_location, server_id, window, cx);
18154 Some(cx.background_spawn(computation))
18155 }
18156 HoverLink::Url(url) => {
18157 first_url_or_file = Some(Either::Left(url));
18158 None
18159 }
18160 HoverLink::File(path) => {
18161 first_url_or_file = Some(Either::Right(path));
18162 None
18163 }
18164 })
18165 .collect();
18166
18167 let workspace = self.workspace();
18168
18169 let excerpt_context_lines = multi_buffer::excerpt_context_lines(cx);
18170 cx.spawn_in(window, async move |editor, cx| {
18171 let locations: Vec<Location> = future::join_all(definitions)
18172 .await
18173 .into_iter()
18174 .filter_map(|location| location.transpose())
18175 .collect::<Result<_>>()
18176 .context("location tasks")?;
18177 let mut locations = cx.update(|_, cx| {
18178 locations
18179 .into_iter()
18180 .map(|location| {
18181 let buffer = location.buffer.read(cx);
18182 (location.buffer, location.range.to_point(buffer))
18183 })
18184 .into_group_map()
18185 })?;
18186 let mut num_locations = 0;
18187 for ranges in locations.values_mut() {
18188 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18189 ranges.dedup();
18190 let fits_in_one_excerpt = ranges
18191 .iter()
18192 .tuple_windows()
18193 .all(|(a, b)| b.start.row - a.end.row <= 2 * excerpt_context_lines);
18194 num_locations += if fits_in_one_excerpt { 1 } else { ranges.len() };
18195 }
18196
18197 if num_locations > 1 {
18198 let tab_kind = match kind {
18199 Some(GotoDefinitionKind::Implementation) => "Implementations",
18200 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
18201 Some(GotoDefinitionKind::Declaration) => "Declarations",
18202 Some(GotoDefinitionKind::Type) => "Types",
18203 };
18204 let title = editor
18205 .update_in(cx, |_, _, cx| {
18206 let target = locations
18207 .iter()
18208 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18209 .map(|(buffer, location)| {
18210 buffer
18211 .read(cx)
18212 .text_for_range(location.clone())
18213 .collect::<String>()
18214 })
18215 .filter(|text| !text.contains('\n'))
18216 .unique()
18217 .take(3)
18218 .join(", ");
18219 if target.is_empty() {
18220 tab_kind.to_owned()
18221 } else {
18222 format!("{tab_kind} for {target}")
18223 }
18224 })
18225 .context("buffer title")?;
18226
18227 let Some(workspace) = workspace else {
18228 return Ok(Navigated::No);
18229 };
18230
18231 let opened = workspace
18232 .update_in(cx, |workspace, window, cx| {
18233 let allow_preview = PreviewTabsSettings::get_global(cx)
18234 .enable_preview_multibuffer_from_code_navigation;
18235 if let Some((target_editor, target_pane)) =
18236 Self::open_locations_in_multibuffer(
18237 workspace,
18238 locations,
18239 title,
18240 split,
18241 allow_preview,
18242 MultibufferSelectionMode::First,
18243 window,
18244 cx,
18245 )
18246 {
18247 // We create our own nav history instead of using
18248 // `target_editor.nav_history` because `nav_history`
18249 // seems to be populated asynchronously when an item
18250 // is added to a pane
18251 let mut nav_history = target_pane
18252 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18253 target_editor.update(cx, |editor, cx| {
18254 let nav_data = editor
18255 .navigation_data(editor.selections.newest_anchor().head(), cx);
18256 let target =
18257 Some(nav_history.navigation_entry(Some(
18258 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18259 )));
18260 nav_history.push_tag(origin, target);
18261 })
18262 }
18263 })
18264 .is_ok();
18265
18266 anyhow::Ok(Navigated::from_bool(opened))
18267 } else if num_locations == 0 {
18268 // If there is one url or file, open it directly
18269 match first_url_or_file {
18270 Some(Either::Left(url)) => {
18271 cx.update(|window, cx| {
18272 if parse_zed_link(&url, cx).is_some() {
18273 window
18274 .dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
18275 } else {
18276 cx.open_url(&url);
18277 }
18278 })?;
18279 Ok(Navigated::Yes)
18280 }
18281 Some(Either::Right(path)) => {
18282 // TODO(andrew): respect preview tab settings
18283 // `enable_keep_preview_on_code_navigation` and
18284 // `enable_preview_file_from_code_navigation`
18285 let Some(workspace) = workspace else {
18286 return Ok(Navigated::No);
18287 };
18288 workspace
18289 .update_in(cx, |workspace, window, cx| {
18290 workspace.open_resolved_path(path, window, cx)
18291 })?
18292 .await?;
18293 Ok(Navigated::Yes)
18294 }
18295 None => Ok(Navigated::No),
18296 }
18297 } else {
18298 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18299
18300 editor.update_in(cx, |editor, window, cx| {
18301 let target_ranges = target_ranges
18302 .into_iter()
18303 .map(|r| editor.range_for_match(&r))
18304 .map(collapse_multiline_range)
18305 .collect::<Vec<_>>();
18306 if !split
18307 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
18308 {
18309 let multibuffer = editor.buffer.read(cx);
18310 let target_ranges = target_ranges
18311 .into_iter()
18312 .filter_map(|r| {
18313 let start = multibuffer.buffer_point_to_anchor(
18314 &target_buffer,
18315 r.start,
18316 cx,
18317 )?;
18318 let end = multibuffer.buffer_point_to_anchor(
18319 &target_buffer,
18320 r.end,
18321 cx,
18322 )?;
18323 Some(start..end)
18324 })
18325 .collect::<Vec<_>>();
18326 if target_ranges.is_empty() {
18327 return Navigated::No;
18328 }
18329
18330 editor.change_selections(
18331 SelectionEffects::default().nav_history(true),
18332 window,
18333 cx,
18334 |s| s.select_anchor_ranges(target_ranges),
18335 );
18336
18337 let target =
18338 editor.navigation_entry(editor.selections.newest_anchor().head(), cx);
18339 if let Some(mut nav_history) = editor.nav_history.clone() {
18340 nav_history.push_tag(origin, target);
18341 }
18342 } else {
18343 let Some(workspace) = workspace else {
18344 return Navigated::No;
18345 };
18346 let pane = workspace.read(cx).active_pane().clone();
18347 window.defer(cx, move |window, cx| {
18348 let (target_editor, target_pane): (Entity<Self>, Entity<Pane>) =
18349 workspace.update(cx, |workspace, cx| {
18350 let pane = if split {
18351 workspace.adjacent_pane(window, cx)
18352 } else {
18353 workspace.active_pane().clone()
18354 };
18355
18356 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18357 let keep_old_preview = preview_tabs_settings
18358 .enable_keep_preview_on_code_navigation;
18359 let allow_new_preview = preview_tabs_settings
18360 .enable_preview_file_from_code_navigation;
18361
18362 let editor = workspace.open_project_item(
18363 pane.clone(),
18364 target_buffer.clone(),
18365 true,
18366 true,
18367 keep_old_preview,
18368 allow_new_preview,
18369 window,
18370 cx,
18371 );
18372 (editor, pane)
18373 });
18374 // We create our own nav history instead of using
18375 // `target_editor.nav_history` because `nav_history`
18376 // seems to be populated asynchronously when an item
18377 // is added to a pane
18378 let mut nav_history = target_pane
18379 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18380 target_editor.update(cx, |target_editor, cx| {
18381 // When selecting a definition in a different buffer, disable the nav history
18382 // to avoid creating a history entry at the previous cursor location.
18383 pane.update(cx, |pane, _| pane.disable_history());
18384
18385 let multibuffer = target_editor.buffer.read(cx);
18386 let Some(target_buffer) = multibuffer.as_singleton() else {
18387 return Navigated::No;
18388 };
18389 let target_ranges = target_ranges
18390 .into_iter()
18391 .filter_map(|r| {
18392 let start = multibuffer.buffer_point_to_anchor(
18393 &target_buffer,
18394 r.start,
18395 cx,
18396 )?;
18397 let end = multibuffer.buffer_point_to_anchor(
18398 &target_buffer,
18399 r.end,
18400 cx,
18401 )?;
18402 Some(start..end)
18403 })
18404 .collect::<Vec<_>>();
18405 if target_ranges.is_empty() {
18406 return Navigated::No;
18407 }
18408
18409 target_editor.change_selections(
18410 SelectionEffects::default().nav_history(true),
18411 window,
18412 cx,
18413 |s| s.select_anchor_ranges(target_ranges),
18414 );
18415
18416 let nav_data = target_editor.navigation_data(
18417 target_editor.selections.newest_anchor().head(),
18418 cx,
18419 );
18420 let target =
18421 Some(nav_history.navigation_entry(Some(
18422 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18423 )));
18424 nav_history.push_tag(origin, target);
18425 pane.update(cx, |pane, _| pane.enable_history());
18426 Navigated::Yes
18427 });
18428 });
18429 }
18430 Navigated::Yes
18431 })
18432 }
18433 })
18434 }
18435
18436 fn compute_target_location(
18437 &self,
18438 lsp_location: lsp::Location,
18439 server_id: LanguageServerId,
18440 window: &mut Window,
18441 cx: &mut Context<Self>,
18442 ) -> Task<anyhow::Result<Option<Location>>> {
18443 let Some(project) = self.project.clone() else {
18444 return Task::ready(Ok(None));
18445 };
18446
18447 cx.spawn_in(window, async move |editor, cx| {
18448 let location_task = editor.update(cx, |_, cx| {
18449 project.update(cx, |project, cx| {
18450 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
18451 })
18452 })?;
18453 let location = Some({
18454 let target_buffer_handle = location_task.await.context("open local buffer")?;
18455 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
18456 let target_start = target_buffer
18457 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
18458 let target_end = target_buffer
18459 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
18460 target_buffer.anchor_after(target_start)
18461 ..target_buffer.anchor_before(target_end)
18462 });
18463 Location {
18464 buffer: target_buffer_handle,
18465 range,
18466 }
18467 });
18468 Ok(location)
18469 })
18470 }
18471
18472 fn go_to_next_reference(
18473 &mut self,
18474 _: &GoToNextReference,
18475 window: &mut Window,
18476 cx: &mut Context<Self>,
18477 ) {
18478 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
18479 if let Some(task) = task {
18480 task.detach();
18481 };
18482 }
18483
18484 fn go_to_prev_reference(
18485 &mut self,
18486 _: &GoToPreviousReference,
18487 window: &mut Window,
18488 cx: &mut Context<Self>,
18489 ) {
18490 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
18491 if let Some(task) = task {
18492 task.detach();
18493 };
18494 }
18495
18496 fn go_to_symbol_by_offset(
18497 &mut self,
18498 window: &mut Window,
18499 cx: &mut Context<Self>,
18500 offset: i8,
18501 ) -> Task<Result<()>> {
18502 let editor_snapshot = self.snapshot(window, cx);
18503
18504 // We don't care about multi-buffer symbols
18505 let Some((excerpt_id, _, _)) = editor_snapshot.as_singleton() else {
18506 return Task::ready(Ok(()));
18507 };
18508
18509 let cursor_offset = self
18510 .selections
18511 .newest::<MultiBufferOffset>(&editor_snapshot.display_snapshot)
18512 .head();
18513
18514 cx.spawn_in(window, async move |editor, wcx| -> Result<()> {
18515 let Ok(Some(remote_id)) = editor.update(wcx, |ed, cx| {
18516 let buffer = ed.buffer.read(cx).as_singleton()?;
18517 Some(buffer.read(cx).remote_id())
18518 }) else {
18519 return Ok(());
18520 };
18521
18522 let task = editor.update(wcx, |ed, cx| ed.buffer_outline_items(remote_id, cx))?;
18523 let outline_items: Vec<OutlineItem<text::Anchor>> = task.await;
18524
18525 let multi_snapshot = editor_snapshot.buffer();
18526 let buffer_range = |range: &Range<_>| {
18527 Anchor::range_in_buffer(excerpt_id, range.clone()).to_offset(multi_snapshot)
18528 };
18529
18530 wcx.update_window(wcx.window_handle(), |_, window, acx| {
18531 let current_idx = outline_items
18532 .iter()
18533 .enumerate()
18534 .filter_map(|(idx, item)| {
18535 // Find the closest outline item by distance between outline text and cursor location
18536 let source_range = buffer_range(&item.source_range_for_text);
18537 let distance_to_closest_endpoint = cmp::min(
18538 (source_range.start.0 as isize - cursor_offset.0 as isize).abs(),
18539 (source_range.end.0 as isize - cursor_offset.0 as isize).abs(),
18540 );
18541
18542 let item_towards_offset =
18543 (source_range.start.0 as isize - cursor_offset.0 as isize).signum()
18544 == (offset as isize).signum();
18545
18546 let source_range_contains_cursor = source_range.contains(&cursor_offset);
18547
18548 // To pick the next outline to jump to, we should jump in the direction of the offset, and
18549 // we should not already be within the outline's source range. We then pick the closest outline
18550 // item.
18551 (item_towards_offset && !source_range_contains_cursor)
18552 .then_some((distance_to_closest_endpoint, idx))
18553 })
18554 .min()
18555 .map(|(_, idx)| idx);
18556
18557 let Some(idx) = current_idx else {
18558 return;
18559 };
18560
18561 let range = buffer_range(&outline_items[idx].source_range_for_text);
18562 let selection = [range.start..range.start];
18563
18564 let _ = editor
18565 .update(acx, |editor, ecx| {
18566 editor.change_selections(
18567 SelectionEffects::scroll(Autoscroll::newest()),
18568 window,
18569 ecx,
18570 |s| s.select_ranges(selection),
18571 );
18572 })
18573 .ok();
18574 })?;
18575
18576 Ok(())
18577 })
18578 }
18579
18580 fn go_to_next_symbol(
18581 &mut self,
18582 _: &GoToNextSymbol,
18583 window: &mut Window,
18584 cx: &mut Context<Self>,
18585 ) {
18586 self.go_to_symbol_by_offset(window, cx, 1).detach();
18587 }
18588
18589 fn go_to_previous_symbol(
18590 &mut self,
18591 _: &GoToPreviousSymbol,
18592 window: &mut Window,
18593 cx: &mut Context<Self>,
18594 ) {
18595 self.go_to_symbol_by_offset(window, cx, -1).detach();
18596 }
18597
18598 pub fn go_to_reference_before_or_after_position(
18599 &mut self,
18600 direction: Direction,
18601 count: usize,
18602 window: &mut Window,
18603 cx: &mut Context<Self>,
18604 ) -> Option<Task<Result<()>>> {
18605 let selection = self.selections.newest_anchor();
18606 let head = selection.head();
18607
18608 let multi_buffer = self.buffer.read(cx);
18609
18610 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
18611 let workspace = self.workspace()?;
18612 let project = workspace.read(cx).project().clone();
18613 let references =
18614 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
18615 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
18616 let Some(locations) = references.await? else {
18617 return Ok(());
18618 };
18619
18620 if locations.is_empty() {
18621 // totally normal - the cursor may be on something which is not
18622 // a symbol (e.g. a keyword)
18623 log::info!("no references found under cursor");
18624 return Ok(());
18625 }
18626
18627 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
18628
18629 let (locations, current_location_index) =
18630 multi_buffer.update(cx, |multi_buffer, cx| {
18631 let mut locations = locations
18632 .into_iter()
18633 .filter_map(|loc| {
18634 let start = multi_buffer.buffer_anchor_to_anchor(
18635 &loc.buffer,
18636 loc.range.start,
18637 cx,
18638 )?;
18639 let end = multi_buffer.buffer_anchor_to_anchor(
18640 &loc.buffer,
18641 loc.range.end,
18642 cx,
18643 )?;
18644 Some(start..end)
18645 })
18646 .collect::<Vec<_>>();
18647
18648 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18649 // There is an O(n) implementation, but given this list will be
18650 // small (usually <100 items), the extra O(log(n)) factor isn't
18651 // worth the (surprisingly large amount of) extra complexity.
18652 locations
18653 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
18654
18655 let head_offset = head.to_offset(&multi_buffer_snapshot);
18656
18657 let current_location_index = locations.iter().position(|loc| {
18658 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
18659 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
18660 });
18661
18662 (locations, current_location_index)
18663 });
18664
18665 let Some(current_location_index) = current_location_index else {
18666 // This indicates something has gone wrong, because we already
18667 // handle the "no references" case above
18668 log::error!(
18669 "failed to find current reference under cursor. Total references: {}",
18670 locations.len()
18671 );
18672 return Ok(());
18673 };
18674
18675 let destination_location_index = match direction {
18676 Direction::Next => (current_location_index + count) % locations.len(),
18677 Direction::Prev => {
18678 (current_location_index + locations.len() - count % locations.len())
18679 % locations.len()
18680 }
18681 };
18682
18683 // TODO(cameron): is this needed?
18684 // the thinking is to avoid "jumping to the current location" (avoid
18685 // polluting "jumplist" in vim terms)
18686 if current_location_index == destination_location_index {
18687 return Ok(());
18688 }
18689
18690 let Range { start, end } = locations[destination_location_index];
18691
18692 editor.update_in(cx, |editor, window, cx| {
18693 let effects = SelectionEffects::default();
18694
18695 editor.unfold_ranges(&[start..end], false, false, cx);
18696 editor.change_selections(effects, window, cx, |s| {
18697 s.select_ranges([start..start]);
18698 });
18699 })?;
18700
18701 Ok(())
18702 }))
18703 }
18704
18705 pub fn find_all_references(
18706 &mut self,
18707 action: &FindAllReferences,
18708 window: &mut Window,
18709 cx: &mut Context<Self>,
18710 ) -> Option<Task<Result<Navigated>>> {
18711 let always_open_multibuffer = action.always_open_multibuffer;
18712 let selection = self.selections.newest_anchor();
18713 let multi_buffer = self.buffer.read(cx);
18714 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18715 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
18716 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
18717 let head = selection_offset.head();
18718
18719 let head_anchor = multi_buffer_snapshot.anchor_at(
18720 head,
18721 if head < selection_offset.tail() {
18722 Bias::Right
18723 } else {
18724 Bias::Left
18725 },
18726 );
18727
18728 match self
18729 .find_all_references_task_sources
18730 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18731 {
18732 Ok(_) => {
18733 log::info!(
18734 "Ignoring repeated FindAllReferences invocation with the position of already running task"
18735 );
18736 return None;
18737 }
18738 Err(i) => {
18739 self.find_all_references_task_sources.insert(i, head_anchor);
18740 }
18741 }
18742
18743 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
18744 let workspace = self.workspace()?;
18745 let project = workspace.read(cx).project().clone();
18746 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
18747 Some(cx.spawn_in(window, async move |editor, cx| {
18748 let _cleanup = cx.on_drop(&editor, move |editor, _| {
18749 if let Ok(i) = editor
18750 .find_all_references_task_sources
18751 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18752 {
18753 editor.find_all_references_task_sources.remove(i);
18754 }
18755 });
18756
18757 let Some(locations) = references.await? else {
18758 return anyhow::Ok(Navigated::No);
18759 };
18760 let mut locations = cx.update(|_, cx| {
18761 locations
18762 .into_iter()
18763 .map(|location| {
18764 let buffer = location.buffer.read(cx);
18765 (location.buffer, location.range.to_point(buffer))
18766 })
18767 // if special-casing the single-match case, remove ranges
18768 // that intersect current selection
18769 .filter(|(location_buffer, location)| {
18770 if always_open_multibuffer || &buffer != location_buffer {
18771 return true;
18772 }
18773
18774 !location.contains_inclusive(&selection_point.range())
18775 })
18776 .into_group_map()
18777 })?;
18778 if locations.is_empty() {
18779 return anyhow::Ok(Navigated::No);
18780 }
18781 for ranges in locations.values_mut() {
18782 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18783 ranges.dedup();
18784 }
18785 let mut num_locations = 0;
18786 for ranges in locations.values_mut() {
18787 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18788 ranges.dedup();
18789 num_locations += ranges.len();
18790 }
18791
18792 if num_locations == 1 && !always_open_multibuffer {
18793 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18794 let target_range = target_ranges.first().unwrap().clone();
18795
18796 return editor.update_in(cx, |editor, window, cx| {
18797 let range = target_range.to_point(target_buffer.read(cx));
18798 let range = editor.range_for_match(&range);
18799 let range = range.start..range.start;
18800
18801 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
18802 editor.go_to_singleton_buffer_range(range, window, cx);
18803 } else {
18804 let pane = workspace.read(cx).active_pane().clone();
18805 window.defer(cx, move |window, cx| {
18806 let target_editor: Entity<Self> =
18807 workspace.update(cx, |workspace, cx| {
18808 let pane = workspace.active_pane().clone();
18809
18810 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18811 let keep_old_preview = preview_tabs_settings
18812 .enable_keep_preview_on_code_navigation;
18813 let allow_new_preview = preview_tabs_settings
18814 .enable_preview_file_from_code_navigation;
18815
18816 workspace.open_project_item(
18817 pane,
18818 target_buffer.clone(),
18819 true,
18820 true,
18821 keep_old_preview,
18822 allow_new_preview,
18823 window,
18824 cx,
18825 )
18826 });
18827 target_editor.update(cx, |target_editor, cx| {
18828 // When selecting a definition in a different buffer, disable the nav history
18829 // to avoid creating a history entry at the previous cursor location.
18830 pane.update(cx, |pane, _| pane.disable_history());
18831 target_editor.go_to_singleton_buffer_range(range, window, cx);
18832 pane.update(cx, |pane, _| pane.enable_history());
18833 });
18834 });
18835 }
18836 Navigated::No
18837 });
18838 }
18839
18840 workspace.update_in(cx, |workspace, window, cx| {
18841 let target = locations
18842 .iter()
18843 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18844 .map(|(buffer, location)| {
18845 buffer
18846 .read(cx)
18847 .text_for_range(location.clone())
18848 .collect::<String>()
18849 })
18850 .filter(|text| !text.contains('\n'))
18851 .unique()
18852 .take(3)
18853 .join(", ");
18854 let title = if target.is_empty() {
18855 "References".to_owned()
18856 } else {
18857 format!("References to {target}")
18858 };
18859 let allow_preview = PreviewTabsSettings::get_global(cx)
18860 .enable_preview_multibuffer_from_code_navigation;
18861 Self::open_locations_in_multibuffer(
18862 workspace,
18863 locations,
18864 title,
18865 false,
18866 allow_preview,
18867 MultibufferSelectionMode::First,
18868 window,
18869 cx,
18870 );
18871 Navigated::Yes
18872 })
18873 }))
18874 }
18875
18876 /// Opens a multibuffer with the given project locations in it.
18877 pub fn open_locations_in_multibuffer(
18878 workspace: &mut Workspace,
18879 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
18880 title: String,
18881 split: bool,
18882 allow_preview: bool,
18883 multibuffer_selection_mode: MultibufferSelectionMode,
18884 window: &mut Window,
18885 cx: &mut Context<Workspace>,
18886 ) -> Option<(Entity<Editor>, Entity<Pane>)> {
18887 if locations.is_empty() {
18888 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
18889 return None;
18890 }
18891
18892 let capability = workspace.project().read(cx).capability();
18893 let mut ranges = <Vec<Range<Anchor>>>::new();
18894
18895 // a key to find existing multibuffer editors with the same set of locations
18896 // to prevent us from opening more and more multibuffer tabs for searches and the like
18897 let mut key = (title.clone(), vec![]);
18898 let excerpt_buffer = cx.new(|cx| {
18899 let key = &mut key.1;
18900 let mut multibuffer = MultiBuffer::new(capability);
18901 for (buffer, mut ranges_for_buffer) in locations {
18902 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
18903 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
18904 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
18905 PathKey::for_buffer(&buffer, cx),
18906 buffer.clone(),
18907 ranges_for_buffer,
18908 multibuffer_context_lines(cx),
18909 cx,
18910 );
18911 ranges.extend(new_ranges)
18912 }
18913
18914 multibuffer.with_title(title)
18915 });
18916 let existing = workspace.active_pane().update(cx, |pane, cx| {
18917 pane.items()
18918 .filter_map(|item| item.downcast::<Editor>())
18919 .find(|editor| {
18920 editor
18921 .read(cx)
18922 .lookup_key
18923 .as_ref()
18924 .and_then(|it| {
18925 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
18926 })
18927 .is_some_and(|it| *it == key)
18928 })
18929 });
18930 let was_existing = existing.is_some();
18931 let editor = existing.unwrap_or_else(|| {
18932 cx.new(|cx| {
18933 let mut editor = Editor::for_multibuffer(
18934 excerpt_buffer,
18935 Some(workspace.project().clone()),
18936 window,
18937 cx,
18938 );
18939 editor.lookup_key = Some(Box::new(key));
18940 editor
18941 })
18942 });
18943 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
18944 MultibufferSelectionMode::First => {
18945 if let Some(first_range) = ranges.first() {
18946 editor.change_selections(
18947 SelectionEffects::no_scroll(),
18948 window,
18949 cx,
18950 |selections| {
18951 selections.clear_disjoint();
18952 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
18953 },
18954 );
18955 }
18956 editor.highlight_background(
18957 HighlightKey::Editor,
18958 &ranges,
18959 |_, theme| theme.colors().editor_highlighted_line_background,
18960 cx,
18961 );
18962 }
18963 MultibufferSelectionMode::All => {
18964 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
18965 selections.clear_disjoint();
18966 selections.select_anchor_ranges(ranges);
18967 });
18968 }
18969 });
18970
18971 let item = Box::new(editor.clone());
18972
18973 let pane = if split {
18974 workspace.adjacent_pane(window, cx)
18975 } else {
18976 workspace.active_pane().clone()
18977 };
18978 let activate_pane = split;
18979
18980 let mut destination_index = None;
18981 pane.update(cx, |pane, cx| {
18982 if allow_preview && !was_existing {
18983 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
18984 }
18985 if was_existing && !allow_preview {
18986 pane.unpreview_item_if_preview(item.item_id());
18987 }
18988 pane.add_item(item, activate_pane, true, destination_index, window, cx);
18989 });
18990
18991 Some((editor, pane))
18992 }
18993
18994 pub fn rename(
18995 &mut self,
18996 _: &Rename,
18997 window: &mut Window,
18998 cx: &mut Context<Self>,
18999 ) -> Option<Task<Result<()>>> {
19000 use language::ToOffset as _;
19001
19002 let provider = self.semantics_provider.clone()?;
19003 let selection = self.selections.newest_anchor().clone();
19004 let (cursor_buffer, cursor_buffer_position) = self
19005 .buffer
19006 .read(cx)
19007 .text_anchor_for_position(selection.head(), cx)?;
19008 let (tail_buffer, cursor_buffer_position_end) = self
19009 .buffer
19010 .read(cx)
19011 .text_anchor_for_position(selection.tail(), cx)?;
19012 if tail_buffer != cursor_buffer {
19013 return None;
19014 }
19015
19016 let snapshot = cursor_buffer.read(cx).snapshot();
19017 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
19018 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
19019 let prepare_rename = provider
19020 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
19021 .unwrap_or_else(|| Task::ready(Ok(None)));
19022 drop(snapshot);
19023
19024 Some(cx.spawn_in(window, async move |this, cx| {
19025 let rename_range = if let Some(range) = prepare_rename.await? {
19026 Some(range)
19027 } else {
19028 this.update(cx, |this, cx| {
19029 let buffer = this.buffer.read(cx).snapshot(cx);
19030 let mut buffer_highlights = this
19031 .document_highlights_for_position(selection.head(), &buffer)
19032 .filter(|highlight| {
19033 highlight.start.excerpt_id == selection.head().excerpt_id
19034 && highlight.end.excerpt_id == selection.head().excerpt_id
19035 });
19036 buffer_highlights
19037 .next()
19038 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
19039 })?
19040 };
19041 if let Some(rename_range) = rename_range {
19042 this.update_in(cx, |this, window, cx| {
19043 let snapshot = cursor_buffer.read(cx).snapshot();
19044 let rename_buffer_range = rename_range.to_offset(&snapshot);
19045 let cursor_offset_in_rename_range =
19046 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
19047 let cursor_offset_in_rename_range_end =
19048 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
19049
19050 this.take_rename(false, window, cx);
19051 let buffer = this.buffer.read(cx).read(cx);
19052 let cursor_offset = selection.head().to_offset(&buffer);
19053 let rename_start =
19054 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
19055 let rename_end = rename_start + rename_buffer_range.len();
19056 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
19057 let mut old_highlight_id = None;
19058 let old_name: Arc<str> = buffer
19059 .chunks(rename_start..rename_end, true)
19060 .map(|chunk| {
19061 if old_highlight_id.is_none() {
19062 old_highlight_id = chunk.syntax_highlight_id;
19063 }
19064 chunk.text
19065 })
19066 .collect::<String>()
19067 .into();
19068
19069 drop(buffer);
19070
19071 // Position the selection in the rename editor so that it matches the current selection.
19072 this.show_local_selections = false;
19073 let rename_editor = cx.new(|cx| {
19074 let mut editor = Editor::single_line(window, cx);
19075 editor.buffer.update(cx, |buffer, cx| {
19076 buffer.edit(
19077 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
19078 None,
19079 cx,
19080 )
19081 });
19082 let cursor_offset_in_rename_range =
19083 MultiBufferOffset(cursor_offset_in_rename_range);
19084 let cursor_offset_in_rename_range_end =
19085 MultiBufferOffset(cursor_offset_in_rename_range_end);
19086 let rename_selection_range = match cursor_offset_in_rename_range
19087 .cmp(&cursor_offset_in_rename_range_end)
19088 {
19089 Ordering::Equal => {
19090 editor.select_all(&SelectAll, window, cx);
19091 return editor;
19092 }
19093 Ordering::Less => {
19094 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
19095 }
19096 Ordering::Greater => {
19097 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
19098 }
19099 };
19100 if rename_selection_range.end.0 > old_name.len() {
19101 editor.select_all(&SelectAll, window, cx);
19102 } else {
19103 editor.change_selections(Default::default(), window, cx, |s| {
19104 s.select_ranges([rename_selection_range]);
19105 });
19106 }
19107 editor
19108 });
19109 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
19110 if e == &EditorEvent::Focused {
19111 cx.emit(EditorEvent::FocusedIn)
19112 }
19113 })
19114 .detach();
19115
19116 let write_highlights =
19117 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
19118 let read_highlights =
19119 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
19120 let ranges = write_highlights
19121 .iter()
19122 .flat_map(|(_, ranges)| ranges.iter())
19123 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
19124 .cloned()
19125 .collect();
19126
19127 this.highlight_text(
19128 HighlightKey::Rename,
19129 ranges,
19130 HighlightStyle {
19131 fade_out: Some(0.6),
19132 ..Default::default()
19133 },
19134 cx,
19135 );
19136 let rename_focus_handle = rename_editor.focus_handle(cx);
19137 window.focus(&rename_focus_handle, cx);
19138 let block_id = this.insert_blocks(
19139 [BlockProperties {
19140 style: BlockStyle::Flex,
19141 placement: BlockPlacement::Below(range.start),
19142 height: Some(1),
19143 render: Arc::new({
19144 let rename_editor = rename_editor.clone();
19145 move |cx: &mut BlockContext| {
19146 let mut text_style = cx.editor_style.text.clone();
19147 if let Some(highlight_style) = old_highlight_id
19148 .and_then(|h| h.style(&cx.editor_style.syntax))
19149 {
19150 text_style = text_style.highlight(highlight_style);
19151 }
19152 div()
19153 .block_mouse_except_scroll()
19154 .pl(cx.anchor_x)
19155 .child(EditorElement::new(
19156 &rename_editor,
19157 EditorStyle {
19158 background: cx.theme().system().transparent,
19159 local_player: cx.editor_style.local_player,
19160 text: text_style,
19161 scrollbar_width: cx.editor_style.scrollbar_width,
19162 syntax: cx.editor_style.syntax.clone(),
19163 status: cx.editor_style.status.clone(),
19164 inlay_hints_style: HighlightStyle {
19165 font_weight: Some(FontWeight::BOLD),
19166 ..make_inlay_hints_style(cx.app)
19167 },
19168 edit_prediction_styles: make_suggestion_styles(
19169 cx.app,
19170 ),
19171 ..EditorStyle::default()
19172 },
19173 ))
19174 .into_any_element()
19175 }
19176 }),
19177 priority: 0,
19178 }],
19179 Some(Autoscroll::fit()),
19180 cx,
19181 )[0];
19182 this.pending_rename = Some(RenameState {
19183 range,
19184 old_name,
19185 editor: rename_editor,
19186 block_id,
19187 });
19188 })?;
19189 }
19190
19191 Ok(())
19192 }))
19193 }
19194
19195 pub fn confirm_rename(
19196 &mut self,
19197 _: &ConfirmRename,
19198 window: &mut Window,
19199 cx: &mut Context<Self>,
19200 ) -> Option<Task<Result<()>>> {
19201 let rename = self.take_rename(false, window, cx)?;
19202 let workspace = self.workspace()?.downgrade();
19203 let (buffer, start) = self
19204 .buffer
19205 .read(cx)
19206 .text_anchor_for_position(rename.range.start, cx)?;
19207 let (end_buffer, _) = self
19208 .buffer
19209 .read(cx)
19210 .text_anchor_for_position(rename.range.end, cx)?;
19211 if buffer != end_buffer {
19212 return None;
19213 }
19214
19215 let old_name = rename.old_name;
19216 let new_name = rename.editor.read(cx).text(cx);
19217
19218 let rename = self.semantics_provider.as_ref()?.perform_rename(
19219 &buffer,
19220 start,
19221 new_name.clone(),
19222 cx,
19223 )?;
19224
19225 Some(cx.spawn_in(window, async move |editor, cx| {
19226 let project_transaction = rename.await?;
19227 Self::open_project_transaction(
19228 &editor,
19229 workspace,
19230 project_transaction,
19231 format!("Rename: {} → {}", old_name, new_name),
19232 cx,
19233 )
19234 .await?;
19235
19236 editor.update(cx, |editor, cx| {
19237 editor.refresh_document_highlights(cx);
19238 })?;
19239 Ok(())
19240 }))
19241 }
19242
19243 fn take_rename(
19244 &mut self,
19245 moving_cursor: bool,
19246 window: &mut Window,
19247 cx: &mut Context<Self>,
19248 ) -> Option<RenameState> {
19249 let rename = self.pending_rename.take()?;
19250 if rename.editor.focus_handle(cx).is_focused(window) {
19251 window.focus(&self.focus_handle, cx);
19252 }
19253
19254 self.remove_blocks(
19255 [rename.block_id].into_iter().collect(),
19256 Some(Autoscroll::fit()),
19257 cx,
19258 );
19259 self.clear_highlights(HighlightKey::Rename, cx);
19260 self.show_local_selections = true;
19261
19262 if moving_cursor {
19263 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
19264 editor
19265 .selections
19266 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
19267 .head()
19268 });
19269
19270 // Update the selection to match the position of the selection inside
19271 // the rename editor.
19272 let snapshot = self.buffer.read(cx).read(cx);
19273 let rename_range = rename.range.to_offset(&snapshot);
19274 let cursor_in_editor = snapshot
19275 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
19276 .min(rename_range.end);
19277 drop(snapshot);
19278
19279 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19280 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
19281 });
19282 } else {
19283 self.refresh_document_highlights(cx);
19284 }
19285
19286 Some(rename)
19287 }
19288
19289 pub fn pending_rename(&self) -> Option<&RenameState> {
19290 self.pending_rename.as_ref()
19291 }
19292
19293 fn format(
19294 &mut self,
19295 _: &Format,
19296 window: &mut Window,
19297 cx: &mut Context<Self>,
19298 ) -> Option<Task<Result<()>>> {
19299 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19300
19301 let project = match &self.project {
19302 Some(project) => project.clone(),
19303 None => return None,
19304 };
19305
19306 Some(self.perform_format(
19307 project,
19308 FormatTrigger::Manual,
19309 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
19310 window,
19311 cx,
19312 ))
19313 }
19314
19315 fn format_selections(
19316 &mut self,
19317 _: &FormatSelections,
19318 window: &mut Window,
19319 cx: &mut Context<Self>,
19320 ) -> Option<Task<Result<()>>> {
19321 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19322
19323 let project = match &self.project {
19324 Some(project) => project.clone(),
19325 None => return None,
19326 };
19327
19328 let ranges = self
19329 .selections
19330 .all_adjusted(&self.display_snapshot(cx))
19331 .into_iter()
19332 .map(|selection| selection.range())
19333 .collect_vec();
19334
19335 Some(self.perform_format(
19336 project,
19337 FormatTrigger::Manual,
19338 FormatTarget::Ranges(ranges),
19339 window,
19340 cx,
19341 ))
19342 }
19343
19344 fn perform_format(
19345 &mut self,
19346 project: Entity<Project>,
19347 trigger: FormatTrigger,
19348 target: FormatTarget,
19349 window: &mut Window,
19350 cx: &mut Context<Self>,
19351 ) -> Task<Result<()>> {
19352 let buffer = self.buffer.clone();
19353 let (buffers, target) = match target {
19354 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
19355 FormatTarget::Ranges(selection_ranges) => {
19356 let multi_buffer = buffer.read(cx);
19357 let snapshot = multi_buffer.read(cx);
19358 let mut buffers = HashSet::default();
19359 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
19360 BTreeMap::new();
19361 for selection_range in selection_ranges {
19362 for (buffer, buffer_range, _) in
19363 snapshot.range_to_buffer_ranges(selection_range.start..=selection_range.end)
19364 {
19365 let buffer_id = buffer.remote_id();
19366 let start = buffer.anchor_before(buffer_range.start);
19367 let end = buffer.anchor_after(buffer_range.end);
19368 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
19369 buffer_id_to_ranges
19370 .entry(buffer_id)
19371 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
19372 .or_insert_with(|| vec![start..end]);
19373 }
19374 }
19375 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
19376 }
19377 };
19378
19379 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
19380 let selections_prev = transaction_id_prev
19381 .and_then(|transaction_id_prev| {
19382 // default to selections as they were after the last edit, if we have them,
19383 // instead of how they are now.
19384 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
19385 // will take you back to where you made the last edit, instead of staying where you scrolled
19386 self.selection_history
19387 .transaction(transaction_id_prev)
19388 .map(|t| t.0.clone())
19389 })
19390 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
19391
19392 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
19393 let format = project.update(cx, |project, cx| {
19394 project.format(buffers, target, true, trigger, cx)
19395 });
19396
19397 cx.spawn_in(window, async move |editor, cx| {
19398 let transaction = futures::select_biased! {
19399 transaction = format.log_err().fuse() => transaction,
19400 () = timeout => {
19401 log::warn!("timed out waiting for formatting");
19402 None
19403 }
19404 };
19405
19406 buffer.update(cx, |buffer, cx| {
19407 if let Some(transaction) = transaction
19408 && !buffer.is_singleton()
19409 {
19410 buffer.push_transaction(&transaction.0, cx);
19411 }
19412 cx.notify();
19413 });
19414
19415 if let Some(transaction_id_now) =
19416 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))
19417 {
19418 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
19419 if has_new_transaction {
19420 editor
19421 .update(cx, |editor, _| {
19422 editor
19423 .selection_history
19424 .insert_transaction(transaction_id_now, selections_prev);
19425 })
19426 .ok();
19427 }
19428 }
19429
19430 Ok(())
19431 })
19432 }
19433
19434 fn organize_imports(
19435 &mut self,
19436 _: &OrganizeImports,
19437 window: &mut Window,
19438 cx: &mut Context<Self>,
19439 ) -> Option<Task<Result<()>>> {
19440 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19441 let project = match &self.project {
19442 Some(project) => project.clone(),
19443 None => return None,
19444 };
19445 Some(self.perform_code_action_kind(
19446 project,
19447 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
19448 window,
19449 cx,
19450 ))
19451 }
19452
19453 fn perform_code_action_kind(
19454 &mut self,
19455 project: Entity<Project>,
19456 kind: CodeActionKind,
19457 window: &mut Window,
19458 cx: &mut Context<Self>,
19459 ) -> Task<Result<()>> {
19460 let buffer = self.buffer.clone();
19461 let buffers = buffer.read(cx).all_buffers();
19462 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
19463 let apply_action = project.update(cx, |project, cx| {
19464 project.apply_code_action_kind(buffers, kind, true, cx)
19465 });
19466 cx.spawn_in(window, async move |_, cx| {
19467 let transaction = futures::select_biased! {
19468 () = timeout => {
19469 log::warn!("timed out waiting for executing code action");
19470 None
19471 }
19472 transaction = apply_action.log_err().fuse() => transaction,
19473 };
19474 buffer.update(cx, |buffer, cx| {
19475 // check if we need this
19476 if let Some(transaction) = transaction
19477 && !buffer.is_singleton()
19478 {
19479 buffer.push_transaction(&transaction.0, cx);
19480 }
19481 cx.notify();
19482 });
19483 Ok(())
19484 })
19485 }
19486
19487 pub fn restart_language_server(
19488 &mut self,
19489 _: &RestartLanguageServer,
19490 _: &mut Window,
19491 cx: &mut Context<Self>,
19492 ) {
19493 if let Some(project) = self.project.clone() {
19494 self.buffer.update(cx, |multi_buffer, cx| {
19495 project.update(cx, |project, cx| {
19496 project.restart_language_servers_for_buffers(
19497 multi_buffer.all_buffers().into_iter().collect(),
19498 HashSet::default(),
19499 cx,
19500 );
19501 });
19502 })
19503 }
19504 }
19505
19506 pub fn stop_language_server(
19507 &mut self,
19508 _: &StopLanguageServer,
19509 _: &mut Window,
19510 cx: &mut Context<Self>,
19511 ) {
19512 if let Some(project) = self.project.clone() {
19513 self.buffer.update(cx, |multi_buffer, cx| {
19514 project.update(cx, |project, cx| {
19515 project.stop_language_servers_for_buffers(
19516 multi_buffer.all_buffers().into_iter().collect(),
19517 HashSet::default(),
19518 cx,
19519 );
19520 });
19521 });
19522 }
19523 }
19524
19525 fn cancel_language_server_work(
19526 workspace: &mut Workspace,
19527 _: &actions::CancelLanguageServerWork,
19528 _: &mut Window,
19529 cx: &mut Context<Workspace>,
19530 ) {
19531 let project = workspace.project();
19532 let buffers = workspace
19533 .active_item(cx)
19534 .and_then(|item| item.act_as::<Editor>(cx))
19535 .map_or(HashSet::default(), |editor| {
19536 editor.read(cx).buffer.read(cx).all_buffers()
19537 });
19538 project.update(cx, |project, cx| {
19539 project.cancel_language_server_work_for_buffers(buffers, cx);
19540 });
19541 }
19542
19543 fn show_character_palette(
19544 &mut self,
19545 _: &ShowCharacterPalette,
19546 window: &mut Window,
19547 _: &mut Context<Self>,
19548 ) {
19549 window.show_character_palette();
19550 }
19551
19552 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
19553 if !self.diagnostics_enabled() {
19554 return;
19555 }
19556
19557 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
19558 let buffer = self.buffer.read(cx).snapshot(cx);
19559 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
19560 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
19561 let is_valid = buffer
19562 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
19563 .any(|entry| {
19564 entry.diagnostic.is_primary
19565 && !entry.range.is_empty()
19566 && entry.range.start == primary_range_start
19567 && entry.diagnostic.message == active_diagnostics.active_message
19568 });
19569
19570 if !is_valid {
19571 self.dismiss_diagnostics(cx);
19572 }
19573 }
19574 }
19575
19576 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
19577 match &self.active_diagnostics {
19578 ActiveDiagnostic::Group(group) => Some(group),
19579 _ => None,
19580 }
19581 }
19582
19583 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
19584 if !self.diagnostics_enabled() {
19585 return;
19586 }
19587 self.dismiss_diagnostics(cx);
19588 self.active_diagnostics = ActiveDiagnostic::All;
19589 }
19590
19591 fn activate_diagnostics(
19592 &mut self,
19593 buffer_id: BufferId,
19594 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
19595 window: &mut Window,
19596 cx: &mut Context<Self>,
19597 ) {
19598 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19599 return;
19600 }
19601 self.dismiss_diagnostics(cx);
19602 let snapshot = self.snapshot(window, cx);
19603 let buffer = self.buffer.read(cx).snapshot(cx);
19604 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
19605 return;
19606 };
19607
19608 let diagnostic_group = buffer
19609 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
19610 .collect::<Vec<_>>();
19611
19612 let language_registry = self
19613 .project()
19614 .map(|project| project.read(cx).languages().clone());
19615
19616 let blocks = renderer.render_group(
19617 diagnostic_group,
19618 buffer_id,
19619 snapshot,
19620 cx.weak_entity(),
19621 language_registry,
19622 cx,
19623 );
19624
19625 let blocks = self.display_map.update(cx, |display_map, cx| {
19626 display_map.insert_blocks(blocks, cx).into_iter().collect()
19627 });
19628 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
19629 active_range: buffer.anchor_before(diagnostic.range.start)
19630 ..buffer.anchor_after(diagnostic.range.end),
19631 active_message: diagnostic.diagnostic.message.clone(),
19632 group_id: diagnostic.diagnostic.group_id,
19633 blocks,
19634 });
19635 cx.notify();
19636 }
19637
19638 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
19639 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19640 return;
19641 };
19642
19643 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
19644 if let ActiveDiagnostic::Group(group) = prev {
19645 self.display_map.update(cx, |display_map, cx| {
19646 display_map.remove_blocks(group.blocks, cx);
19647 });
19648 cx.notify();
19649 }
19650 }
19651
19652 /// Disable inline diagnostics rendering for this editor.
19653 pub fn disable_inline_diagnostics(&mut self) {
19654 self.inline_diagnostics_enabled = false;
19655 self.inline_diagnostics_update = Task::ready(());
19656 self.inline_diagnostics.clear();
19657 }
19658
19659 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
19660 self.diagnostics_enabled = false;
19661 self.dismiss_diagnostics(cx);
19662 self.inline_diagnostics_update = Task::ready(());
19663 self.inline_diagnostics.clear();
19664 }
19665
19666 pub fn disable_word_completions(&mut self) {
19667 self.word_completions_enabled = false;
19668 }
19669
19670 pub fn diagnostics_enabled(&self) -> bool {
19671 self.diagnostics_enabled && self.lsp_data_enabled()
19672 }
19673
19674 pub fn inline_diagnostics_enabled(&self) -> bool {
19675 self.inline_diagnostics_enabled && self.diagnostics_enabled()
19676 }
19677
19678 pub fn show_inline_diagnostics(&self) -> bool {
19679 self.show_inline_diagnostics
19680 }
19681
19682 pub fn toggle_inline_diagnostics(
19683 &mut self,
19684 _: &ToggleInlineDiagnostics,
19685 window: &mut Window,
19686 cx: &mut Context<Editor>,
19687 ) {
19688 self.show_inline_diagnostics = !self.show_inline_diagnostics;
19689 self.refresh_inline_diagnostics(false, window, cx);
19690 }
19691
19692 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
19693 self.diagnostics_max_severity = severity;
19694 self.display_map.update(cx, |display_map, _| {
19695 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
19696 });
19697 }
19698
19699 pub fn toggle_diagnostics(
19700 &mut self,
19701 _: &ToggleDiagnostics,
19702 window: &mut Window,
19703 cx: &mut Context<Editor>,
19704 ) {
19705 if !self.diagnostics_enabled() {
19706 return;
19707 }
19708
19709 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19710 EditorSettings::get_global(cx)
19711 .diagnostics_max_severity
19712 .filter(|severity| severity != &DiagnosticSeverity::Off)
19713 .unwrap_or(DiagnosticSeverity::Hint)
19714 } else {
19715 DiagnosticSeverity::Off
19716 };
19717 self.set_max_diagnostics_severity(new_severity, cx);
19718 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19719 self.active_diagnostics = ActiveDiagnostic::None;
19720 self.inline_diagnostics_update = Task::ready(());
19721 self.inline_diagnostics.clear();
19722 } else {
19723 self.refresh_inline_diagnostics(false, window, cx);
19724 }
19725
19726 cx.notify();
19727 }
19728
19729 pub fn toggle_minimap(
19730 &mut self,
19731 _: &ToggleMinimap,
19732 window: &mut Window,
19733 cx: &mut Context<Editor>,
19734 ) {
19735 if self.supports_minimap(cx) {
19736 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
19737 }
19738 }
19739
19740 fn refresh_inline_diagnostics(
19741 &mut self,
19742 debounce: bool,
19743 window: &mut Window,
19744 cx: &mut Context<Self>,
19745 ) {
19746 let max_severity = ProjectSettings::get_global(cx)
19747 .diagnostics
19748 .inline
19749 .max_severity
19750 .unwrap_or(self.diagnostics_max_severity);
19751
19752 if !self.inline_diagnostics_enabled()
19753 || !self.diagnostics_enabled()
19754 || !self.show_inline_diagnostics
19755 || max_severity == DiagnosticSeverity::Off
19756 {
19757 self.inline_diagnostics_update = Task::ready(());
19758 self.inline_diagnostics.clear();
19759 return;
19760 }
19761
19762 let debounce_ms = ProjectSettings::get_global(cx)
19763 .diagnostics
19764 .inline
19765 .update_debounce_ms;
19766 let debounce = if debounce && debounce_ms > 0 {
19767 Some(Duration::from_millis(debounce_ms))
19768 } else {
19769 None
19770 };
19771 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
19772 if let Some(debounce) = debounce {
19773 cx.background_executor().timer(debounce).await;
19774 }
19775 let Some(snapshot) = editor.upgrade().map(|editor| {
19776 editor.update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
19777 }) else {
19778 return;
19779 };
19780
19781 let new_inline_diagnostics = cx
19782 .background_spawn(async move {
19783 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
19784 for diagnostic_entry in
19785 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
19786 {
19787 let message = diagnostic_entry
19788 .diagnostic
19789 .message
19790 .split_once('\n')
19791 .map(|(line, _)| line)
19792 .map(SharedString::new)
19793 .unwrap_or_else(|| {
19794 SharedString::new(&*diagnostic_entry.diagnostic.message)
19795 });
19796 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
19797 let (Ok(i) | Err(i)) = inline_diagnostics
19798 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
19799 inline_diagnostics.insert(
19800 i,
19801 (
19802 start_anchor,
19803 InlineDiagnostic {
19804 message,
19805 group_id: diagnostic_entry.diagnostic.group_id,
19806 start: diagnostic_entry.range.start.to_point(&snapshot),
19807 is_primary: diagnostic_entry.diagnostic.is_primary,
19808 severity: diagnostic_entry.diagnostic.severity,
19809 },
19810 ),
19811 );
19812 }
19813 inline_diagnostics
19814 })
19815 .await;
19816
19817 editor
19818 .update(cx, |editor, cx| {
19819 editor.inline_diagnostics = new_inline_diagnostics;
19820 cx.notify();
19821 })
19822 .ok();
19823 });
19824 }
19825
19826 fn pull_diagnostics(
19827 &mut self,
19828 buffer_id: BufferId,
19829 _window: &Window,
19830 cx: &mut Context<Self>,
19831 ) -> Option<()> {
19832 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
19833 // skip any LSP updates for it.
19834
19835 if self.active_diagnostics == ActiveDiagnostic::All || !self.diagnostics_enabled() {
19836 return None;
19837 }
19838 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
19839 .diagnostics
19840 .lsp_pull_diagnostics;
19841 if !pull_diagnostics_settings.enabled {
19842 return None;
19843 }
19844 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
19845 let project = self.project()?.downgrade();
19846 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
19847
19848 self.pull_diagnostics_task = cx.spawn(async move |_, cx| {
19849 cx.background_executor().timer(debounce).await;
19850 if let Ok(task) = project.update(cx, |project, cx| {
19851 project.lsp_store().update(cx, |lsp_store, cx| {
19852 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
19853 })
19854 }) {
19855 task.await.log_err();
19856 }
19857 project
19858 .update(cx, |project, cx| {
19859 project.lsp_store().update(cx, |lsp_store, cx| {
19860 lsp_store.pull_document_diagnostics_for_buffer_edit(buffer_id, cx);
19861 })
19862 })
19863 .log_err();
19864 });
19865
19866 Some(())
19867 }
19868
19869 pub fn set_selections_from_remote(
19870 &mut self,
19871 selections: Vec<Selection<Anchor>>,
19872 pending_selection: Option<Selection<Anchor>>,
19873 window: &mut Window,
19874 cx: &mut Context<Self>,
19875 ) {
19876 let old_cursor_position = self.selections.newest_anchor().head();
19877 self.selections
19878 .change_with(&self.display_snapshot(cx), |s| {
19879 s.select_anchors(selections);
19880 if let Some(pending_selection) = pending_selection {
19881 s.set_pending(pending_selection, SelectMode::Character);
19882 } else {
19883 s.clear_pending();
19884 }
19885 });
19886 self.selections_did_change(
19887 false,
19888 &old_cursor_position,
19889 SelectionEffects::default(),
19890 window,
19891 cx,
19892 );
19893 }
19894
19895 pub fn transact(
19896 &mut self,
19897 window: &mut Window,
19898 cx: &mut Context<Self>,
19899 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
19900 ) -> Option<TransactionId> {
19901 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
19902 this.start_transaction_at(Instant::now(), window, cx);
19903 update(this, window, cx);
19904 this.end_transaction_at(Instant::now(), cx)
19905 })
19906 }
19907
19908 pub fn start_transaction_at(
19909 &mut self,
19910 now: Instant,
19911 window: &mut Window,
19912 cx: &mut Context<Self>,
19913 ) -> Option<TransactionId> {
19914 self.end_selection(window, cx);
19915 if let Some(tx_id) = self
19916 .buffer
19917 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
19918 {
19919 self.selection_history
19920 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
19921 cx.emit(EditorEvent::TransactionBegun {
19922 transaction_id: tx_id,
19923 });
19924 Some(tx_id)
19925 } else {
19926 None
19927 }
19928 }
19929
19930 pub fn end_transaction_at(
19931 &mut self,
19932 now: Instant,
19933 cx: &mut Context<Self>,
19934 ) -> Option<TransactionId> {
19935 if let Some(transaction_id) = self
19936 .buffer
19937 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
19938 {
19939 if let Some((_, end_selections)) =
19940 self.selection_history.transaction_mut(transaction_id)
19941 {
19942 *end_selections = Some(self.selections.disjoint_anchors_arc());
19943 } else {
19944 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
19945 }
19946
19947 cx.emit(EditorEvent::Edited { transaction_id });
19948 Some(transaction_id)
19949 } else {
19950 None
19951 }
19952 }
19953
19954 pub fn modify_transaction_selection_history(
19955 &mut self,
19956 transaction_id: TransactionId,
19957 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
19958 ) -> bool {
19959 self.selection_history
19960 .transaction_mut(transaction_id)
19961 .map(modify)
19962 .is_some()
19963 }
19964
19965 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
19966 if self.selection_mark_mode {
19967 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19968 s.move_with(&mut |_, sel| {
19969 sel.collapse_to(sel.head(), SelectionGoal::None);
19970 });
19971 })
19972 }
19973 self.selection_mark_mode = true;
19974 cx.notify();
19975 }
19976
19977 pub fn swap_selection_ends(
19978 &mut self,
19979 _: &actions::SwapSelectionEnds,
19980 window: &mut Window,
19981 cx: &mut Context<Self>,
19982 ) {
19983 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19984 s.move_with(&mut |_, sel| {
19985 if sel.start != sel.end {
19986 sel.reversed = !sel.reversed
19987 }
19988 });
19989 });
19990 self.request_autoscroll(Autoscroll::newest(), cx);
19991 cx.notify();
19992 }
19993
19994 pub fn toggle_focus(
19995 workspace: &mut Workspace,
19996 _: &actions::ToggleFocus,
19997 window: &mut Window,
19998 cx: &mut Context<Workspace>,
19999 ) {
20000 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
20001 return;
20002 };
20003 workspace.activate_item(&item, true, true, window, cx);
20004 }
20005
20006 pub fn toggle_fold(
20007 &mut self,
20008 _: &actions::ToggleFold,
20009 window: &mut Window,
20010 cx: &mut Context<Self>,
20011 ) {
20012 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
20013 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20014 let selection = self.selections.newest::<Point>(&display_map);
20015
20016 let range = if selection.is_empty() {
20017 let point = selection.head().to_display_point(&display_map);
20018 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
20019 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
20020 .to_point(&display_map);
20021 start..end
20022 } else {
20023 selection.range()
20024 };
20025 if display_map.folds_in_range(range).next().is_some() {
20026 self.unfold_lines(&Default::default(), window, cx)
20027 } else {
20028 self.fold(&Default::default(), window, cx)
20029 }
20030 } else {
20031 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20032 let buffer_ids: HashSet<_> = self
20033 .selections
20034 .disjoint_anchor_ranges()
20035 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20036 .collect();
20037
20038 let should_unfold = buffer_ids
20039 .iter()
20040 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
20041
20042 for buffer_id in buffer_ids {
20043 if should_unfold {
20044 self.unfold_buffer(buffer_id, cx);
20045 } else {
20046 self.fold_buffer(buffer_id, cx);
20047 }
20048 }
20049 }
20050 }
20051
20052 pub fn toggle_fold_recursive(
20053 &mut self,
20054 _: &actions::ToggleFoldRecursive,
20055 window: &mut Window,
20056 cx: &mut Context<Self>,
20057 ) {
20058 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20059
20060 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20061 let range = if selection.is_empty() {
20062 let point = selection.head().to_display_point(&display_map);
20063 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
20064 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
20065 .to_point(&display_map);
20066 start..end
20067 } else {
20068 selection.range()
20069 };
20070 if display_map.folds_in_range(range).next().is_some() {
20071 self.unfold_recursive(&Default::default(), window, cx)
20072 } else {
20073 self.fold_recursive(&Default::default(), window, cx)
20074 }
20075 }
20076
20077 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
20078 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
20079 let mut to_fold = Vec::new();
20080 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20081 let selections = self.selections.all_adjusted(&display_map);
20082
20083 for selection in selections {
20084 let range = selection.range().sorted();
20085 let buffer_start_row = range.start.row;
20086
20087 if range.start.row != range.end.row {
20088 let mut found = false;
20089 let mut row = range.start.row;
20090 while row <= range.end.row {
20091 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
20092 {
20093 found = true;
20094 row = crease.range().end.row + 1;
20095 to_fold.push(crease);
20096 } else {
20097 row += 1
20098 }
20099 }
20100 if found {
20101 continue;
20102 }
20103 }
20104
20105 for row in (0..=range.start.row).rev() {
20106 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
20107 && crease.range().end.row >= buffer_start_row
20108 {
20109 to_fold.push(crease);
20110 if row <= range.start.row {
20111 break;
20112 }
20113 }
20114 }
20115 }
20116
20117 self.fold_creases(to_fold, true, window, cx);
20118 } else {
20119 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20120 let buffer_ids = self
20121 .selections
20122 .disjoint_anchor_ranges()
20123 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20124 .collect::<HashSet<_>>();
20125 for buffer_id in buffer_ids {
20126 self.fold_buffer(buffer_id, cx);
20127 }
20128 }
20129 }
20130
20131 pub fn toggle_fold_all(
20132 &mut self,
20133 _: &actions::ToggleFoldAll,
20134 window: &mut Window,
20135 cx: &mut Context<Self>,
20136 ) {
20137 let has_folds = if self.buffer.read(cx).is_singleton() {
20138 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20139 let has_folds = display_map
20140 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
20141 .next()
20142 .is_some();
20143 has_folds
20144 } else {
20145 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
20146 let has_folds = buffer_ids
20147 .iter()
20148 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
20149 has_folds
20150 };
20151
20152 if has_folds {
20153 self.unfold_all(&actions::UnfoldAll, window, cx);
20154 } else {
20155 self.fold_all(&actions::FoldAll, window, cx);
20156 }
20157 }
20158
20159 fn fold_at_level(
20160 &mut self,
20161 fold_at: &FoldAtLevel,
20162 window: &mut Window,
20163 cx: &mut Context<Self>,
20164 ) {
20165 if !self.buffer.read(cx).is_singleton() {
20166 return;
20167 }
20168
20169 let fold_at_level = fold_at.0;
20170 let snapshot = self.buffer.read(cx).snapshot(cx);
20171 let mut to_fold = Vec::new();
20172 let mut stack = vec![(0, snapshot.max_row().0, 1)];
20173
20174 let row_ranges_to_keep: Vec<Range<u32>> = self
20175 .selections
20176 .all::<Point>(&self.display_snapshot(cx))
20177 .into_iter()
20178 .map(|sel| sel.start.row..sel.end.row)
20179 .collect();
20180
20181 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
20182 while start_row < end_row {
20183 match self
20184 .snapshot(window, cx)
20185 .crease_for_buffer_row(MultiBufferRow(start_row))
20186 {
20187 Some(crease) => {
20188 let nested_start_row = crease.range().start.row + 1;
20189 let nested_end_row = crease.range().end.row;
20190
20191 if current_level < fold_at_level {
20192 stack.push((nested_start_row, nested_end_row, current_level + 1));
20193 } else if current_level == fold_at_level {
20194 // Fold iff there is no selection completely contained within the fold region
20195 if !row_ranges_to_keep.iter().any(|selection| {
20196 selection.end >= nested_start_row
20197 && selection.start <= nested_end_row
20198 }) {
20199 to_fold.push(crease);
20200 }
20201 }
20202
20203 start_row = nested_end_row + 1;
20204 }
20205 None => start_row += 1,
20206 }
20207 }
20208 }
20209
20210 self.fold_creases(to_fold, true, window, cx);
20211 }
20212
20213 pub fn fold_at_level_1(
20214 &mut self,
20215 _: &actions::FoldAtLevel1,
20216 window: &mut Window,
20217 cx: &mut Context<Self>,
20218 ) {
20219 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
20220 }
20221
20222 pub fn fold_at_level_2(
20223 &mut self,
20224 _: &actions::FoldAtLevel2,
20225 window: &mut Window,
20226 cx: &mut Context<Self>,
20227 ) {
20228 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
20229 }
20230
20231 pub fn fold_at_level_3(
20232 &mut self,
20233 _: &actions::FoldAtLevel3,
20234 window: &mut Window,
20235 cx: &mut Context<Self>,
20236 ) {
20237 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
20238 }
20239
20240 pub fn fold_at_level_4(
20241 &mut self,
20242 _: &actions::FoldAtLevel4,
20243 window: &mut Window,
20244 cx: &mut Context<Self>,
20245 ) {
20246 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
20247 }
20248
20249 pub fn fold_at_level_5(
20250 &mut self,
20251 _: &actions::FoldAtLevel5,
20252 window: &mut Window,
20253 cx: &mut Context<Self>,
20254 ) {
20255 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
20256 }
20257
20258 pub fn fold_at_level_6(
20259 &mut self,
20260 _: &actions::FoldAtLevel6,
20261 window: &mut Window,
20262 cx: &mut Context<Self>,
20263 ) {
20264 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
20265 }
20266
20267 pub fn fold_at_level_7(
20268 &mut self,
20269 _: &actions::FoldAtLevel7,
20270 window: &mut Window,
20271 cx: &mut Context<Self>,
20272 ) {
20273 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
20274 }
20275
20276 pub fn fold_at_level_8(
20277 &mut self,
20278 _: &actions::FoldAtLevel8,
20279 window: &mut Window,
20280 cx: &mut Context<Self>,
20281 ) {
20282 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
20283 }
20284
20285 pub fn fold_at_level_9(
20286 &mut self,
20287 _: &actions::FoldAtLevel9,
20288 window: &mut Window,
20289 cx: &mut Context<Self>,
20290 ) {
20291 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
20292 }
20293
20294 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
20295 if self.buffer.read(cx).is_singleton() {
20296 let mut fold_ranges = Vec::new();
20297 let snapshot = self.buffer.read(cx).snapshot(cx);
20298
20299 for row in 0..snapshot.max_row().0 {
20300 if let Some(foldable_range) = self
20301 .snapshot(window, cx)
20302 .crease_for_buffer_row(MultiBufferRow(row))
20303 {
20304 fold_ranges.push(foldable_range);
20305 }
20306 }
20307
20308 self.fold_creases(fold_ranges, true, window, cx);
20309 } else {
20310 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
20311 editor
20312 .update_in(cx, |editor, _, cx| {
20313 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
20314 editor.fold_buffer(buffer_id, cx);
20315 }
20316 })
20317 .ok();
20318 });
20319 }
20320 }
20321
20322 pub fn fold_function_bodies(
20323 &mut self,
20324 _: &actions::FoldFunctionBodies,
20325 window: &mut Window,
20326 cx: &mut Context<Self>,
20327 ) {
20328 let snapshot = self.buffer.read(cx).snapshot(cx);
20329
20330 let ranges = snapshot
20331 .text_object_ranges(
20332 MultiBufferOffset(0)..snapshot.len(),
20333 TreeSitterOptions::default(),
20334 )
20335 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
20336 .collect::<Vec<_>>();
20337
20338 let creases = ranges
20339 .into_iter()
20340 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
20341 .collect();
20342
20343 self.fold_creases(creases, true, window, cx);
20344 }
20345
20346 pub fn fold_recursive(
20347 &mut self,
20348 _: &actions::FoldRecursive,
20349 window: &mut Window,
20350 cx: &mut Context<Self>,
20351 ) {
20352 let mut to_fold = Vec::new();
20353 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20354 let selections = self.selections.all_adjusted(&display_map);
20355
20356 for selection in selections {
20357 let range = selection.range().sorted();
20358 let buffer_start_row = range.start.row;
20359
20360 if range.start.row != range.end.row {
20361 let mut found = false;
20362 for row in range.start.row..=range.end.row {
20363 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
20364 found = true;
20365 to_fold.push(crease);
20366 }
20367 }
20368 if found {
20369 continue;
20370 }
20371 }
20372
20373 for row in (0..=range.start.row).rev() {
20374 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
20375 if crease.range().end.row >= buffer_start_row {
20376 to_fold.push(crease);
20377 } else {
20378 break;
20379 }
20380 }
20381 }
20382 }
20383
20384 self.fold_creases(to_fold, true, window, cx);
20385 }
20386
20387 pub fn fold_at(
20388 &mut self,
20389 buffer_row: MultiBufferRow,
20390 window: &mut Window,
20391 cx: &mut Context<Self>,
20392 ) {
20393 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20394
20395 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
20396 let autoscroll = self
20397 .selections
20398 .all::<Point>(&display_map)
20399 .iter()
20400 .any(|selection| crease.range().overlaps(&selection.range()));
20401
20402 self.fold_creases(vec![crease], autoscroll, window, cx);
20403 }
20404 }
20405
20406 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
20407 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
20408 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20409 let buffer = display_map.buffer_snapshot();
20410 let selections = self.selections.all::<Point>(&display_map);
20411 let ranges = selections
20412 .iter()
20413 .map(|s| {
20414 let range = s.display_range(&display_map).sorted();
20415 let mut start = range.start.to_point(&display_map);
20416 let mut end = range.end.to_point(&display_map);
20417 start.column = 0;
20418 end.column = buffer.line_len(MultiBufferRow(end.row));
20419 start..end
20420 })
20421 .collect::<Vec<_>>();
20422
20423 self.unfold_ranges(&ranges, true, true, cx);
20424 } else {
20425 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20426 let buffer_ids = self
20427 .selections
20428 .disjoint_anchor_ranges()
20429 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20430 .collect::<HashSet<_>>();
20431 for buffer_id in buffer_ids {
20432 self.unfold_buffer(buffer_id, cx);
20433 }
20434 }
20435 }
20436
20437 pub fn unfold_recursive(
20438 &mut self,
20439 _: &UnfoldRecursive,
20440 _window: &mut Window,
20441 cx: &mut Context<Self>,
20442 ) {
20443 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20444 let selections = self.selections.all::<Point>(&display_map);
20445 let ranges = selections
20446 .iter()
20447 .map(|s| {
20448 let mut range = s.display_range(&display_map).sorted();
20449 *range.start.column_mut() = 0;
20450 *range.end.column_mut() = display_map.line_len(range.end.row());
20451 let start = range.start.to_point(&display_map);
20452 let end = range.end.to_point(&display_map);
20453 start..end
20454 })
20455 .collect::<Vec<_>>();
20456
20457 self.unfold_ranges(&ranges, true, true, cx);
20458 }
20459
20460 pub fn unfold_at(
20461 &mut self,
20462 buffer_row: MultiBufferRow,
20463 _window: &mut Window,
20464 cx: &mut Context<Self>,
20465 ) {
20466 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20467
20468 let intersection_range = Point::new(buffer_row.0, 0)
20469 ..Point::new(
20470 buffer_row.0,
20471 display_map.buffer_snapshot().line_len(buffer_row),
20472 );
20473
20474 let autoscroll = self
20475 .selections
20476 .all::<Point>(&display_map)
20477 .iter()
20478 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
20479
20480 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
20481 }
20482
20483 pub fn unfold_all(
20484 &mut self,
20485 _: &actions::UnfoldAll,
20486 _window: &mut Window,
20487 cx: &mut Context<Self>,
20488 ) {
20489 if self.buffer.read(cx).is_singleton() {
20490 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20491 self.unfold_ranges(
20492 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
20493 true,
20494 true,
20495 cx,
20496 );
20497 } else {
20498 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
20499 editor
20500 .update(cx, |editor, cx| {
20501 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
20502 editor.unfold_buffer(buffer_id, cx);
20503 }
20504 })
20505 .ok();
20506 });
20507 }
20508 }
20509
20510 pub fn fold_selected_ranges(
20511 &mut self,
20512 _: &FoldSelectedRanges,
20513 window: &mut Window,
20514 cx: &mut Context<Self>,
20515 ) {
20516 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20517 let selections = self.selections.all_adjusted(&display_map);
20518 let ranges = selections
20519 .into_iter()
20520 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
20521 .collect::<Vec<_>>();
20522 self.fold_creases(ranges, true, window, cx);
20523 }
20524
20525 pub fn fold_ranges<T: ToOffset + Clone>(
20526 &mut self,
20527 ranges: Vec<Range<T>>,
20528 auto_scroll: bool,
20529 window: &mut Window,
20530 cx: &mut Context<Self>,
20531 ) {
20532 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20533 let ranges = ranges
20534 .into_iter()
20535 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
20536 .collect::<Vec<_>>();
20537 self.fold_creases(ranges, auto_scroll, window, cx);
20538 }
20539
20540 pub fn fold_creases<T: ToOffset + Clone>(
20541 &mut self,
20542 creases: Vec<Crease<T>>,
20543 auto_scroll: bool,
20544 window: &mut Window,
20545 cx: &mut Context<Self>,
20546 ) {
20547 if creases.is_empty() {
20548 return;
20549 }
20550
20551 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
20552
20553 if auto_scroll {
20554 self.request_autoscroll(Autoscroll::fit(), cx);
20555 }
20556
20557 cx.notify();
20558
20559 self.scrollbar_marker_state.dirty = true;
20560 self.update_data_on_scroll(window, cx);
20561 self.folds_did_change(cx);
20562 }
20563
20564 /// Removes any folds whose ranges intersect any of the given ranges.
20565 pub fn unfold_ranges<T: ToOffset + Clone>(
20566 &mut self,
20567 ranges: &[Range<T>],
20568 inclusive: bool,
20569 auto_scroll: bool,
20570 cx: &mut Context<Self>,
20571 ) {
20572 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20573 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx);
20574 });
20575 self.folds_did_change(cx);
20576 }
20577
20578 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20579 self.fold_buffers([buffer_id], cx);
20580 }
20581
20582 pub fn fold_buffers(
20583 &mut self,
20584 buffer_ids: impl IntoIterator<Item = BufferId>,
20585 cx: &mut Context<Self>,
20586 ) {
20587 if self.buffer().read(cx).is_singleton() {
20588 return;
20589 }
20590
20591 let ids_to_fold: Vec<BufferId> = buffer_ids
20592 .into_iter()
20593 .filter(|id| !self.is_buffer_folded(*id, cx))
20594 .collect();
20595
20596 if ids_to_fold.is_empty() {
20597 return;
20598 }
20599
20600 let mut all_folded_excerpt_ids = Vec::new();
20601 for buffer_id in &ids_to_fold {
20602 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(*buffer_id, cx);
20603 all_folded_excerpt_ids.extend(folded_excerpts.into_iter().map(|(id, _, _)| id));
20604 }
20605
20606 self.display_map.update(cx, |display_map, cx| {
20607 display_map.fold_buffers(ids_to_fold.clone(), cx)
20608 });
20609
20610 let snapshot = self.display_snapshot(cx);
20611 self.selections.change_with(&snapshot, |selections| {
20612 for buffer_id in ids_to_fold {
20613 selections.remove_selections_from_buffer(buffer_id);
20614 }
20615 });
20616
20617 cx.emit(EditorEvent::BufferFoldToggled {
20618 ids: all_folded_excerpt_ids,
20619 folded: true,
20620 });
20621 cx.notify();
20622 }
20623
20624 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20625 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
20626 return;
20627 }
20628 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
20629 self.display_map.update(cx, |display_map, cx| {
20630 display_map.unfold_buffers([buffer_id], cx);
20631 });
20632 cx.emit(EditorEvent::BufferFoldToggled {
20633 ids: unfolded_excerpts.iter().map(|&(id, _, _)| id).collect(),
20634 folded: false,
20635 });
20636 cx.notify();
20637 }
20638
20639 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
20640 self.display_map.read(cx).is_buffer_folded(buffer)
20641 }
20642
20643 pub fn has_any_buffer_folded(&self, cx: &App) -> bool {
20644 if self.buffer().read(cx).is_singleton() {
20645 return false;
20646 }
20647 !self.folded_buffers(cx).is_empty()
20648 }
20649
20650 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
20651 self.display_map.read(cx).folded_buffers()
20652 }
20653
20654 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20655 self.display_map.update(cx, |display_map, cx| {
20656 display_map.disable_header_for_buffer(buffer_id, cx);
20657 });
20658 cx.notify();
20659 }
20660
20661 /// Removes any folds with the given ranges.
20662 pub fn remove_folds_with_type<T: ToOffset + Clone>(
20663 &mut self,
20664 ranges: &[Range<T>],
20665 type_id: TypeId,
20666 auto_scroll: bool,
20667 cx: &mut Context<Self>,
20668 ) {
20669 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20670 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
20671 });
20672 self.folds_did_change(cx);
20673 }
20674
20675 fn remove_folds_with<T: ToOffset + Clone>(
20676 &mut self,
20677 ranges: &[Range<T>],
20678 auto_scroll: bool,
20679 cx: &mut Context<Self>,
20680 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
20681 ) {
20682 if ranges.is_empty() {
20683 return;
20684 }
20685
20686 let mut buffers_affected = HashSet::default();
20687 let multi_buffer = self.buffer().read(cx);
20688 for range in ranges {
20689 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
20690 buffers_affected.insert(buffer.read(cx).remote_id());
20691 };
20692 }
20693
20694 self.display_map.update(cx, update);
20695
20696 if auto_scroll {
20697 self.request_autoscroll(Autoscroll::fit(), cx);
20698 }
20699
20700 cx.notify();
20701 self.scrollbar_marker_state.dirty = true;
20702 self.active_indent_guides_state.dirty = true;
20703 }
20704
20705 pub fn update_renderer_widths(
20706 &mut self,
20707 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
20708 cx: &mut Context<Self>,
20709 ) -> bool {
20710 self.display_map
20711 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
20712 }
20713
20714 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
20715 self.display_map.read(cx).fold_placeholder.clone()
20716 }
20717
20718 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
20719 self.buffer.update(cx, |buffer, cx| {
20720 buffer.set_all_diff_hunks_expanded(cx);
20721 });
20722 }
20723
20724 pub fn expand_all_diff_hunks(
20725 &mut self,
20726 _: &ExpandAllDiffHunks,
20727 _window: &mut Window,
20728 cx: &mut Context<Self>,
20729 ) {
20730 self.buffer.update(cx, |buffer, cx| {
20731 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20732 });
20733 }
20734
20735 pub fn collapse_all_diff_hunks(
20736 &mut self,
20737 _: &CollapseAllDiffHunks,
20738 _window: &mut Window,
20739 cx: &mut Context<Self>,
20740 ) {
20741 self.buffer.update(cx, |buffer, cx| {
20742 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20743 });
20744 }
20745
20746 pub fn toggle_selected_diff_hunks(
20747 &mut self,
20748 _: &ToggleSelectedDiffHunks,
20749 _window: &mut Window,
20750 cx: &mut Context<Self>,
20751 ) {
20752 let ranges: Vec<_> = self
20753 .selections
20754 .disjoint_anchors()
20755 .iter()
20756 .map(|s| s.range())
20757 .collect();
20758 self.toggle_diff_hunks_in_ranges(ranges, cx);
20759 }
20760
20761 pub fn diff_hunks_in_ranges<'a>(
20762 &'a self,
20763 ranges: &'a [Range<Anchor>],
20764 buffer: &'a MultiBufferSnapshot,
20765 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
20766 ranges.iter().flat_map(move |range| {
20767 let end_excerpt_id = range.end.excerpt_id;
20768 let range = range.to_point(buffer);
20769 let mut peek_end = range.end;
20770 if range.end.row < buffer.max_row().0 {
20771 peek_end = Point::new(range.end.row + 1, 0);
20772 }
20773 buffer
20774 .diff_hunks_in_range(range.start..peek_end)
20775 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
20776 })
20777 }
20778
20779 pub fn has_stageable_diff_hunks_in_ranges(
20780 &self,
20781 ranges: &[Range<Anchor>],
20782 snapshot: &MultiBufferSnapshot,
20783 ) -> bool {
20784 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
20785 hunks.any(|hunk| hunk.status().has_secondary_hunk())
20786 }
20787
20788 pub fn toggle_staged_selected_diff_hunks(
20789 &mut self,
20790 _: &::git::ToggleStaged,
20791 _: &mut Window,
20792 cx: &mut Context<Self>,
20793 ) {
20794 let snapshot = self.buffer.read(cx).snapshot(cx);
20795 let ranges: Vec<_> = self
20796 .selections
20797 .disjoint_anchors()
20798 .iter()
20799 .map(|s| s.range())
20800 .collect();
20801 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
20802 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20803 }
20804
20805 pub fn set_render_diff_hunk_controls(
20806 &mut self,
20807 render_diff_hunk_controls: RenderDiffHunkControlsFn,
20808 cx: &mut Context<Self>,
20809 ) {
20810 self.render_diff_hunk_controls = render_diff_hunk_controls;
20811 cx.notify();
20812 }
20813
20814 pub fn stage_and_next(
20815 &mut self,
20816 _: &::git::StageAndNext,
20817 window: &mut Window,
20818 cx: &mut Context<Self>,
20819 ) {
20820 self.do_stage_or_unstage_and_next(true, window, cx);
20821 }
20822
20823 pub fn unstage_and_next(
20824 &mut self,
20825 _: &::git::UnstageAndNext,
20826 window: &mut Window,
20827 cx: &mut Context<Self>,
20828 ) {
20829 self.do_stage_or_unstage_and_next(false, window, cx);
20830 }
20831
20832 pub fn stage_or_unstage_diff_hunks(
20833 &mut self,
20834 stage: bool,
20835 ranges: Vec<Range<Anchor>>,
20836 cx: &mut Context<Self>,
20837 ) {
20838 if self.delegate_stage_and_restore {
20839 let snapshot = self.buffer.read(cx).snapshot(cx);
20840 let hunks: Vec<_> = self.diff_hunks_in_ranges(&ranges, &snapshot).collect();
20841 if !hunks.is_empty() {
20842 cx.emit(EditorEvent::StageOrUnstageRequested { stage, hunks });
20843 }
20844 return;
20845 }
20846 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
20847 cx.spawn(async move |this, cx| {
20848 task.await?;
20849 this.update(cx, |this, cx| {
20850 let snapshot = this.buffer.read(cx).snapshot(cx);
20851 let chunk_by = this
20852 .diff_hunks_in_ranges(&ranges, &snapshot)
20853 .chunk_by(|hunk| hunk.buffer_id);
20854 for (buffer_id, hunks) in &chunk_by {
20855 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
20856 }
20857 })
20858 })
20859 .detach_and_log_err(cx);
20860 }
20861
20862 fn save_buffers_for_ranges_if_needed(
20863 &mut self,
20864 ranges: &[Range<Anchor>],
20865 cx: &mut Context<Editor>,
20866 ) -> Task<Result<()>> {
20867 let multibuffer = self.buffer.read(cx);
20868 let snapshot = multibuffer.read(cx);
20869 let buffer_ids: HashSet<_> = ranges
20870 .iter()
20871 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
20872 .collect();
20873 drop(snapshot);
20874
20875 let mut buffers = HashSet::default();
20876 for buffer_id in buffer_ids {
20877 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
20878 let buffer = buffer_entity.read(cx);
20879 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
20880 {
20881 buffers.insert(buffer_entity);
20882 }
20883 }
20884 }
20885
20886 if let Some(project) = &self.project {
20887 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
20888 } else {
20889 Task::ready(Ok(()))
20890 }
20891 }
20892
20893 fn do_stage_or_unstage_and_next(
20894 &mut self,
20895 stage: bool,
20896 window: &mut Window,
20897 cx: &mut Context<Self>,
20898 ) {
20899 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
20900
20901 if ranges.iter().any(|range| range.start != range.end) {
20902 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20903 return;
20904 }
20905
20906 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20907
20908 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
20909 let wrap_around = !all_diff_hunks_expanded;
20910 let snapshot = self.snapshot(window, cx);
20911 let position = self
20912 .selections
20913 .newest::<Point>(&snapshot.display_snapshot)
20914 .head();
20915
20916 self.go_to_hunk_before_or_after_position(
20917 &snapshot,
20918 position,
20919 Direction::Next,
20920 wrap_around,
20921 window,
20922 cx,
20923 );
20924 }
20925
20926 pub(crate) fn do_stage_or_unstage(
20927 &self,
20928 stage: bool,
20929 buffer_id: BufferId,
20930 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
20931 cx: &mut App,
20932 ) -> Option<()> {
20933 let project = self.project()?;
20934 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
20935 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
20936 let buffer_snapshot = buffer.read(cx).snapshot();
20937 let file_exists = buffer_snapshot
20938 .file()
20939 .is_some_and(|file| file.disk_state().exists());
20940 diff.update(cx, |diff, cx| {
20941 diff.stage_or_unstage_hunks(
20942 stage,
20943 &hunks
20944 .map(|hunk| buffer_diff::DiffHunk {
20945 buffer_range: hunk.buffer_range,
20946 // We don't need to pass in word diffs here because they're only used for rendering and
20947 // this function changes internal state
20948 base_word_diffs: Vec::default(),
20949 buffer_word_diffs: Vec::default(),
20950 diff_base_byte_range: hunk.diff_base_byte_range.start.0
20951 ..hunk.diff_base_byte_range.end.0,
20952 secondary_status: hunk.status.secondary,
20953 range: Point::zero()..Point::zero(), // unused
20954 })
20955 .collect::<Vec<_>>(),
20956 &buffer_snapshot,
20957 file_exists,
20958 cx,
20959 )
20960 });
20961 None
20962 }
20963
20964 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
20965 let ranges: Vec<_> = self
20966 .selections
20967 .disjoint_anchors()
20968 .iter()
20969 .map(|s| s.range())
20970 .collect();
20971 self.buffer
20972 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
20973 }
20974
20975 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
20976 self.buffer.update(cx, |buffer, cx| {
20977 let ranges = vec![Anchor::min()..Anchor::max()];
20978 if !buffer.all_diff_hunks_expanded()
20979 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
20980 {
20981 buffer.collapse_diff_hunks(ranges, cx);
20982 true
20983 } else {
20984 false
20985 }
20986 })
20987 }
20988
20989 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
20990 if self.buffer.read(cx).all_diff_hunks_expanded() {
20991 return true;
20992 }
20993 let ranges = vec![Anchor::min()..Anchor::max()];
20994 self.buffer
20995 .read(cx)
20996 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
20997 }
20998
20999 fn toggle_diff_hunks_in_ranges(
21000 &mut self,
21001 ranges: Vec<Range<Anchor>>,
21002 cx: &mut Context<Editor>,
21003 ) {
21004 self.buffer.update(cx, |buffer, cx| {
21005 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
21006 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
21007 })
21008 }
21009
21010 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
21011 self.buffer.update(cx, |buffer, cx| {
21012 buffer.toggle_single_diff_hunk(range, cx);
21013 })
21014 }
21015
21016 pub(crate) fn apply_all_diff_hunks(
21017 &mut self,
21018 _: &ApplyAllDiffHunks,
21019 window: &mut Window,
21020 cx: &mut Context<Self>,
21021 ) {
21022 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
21023
21024 let buffers = self.buffer.read(cx).all_buffers();
21025 for branch_buffer in buffers {
21026 branch_buffer.update(cx, |branch_buffer, cx| {
21027 branch_buffer.merge_into_base(Vec::new(), cx);
21028 });
21029 }
21030
21031 if let Some(project) = self.project.clone() {
21032 self.save(
21033 SaveOptions {
21034 format: true,
21035 autosave: false,
21036 },
21037 project,
21038 window,
21039 cx,
21040 )
21041 .detach_and_log_err(cx);
21042 }
21043 }
21044
21045 pub(crate) fn apply_selected_diff_hunks(
21046 &mut self,
21047 _: &ApplyDiffHunk,
21048 window: &mut Window,
21049 cx: &mut Context<Self>,
21050 ) {
21051 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
21052 let snapshot = self.snapshot(window, cx);
21053 let hunks = snapshot.hunks_for_ranges(
21054 self.selections
21055 .all(&snapshot.display_snapshot)
21056 .into_iter()
21057 .map(|selection| selection.range()),
21058 );
21059 let mut ranges_by_buffer = HashMap::default();
21060 self.transact(window, cx, |editor, _window, cx| {
21061 for hunk in hunks {
21062 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
21063 ranges_by_buffer
21064 .entry(buffer.clone())
21065 .or_insert_with(Vec::new)
21066 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
21067 }
21068 }
21069
21070 for (buffer, ranges) in ranges_by_buffer {
21071 buffer.update(cx, |buffer, cx| {
21072 buffer.merge_into_base(ranges, cx);
21073 });
21074 }
21075 });
21076
21077 if let Some(project) = self.project.clone() {
21078 self.save(
21079 SaveOptions {
21080 format: true,
21081 autosave: false,
21082 },
21083 project,
21084 window,
21085 cx,
21086 )
21087 .detach_and_log_err(cx);
21088 }
21089 }
21090
21091 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
21092 if hovered != self.gutter_hovered {
21093 self.gutter_hovered = hovered;
21094 cx.notify();
21095 }
21096 }
21097
21098 pub fn insert_blocks(
21099 &mut self,
21100 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
21101 autoscroll: Option<Autoscroll>,
21102 cx: &mut Context<Self>,
21103 ) -> Vec<CustomBlockId> {
21104 let blocks = self
21105 .display_map
21106 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
21107 if let Some(autoscroll) = autoscroll {
21108 self.request_autoscroll(autoscroll, cx);
21109 }
21110 cx.notify();
21111 blocks
21112 }
21113
21114 pub fn resize_blocks(
21115 &mut self,
21116 heights: HashMap<CustomBlockId, u32>,
21117 autoscroll: Option<Autoscroll>,
21118 cx: &mut Context<Self>,
21119 ) {
21120 self.display_map
21121 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
21122 if let Some(autoscroll) = autoscroll {
21123 self.request_autoscroll(autoscroll, cx);
21124 }
21125 cx.notify();
21126 }
21127
21128 pub fn replace_blocks(
21129 &mut self,
21130 renderers: HashMap<CustomBlockId, RenderBlock>,
21131 autoscroll: Option<Autoscroll>,
21132 cx: &mut Context<Self>,
21133 ) {
21134 self.display_map
21135 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
21136 if let Some(autoscroll) = autoscroll {
21137 self.request_autoscroll(autoscroll, cx);
21138 }
21139 cx.notify();
21140 }
21141
21142 pub fn remove_blocks(
21143 &mut self,
21144 block_ids: HashSet<CustomBlockId>,
21145 autoscroll: Option<Autoscroll>,
21146 cx: &mut Context<Self>,
21147 ) {
21148 self.display_map.update(cx, |display_map, cx| {
21149 display_map.remove_blocks(block_ids, cx)
21150 });
21151 if let Some(autoscroll) = autoscroll {
21152 self.request_autoscroll(autoscroll, cx);
21153 }
21154 cx.notify();
21155 }
21156
21157 pub fn row_for_block(
21158 &self,
21159 block_id: CustomBlockId,
21160 cx: &mut Context<Self>,
21161 ) -> Option<DisplayRow> {
21162 self.display_map
21163 .update(cx, |map, cx| map.row_for_block(block_id, cx))
21164 }
21165
21166 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
21167 self.focused_block = Some(focused_block);
21168 }
21169
21170 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
21171 self.focused_block.take()
21172 }
21173
21174 pub fn insert_creases(
21175 &mut self,
21176 creases: impl IntoIterator<Item = Crease<Anchor>>,
21177 cx: &mut Context<Self>,
21178 ) -> Vec<CreaseId> {
21179 self.display_map
21180 .update(cx, |map, cx| map.insert_creases(creases, cx))
21181 }
21182
21183 pub fn remove_creases(
21184 &mut self,
21185 ids: impl IntoIterator<Item = CreaseId>,
21186 cx: &mut Context<Self>,
21187 ) -> Vec<(CreaseId, Range<Anchor>)> {
21188 self.display_map
21189 .update(cx, |map, cx| map.remove_creases(ids, cx))
21190 }
21191
21192 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
21193 self.display_map
21194 .update(cx, |map, cx| map.snapshot(cx))
21195 .longest_row()
21196 }
21197
21198 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
21199 self.display_map
21200 .update(cx, |map, cx| map.snapshot(cx))
21201 .max_point()
21202 }
21203
21204 pub fn text(&self, cx: &App) -> String {
21205 self.buffer.read(cx).read(cx).text()
21206 }
21207
21208 pub fn is_empty(&self, cx: &App) -> bool {
21209 self.buffer.read(cx).read(cx).is_empty()
21210 }
21211
21212 pub fn text_option(&self, cx: &App) -> Option<String> {
21213 let text = self.text(cx);
21214 let text = text.trim();
21215
21216 if text.is_empty() {
21217 return None;
21218 }
21219
21220 Some(text.to_string())
21221 }
21222
21223 pub fn set_text(
21224 &mut self,
21225 text: impl Into<Arc<str>>,
21226 window: &mut Window,
21227 cx: &mut Context<Self>,
21228 ) {
21229 self.transact(window, cx, |this, _, cx| {
21230 this.buffer
21231 .read(cx)
21232 .as_singleton()
21233 .expect("you can only call set_text on editors for singleton buffers")
21234 .update(cx, |buffer, cx| buffer.set_text(text, cx));
21235 });
21236 }
21237
21238 pub fn display_text(&self, cx: &mut App) -> String {
21239 self.display_map
21240 .update(cx, |map, cx| map.snapshot(cx))
21241 .text()
21242 }
21243
21244 fn create_minimap(
21245 &self,
21246 minimap_settings: MinimapSettings,
21247 window: &mut Window,
21248 cx: &mut Context<Self>,
21249 ) -> Option<Entity<Self>> {
21250 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
21251 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
21252 }
21253
21254 fn initialize_new_minimap(
21255 &self,
21256 minimap_settings: MinimapSettings,
21257 window: &mut Window,
21258 cx: &mut Context<Self>,
21259 ) -> Entity<Self> {
21260 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
21261 const MINIMAP_FONT_FAMILY: SharedString = SharedString::new_static(".ZedMono");
21262
21263 let mut minimap = Editor::new_internal(
21264 EditorMode::Minimap {
21265 parent: cx.weak_entity(),
21266 },
21267 self.buffer.clone(),
21268 None,
21269 Some(self.display_map.clone()),
21270 window,
21271 cx,
21272 );
21273 let my_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
21274 let minimap_snapshot = minimap.display_map.update(cx, |map, cx| map.snapshot(cx));
21275 minimap.scroll_manager.clone_state(
21276 &self.scroll_manager,
21277 &my_snapshot,
21278 &minimap_snapshot,
21279 cx,
21280 );
21281 minimap.set_text_style_refinement(TextStyleRefinement {
21282 font_size: Some(MINIMAP_FONT_SIZE),
21283 font_weight: Some(MINIMAP_FONT_WEIGHT),
21284 font_family: Some(MINIMAP_FONT_FAMILY),
21285 ..Default::default()
21286 });
21287 minimap.update_minimap_configuration(minimap_settings, cx);
21288 cx.new(|_| minimap)
21289 }
21290
21291 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
21292 let current_line_highlight = minimap_settings
21293 .current_line_highlight
21294 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
21295 self.set_current_line_highlight(Some(current_line_highlight));
21296 }
21297
21298 pub fn minimap(&self) -> Option<&Entity<Self>> {
21299 self.minimap
21300 .as_ref()
21301 .filter(|_| self.minimap_visibility.visible())
21302 }
21303
21304 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
21305 let mut wrap_guides = smallvec![];
21306
21307 if self.show_wrap_guides == Some(false) {
21308 return wrap_guides;
21309 }
21310
21311 let settings = self.buffer.read(cx).language_settings(cx);
21312 if settings.show_wrap_guides {
21313 match self.soft_wrap_mode(cx) {
21314 SoftWrap::Column(soft_wrap) => {
21315 wrap_guides.push((soft_wrap as usize, true));
21316 }
21317 SoftWrap::Bounded(soft_wrap) => {
21318 wrap_guides.push((soft_wrap as usize, true));
21319 }
21320 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
21321 }
21322 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
21323 }
21324
21325 wrap_guides
21326 }
21327
21328 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
21329 let settings = self.buffer.read(cx).language_settings(cx);
21330 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
21331 match mode {
21332 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
21333 SoftWrap::None
21334 }
21335 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
21336 language_settings::SoftWrap::PreferredLineLength => {
21337 SoftWrap::Column(settings.preferred_line_length)
21338 }
21339 language_settings::SoftWrap::Bounded => {
21340 SoftWrap::Bounded(settings.preferred_line_length)
21341 }
21342 }
21343 }
21344
21345 pub fn set_soft_wrap_mode(
21346 &mut self,
21347 mode: language_settings::SoftWrap,
21348 cx: &mut Context<Self>,
21349 ) {
21350 self.soft_wrap_mode_override = Some(mode);
21351 cx.notify();
21352 }
21353
21354 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
21355 self.hard_wrap = hard_wrap;
21356 cx.notify();
21357 }
21358
21359 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
21360 self.text_style_refinement = Some(style);
21361 }
21362
21363 /// called by the Element so we know what style we were most recently rendered with.
21364 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
21365 // We intentionally do not inform the display map about the minimap style
21366 // so that wrapping is not recalculated and stays consistent for the editor
21367 // and its linked minimap.
21368 if !self.mode.is_minimap() {
21369 let font = style.text.font();
21370 let font_size = style.text.font_size.to_pixels(window.rem_size());
21371 let display_map = self
21372 .placeholder_display_map
21373 .as_ref()
21374 .filter(|_| self.is_empty(cx))
21375 .unwrap_or(&self.display_map);
21376
21377 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
21378 }
21379 self.style = Some(style);
21380 }
21381
21382 pub fn style(&mut self, cx: &App) -> &EditorStyle {
21383 if self.style.is_none() {
21384 self.style = Some(self.create_style(cx));
21385 }
21386 self.style.as_ref().unwrap()
21387 }
21388
21389 // Called by the element. This method is not designed to be called outside of the editor
21390 // element's layout code because it does not notify when rewrapping is computed synchronously.
21391 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
21392 if self.is_empty(cx) {
21393 self.placeholder_display_map
21394 .as_ref()
21395 .map_or(false, |display_map| {
21396 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
21397 })
21398 } else {
21399 self.display_map
21400 .update(cx, |map, cx| map.set_wrap_width(width, cx))
21401 }
21402 }
21403
21404 pub fn set_soft_wrap(&mut self) {
21405 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
21406 }
21407
21408 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
21409 if self.soft_wrap_mode_override.is_some() {
21410 self.soft_wrap_mode_override.take();
21411 } else {
21412 let soft_wrap = match self.soft_wrap_mode(cx) {
21413 SoftWrap::GitDiff => return,
21414 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
21415 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
21416 language_settings::SoftWrap::None
21417 }
21418 };
21419 self.soft_wrap_mode_override = Some(soft_wrap);
21420 }
21421 cx.notify();
21422 }
21423
21424 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
21425 let Some(workspace) = self.workspace() else {
21426 return;
21427 };
21428 let fs = workspace.read(cx).app_state().fs.clone();
21429 let current_show = TabBarSettings::get_global(cx).show;
21430 update_settings_file(fs, cx, move |setting, _| {
21431 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
21432 });
21433 }
21434
21435 pub fn toggle_indent_guides(
21436 &mut self,
21437 _: &ToggleIndentGuides,
21438 _: &mut Window,
21439 cx: &mut Context<Self>,
21440 ) {
21441 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
21442 self.buffer
21443 .read(cx)
21444 .language_settings(cx)
21445 .indent_guides
21446 .enabled
21447 });
21448 self.show_indent_guides = Some(!currently_enabled);
21449 cx.notify();
21450 }
21451
21452 fn should_show_indent_guides(&self) -> Option<bool> {
21453 self.show_indent_guides
21454 }
21455
21456 pub fn disable_indent_guides_for_buffer(
21457 &mut self,
21458 buffer_id: BufferId,
21459 cx: &mut Context<Self>,
21460 ) {
21461 self.buffers_with_disabled_indent_guides.insert(buffer_id);
21462 cx.notify();
21463 }
21464
21465 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
21466 self.buffers_with_disabled_indent_guides
21467 .contains(&buffer_id)
21468 }
21469
21470 pub fn toggle_line_numbers(
21471 &mut self,
21472 _: &ToggleLineNumbers,
21473 _: &mut Window,
21474 cx: &mut Context<Self>,
21475 ) {
21476 let mut editor_settings = EditorSettings::get_global(cx).clone();
21477 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
21478 EditorSettings::override_global(editor_settings, cx);
21479 }
21480
21481 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
21482 if let Some(show_line_numbers) = self.show_line_numbers {
21483 return show_line_numbers;
21484 }
21485 EditorSettings::get_global(cx).gutter.line_numbers
21486 }
21487
21488 pub fn relative_line_numbers(&self, cx: &App) -> RelativeLineNumbers {
21489 match (
21490 self.use_relative_line_numbers,
21491 EditorSettings::get_global(cx).relative_line_numbers,
21492 ) {
21493 (None, setting) => setting,
21494 (Some(false), _) => RelativeLineNumbers::Disabled,
21495 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
21496 (Some(true), _) => RelativeLineNumbers::Enabled,
21497 }
21498 }
21499
21500 pub fn toggle_relative_line_numbers(
21501 &mut self,
21502 _: &ToggleRelativeLineNumbers,
21503 _: &mut Window,
21504 cx: &mut Context<Self>,
21505 ) {
21506 let is_relative = self.relative_line_numbers(cx);
21507 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
21508 }
21509
21510 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
21511 self.use_relative_line_numbers = is_relative;
21512 cx.notify();
21513 }
21514
21515 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
21516 self.show_gutter = show_gutter;
21517 cx.notify();
21518 }
21519
21520 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
21521 self.show_scrollbars = ScrollbarAxes {
21522 horizontal: show,
21523 vertical: show,
21524 };
21525 cx.notify();
21526 }
21527
21528 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21529 self.show_scrollbars.vertical = show;
21530 cx.notify();
21531 }
21532
21533 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21534 self.show_scrollbars.horizontal = show;
21535 cx.notify();
21536 }
21537
21538 pub fn set_minimap_visibility(
21539 &mut self,
21540 minimap_visibility: MinimapVisibility,
21541 window: &mut Window,
21542 cx: &mut Context<Self>,
21543 ) {
21544 if self.minimap_visibility != minimap_visibility {
21545 if minimap_visibility.visible() && self.minimap.is_none() {
21546 let minimap_settings = EditorSettings::get_global(cx).minimap;
21547 self.minimap =
21548 self.create_minimap(minimap_settings.with_show_override(), window, cx);
21549 }
21550 self.minimap_visibility = minimap_visibility;
21551 cx.notify();
21552 }
21553 }
21554
21555 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21556 self.set_show_scrollbars(false, cx);
21557 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
21558 }
21559
21560 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21561 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
21562 }
21563
21564 /// Normally the text in full mode and auto height editors is padded on the
21565 /// left side by roughly half a character width for improved hit testing.
21566 ///
21567 /// Use this method to disable this for cases where this is not wanted (e.g.
21568 /// if you want to align the editor text with some other text above or below)
21569 /// or if you want to add this padding to single-line editors.
21570 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
21571 self.offset_content = offset_content;
21572 cx.notify();
21573 }
21574
21575 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
21576 self.show_line_numbers = Some(show_line_numbers);
21577 cx.notify();
21578 }
21579
21580 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
21581 self.disable_expand_excerpt_buttons = true;
21582 cx.notify();
21583 }
21584
21585 pub fn set_number_deleted_lines(&mut self, number: bool, cx: &mut Context<Self>) {
21586 self.number_deleted_lines = number;
21587 cx.notify();
21588 }
21589
21590 pub fn set_delegate_expand_excerpts(&mut self, delegate: bool) {
21591 self.delegate_expand_excerpts = delegate;
21592 }
21593
21594 pub fn set_delegate_stage_and_restore(&mut self, delegate: bool) {
21595 self.delegate_stage_and_restore = delegate;
21596 }
21597
21598 pub fn set_delegate_open_excerpts(&mut self, delegate: bool) {
21599 self.delegate_open_excerpts = delegate;
21600 }
21601
21602 pub fn set_on_local_selections_changed(
21603 &mut self,
21604 callback: Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
21605 ) {
21606 self.on_local_selections_changed = callback;
21607 }
21608
21609 pub fn set_suppress_selection_callback(&mut self, suppress: bool) {
21610 self.suppress_selection_callback = suppress;
21611 }
21612
21613 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
21614 self.show_git_diff_gutter = Some(show_git_diff_gutter);
21615 cx.notify();
21616 }
21617
21618 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
21619 self.show_code_actions = Some(show_code_actions);
21620 cx.notify();
21621 }
21622
21623 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
21624 self.show_runnables = Some(show_runnables);
21625 cx.notify();
21626 }
21627
21628 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
21629 self.show_breakpoints = Some(show_breakpoints);
21630 cx.notify();
21631 }
21632
21633 pub fn set_show_diff_review_button(&mut self, show: bool, cx: &mut Context<Self>) {
21634 self.show_diff_review_button = show;
21635 cx.notify();
21636 }
21637
21638 pub fn show_diff_review_button(&self) -> bool {
21639 self.show_diff_review_button
21640 }
21641
21642 pub fn render_diff_review_button(
21643 &self,
21644 display_row: DisplayRow,
21645 width: Pixels,
21646 cx: &mut Context<Self>,
21647 ) -> impl IntoElement {
21648 let text_color = cx.theme().colors().text;
21649 let icon_color = cx.theme().colors().icon_accent;
21650
21651 h_flex()
21652 .id("diff_review_button")
21653 .cursor_pointer()
21654 .w(width - px(1.))
21655 .h(relative(0.9))
21656 .justify_center()
21657 .rounded_sm()
21658 .border_1()
21659 .border_color(text_color.opacity(0.1))
21660 .bg(text_color.opacity(0.15))
21661 .hover(|s| {
21662 s.bg(icon_color.opacity(0.4))
21663 .border_color(icon_color.opacity(0.5))
21664 })
21665 .child(Icon::new(IconName::Plus).size(IconSize::Small))
21666 .tooltip(Tooltip::text("Add Review (drag to select multiple lines)"))
21667 .on_mouse_down(
21668 gpui::MouseButton::Left,
21669 cx.listener(move |editor, _event: &gpui::MouseDownEvent, window, cx| {
21670 editor.start_diff_review_drag(display_row, window, cx);
21671 }),
21672 )
21673 }
21674
21675 pub fn start_diff_review_drag(
21676 &mut self,
21677 display_row: DisplayRow,
21678 window: &mut Window,
21679 cx: &mut Context<Self>,
21680 ) {
21681 let snapshot = self.snapshot(window, cx);
21682 let point = snapshot
21683 .display_snapshot
21684 .display_point_to_point(DisplayPoint::new(display_row, 0), Bias::Left);
21685 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21686 self.diff_review_drag_state = Some(DiffReviewDragState {
21687 start_anchor: anchor,
21688 current_anchor: anchor,
21689 });
21690 cx.notify();
21691 }
21692
21693 pub fn update_diff_review_drag(
21694 &mut self,
21695 display_row: DisplayRow,
21696 window: &mut Window,
21697 cx: &mut Context<Self>,
21698 ) {
21699 if self.diff_review_drag_state.is_none() {
21700 return;
21701 }
21702 let snapshot = self.snapshot(window, cx);
21703 let point = snapshot
21704 .display_snapshot
21705 .display_point_to_point(display_row.as_display_point(), Bias::Left);
21706 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21707 if let Some(drag_state) = &mut self.diff_review_drag_state {
21708 drag_state.current_anchor = anchor;
21709 cx.notify();
21710 }
21711 }
21712
21713 pub fn end_diff_review_drag(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21714 if let Some(drag_state) = self.diff_review_drag_state.take() {
21715 let snapshot = self.snapshot(window, cx);
21716 let range = drag_state.row_range(&snapshot.display_snapshot);
21717 self.show_diff_review_overlay(*range.start()..*range.end(), window, cx);
21718 }
21719 cx.notify();
21720 }
21721
21722 pub fn cancel_diff_review_drag(&mut self, cx: &mut Context<Self>) {
21723 self.diff_review_drag_state = None;
21724 cx.notify();
21725 }
21726
21727 /// Calculates the appropriate block height for the diff review overlay.
21728 /// Height is in lines: 2 for input row, 1 for header when comments exist,
21729 /// and 2 lines per comment when expanded.
21730 fn calculate_overlay_height(
21731 &self,
21732 hunk_key: &DiffHunkKey,
21733 comments_expanded: bool,
21734 snapshot: &MultiBufferSnapshot,
21735 ) -> u32 {
21736 let comment_count = self.hunk_comment_count(hunk_key, snapshot);
21737 let base_height: u32 = 2; // Input row with avatar and buttons
21738
21739 if comment_count == 0 {
21740 base_height
21741 } else if comments_expanded {
21742 // Header (1 line) + 2 lines per comment
21743 base_height + 1 + (comment_count as u32 * 2)
21744 } else {
21745 // Just header when collapsed
21746 base_height + 1
21747 }
21748 }
21749
21750 pub fn show_diff_review_overlay(
21751 &mut self,
21752 display_range: Range<DisplayRow>,
21753 window: &mut Window,
21754 cx: &mut Context<Self>,
21755 ) {
21756 let Range { start, end } = display_range.sorted();
21757
21758 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21759 let editor_snapshot = self.snapshot(window, cx);
21760
21761 // Convert display rows to multibuffer points
21762 let start_point = editor_snapshot
21763 .display_snapshot
21764 .display_point_to_point(start.as_display_point(), Bias::Left);
21765 let end_point = editor_snapshot
21766 .display_snapshot
21767 .display_point_to_point(end.as_display_point(), Bias::Left);
21768 let end_multi_buffer_row = MultiBufferRow(end_point.row);
21769
21770 // Create anchor range for the selected lines (start of first line to end of last line)
21771 let line_end = Point::new(
21772 end_point.row,
21773 buffer_snapshot.line_len(end_multi_buffer_row),
21774 );
21775 let anchor_range =
21776 buffer_snapshot.anchor_after(start_point)..buffer_snapshot.anchor_before(line_end);
21777
21778 // Compute the hunk key for this display row
21779 let file_path = buffer_snapshot
21780 .file_at(start_point)
21781 .map(|file: &Arc<dyn language::File>| file.path().clone())
21782 .unwrap_or_else(|| Arc::from(util::rel_path::RelPath::empty()));
21783 let hunk_start_anchor = buffer_snapshot.anchor_before(start_point);
21784 let new_hunk_key = DiffHunkKey {
21785 file_path,
21786 hunk_start_anchor,
21787 };
21788
21789 // Check if we already have an overlay for this hunk
21790 if let Some(existing_overlay) = self.diff_review_overlays.iter().find(|overlay| {
21791 Self::hunk_keys_match(&overlay.hunk_key, &new_hunk_key, &buffer_snapshot)
21792 }) {
21793 // Just focus the existing overlay's prompt editor
21794 let focus_handle = existing_overlay.prompt_editor.focus_handle(cx);
21795 window.focus(&focus_handle, cx);
21796 return;
21797 }
21798
21799 // Dismiss overlays that have no comments for their hunks
21800 self.dismiss_overlays_without_comments(cx);
21801
21802 // Get the current user's avatar URI from the project's user_store
21803 let user_avatar_uri = self.project.as_ref().and_then(|project| {
21804 let user_store = project.read(cx).user_store();
21805 user_store
21806 .read(cx)
21807 .current_user()
21808 .map(|user| user.avatar_uri.clone())
21809 });
21810
21811 // Create anchor at the end of the last row so the block appears immediately below it
21812 // Use multibuffer coordinates for anchor creation
21813 let line_len = buffer_snapshot.line_len(end_multi_buffer_row);
21814 let anchor = buffer_snapshot.anchor_after(Point::new(end_multi_buffer_row.0, line_len));
21815
21816 // Use the hunk key we already computed
21817 let hunk_key = new_hunk_key;
21818
21819 // Create the prompt editor for the review input
21820 let prompt_editor = cx.new(|cx| {
21821 let mut editor = Editor::single_line(window, cx);
21822 editor.set_placeholder_text("Add a review comment...", window, cx);
21823 editor
21824 });
21825
21826 // Register the Newline action on the prompt editor to submit the review
21827 let parent_editor = cx.entity().downgrade();
21828 let subscription = prompt_editor.update(cx, |prompt_editor, _cx| {
21829 prompt_editor.register_action({
21830 let parent_editor = parent_editor.clone();
21831 move |_: &crate::actions::Newline, window, cx| {
21832 if let Some(editor) = parent_editor.upgrade() {
21833 editor.update(cx, |editor, cx| {
21834 editor.submit_diff_review_comment(window, cx);
21835 });
21836 }
21837 }
21838 })
21839 });
21840
21841 // Calculate initial height based on existing comments for this hunk
21842 let initial_height = self.calculate_overlay_height(&hunk_key, true, &buffer_snapshot);
21843
21844 // Create the overlay block
21845 let prompt_editor_for_render = prompt_editor.clone();
21846 let hunk_key_for_render = hunk_key.clone();
21847 let editor_handle = cx.entity().downgrade();
21848 let block = BlockProperties {
21849 style: BlockStyle::Sticky,
21850 placement: BlockPlacement::Below(anchor),
21851 height: Some(initial_height),
21852 render: Arc::new(move |cx| {
21853 Self::render_diff_review_overlay(
21854 &prompt_editor_for_render,
21855 &hunk_key_for_render,
21856 &editor_handle,
21857 cx,
21858 )
21859 }),
21860 priority: 0,
21861 };
21862
21863 let block_ids = self.insert_blocks([block], None, cx);
21864 let Some(block_id) = block_ids.into_iter().next() else {
21865 log::error!("Failed to insert diff review overlay block");
21866 return;
21867 };
21868
21869 self.diff_review_overlays.push(DiffReviewOverlay {
21870 anchor_range,
21871 block_id,
21872 prompt_editor: prompt_editor.clone(),
21873 hunk_key,
21874 comments_expanded: true,
21875 inline_edit_editors: HashMap::default(),
21876 inline_edit_subscriptions: HashMap::default(),
21877 user_avatar_uri,
21878 _subscription: subscription,
21879 });
21880
21881 // Focus the prompt editor
21882 let focus_handle = prompt_editor.focus_handle(cx);
21883 window.focus(&focus_handle, cx);
21884
21885 cx.notify();
21886 }
21887
21888 /// Dismisses all diff review overlays.
21889 pub fn dismiss_all_diff_review_overlays(&mut self, cx: &mut Context<Self>) {
21890 if self.diff_review_overlays.is_empty() {
21891 return;
21892 }
21893 let block_ids: HashSet<_> = self
21894 .diff_review_overlays
21895 .drain(..)
21896 .map(|overlay| overlay.block_id)
21897 .collect();
21898 self.remove_blocks(block_ids, None, cx);
21899 cx.notify();
21900 }
21901
21902 /// Dismisses overlays that have no comments stored for their hunks.
21903 /// Keeps overlays that have at least one comment.
21904 fn dismiss_overlays_without_comments(&mut self, cx: &mut Context<Self>) {
21905 let snapshot = self.buffer.read(cx).snapshot(cx);
21906
21907 // First, compute which overlays have comments (to avoid borrow issues with retain)
21908 let overlays_with_comments: Vec<bool> = self
21909 .diff_review_overlays
21910 .iter()
21911 .map(|overlay| self.hunk_comment_count(&overlay.hunk_key, &snapshot) > 0)
21912 .collect();
21913
21914 // Now collect block IDs to remove and retain overlays
21915 let mut block_ids_to_remove = HashSet::default();
21916 let mut index = 0;
21917 self.diff_review_overlays.retain(|overlay| {
21918 let has_comments = overlays_with_comments[index];
21919 index += 1;
21920 if !has_comments {
21921 block_ids_to_remove.insert(overlay.block_id);
21922 }
21923 has_comments
21924 });
21925
21926 if !block_ids_to_remove.is_empty() {
21927 self.remove_blocks(block_ids_to_remove, None, cx);
21928 cx.notify();
21929 }
21930 }
21931
21932 /// Refreshes the diff review overlay block to update its height and render function.
21933 /// Uses resize_blocks and replace_blocks to avoid visual flicker from remove+insert.
21934 fn refresh_diff_review_overlay_height(
21935 &mut self,
21936 hunk_key: &DiffHunkKey,
21937 _window: &mut Window,
21938 cx: &mut Context<Self>,
21939 ) {
21940 // Extract all needed data from overlay first to avoid borrow conflicts
21941 let snapshot = self.buffer.read(cx).snapshot(cx);
21942 let (comments_expanded, block_id, prompt_editor) = {
21943 let Some(overlay) = self
21944 .diff_review_overlays
21945 .iter()
21946 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
21947 else {
21948 return;
21949 };
21950
21951 (
21952 overlay.comments_expanded,
21953 overlay.block_id,
21954 overlay.prompt_editor.clone(),
21955 )
21956 };
21957
21958 // Calculate new height
21959 let snapshot = self.buffer.read(cx).snapshot(cx);
21960 let new_height = self.calculate_overlay_height(hunk_key, comments_expanded, &snapshot);
21961
21962 // Update the block height using resize_blocks (avoids flicker)
21963 let mut heights = HashMap::default();
21964 heights.insert(block_id, new_height);
21965 self.resize_blocks(heights, None, cx);
21966
21967 // Update the render function using replace_blocks (avoids flicker)
21968 let hunk_key_for_render = hunk_key.clone();
21969 let editor_handle = cx.entity().downgrade();
21970 let render: Arc<dyn Fn(&mut BlockContext) -> AnyElement + Send + Sync> =
21971 Arc::new(move |cx| {
21972 Self::render_diff_review_overlay(
21973 &prompt_editor,
21974 &hunk_key_for_render,
21975 &editor_handle,
21976 cx,
21977 )
21978 });
21979
21980 let mut renderers = HashMap::default();
21981 renderers.insert(block_id, render);
21982 self.replace_blocks(renderers, None, cx);
21983 }
21984
21985 /// Action handler for SubmitDiffReviewComment.
21986 pub fn submit_diff_review_comment_action(
21987 &mut self,
21988 _: &SubmitDiffReviewComment,
21989 window: &mut Window,
21990 cx: &mut Context<Self>,
21991 ) {
21992 self.submit_diff_review_comment(window, cx);
21993 }
21994
21995 /// Stores the diff review comment locally.
21996 /// Comments are stored per-hunk and can later be batch-submitted to the Agent panel.
21997 pub fn submit_diff_review_comment(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21998 // Find the overlay that currently has focus
21999 let overlay_index = self
22000 .diff_review_overlays
22001 .iter()
22002 .position(|overlay| overlay.prompt_editor.focus_handle(cx).is_focused(window));
22003 let Some(overlay_index) = overlay_index else {
22004 return;
22005 };
22006 let overlay = &self.diff_review_overlays[overlay_index];
22007
22008 let comment_text = overlay.prompt_editor.read(cx).text(cx).trim().to_string();
22009 if comment_text.is_empty() {
22010 return;
22011 }
22012
22013 let anchor_range = overlay.anchor_range.clone();
22014 let hunk_key = overlay.hunk_key.clone();
22015
22016 self.add_review_comment(hunk_key.clone(), comment_text, anchor_range, cx);
22017
22018 // Clear the prompt editor but keep the overlay open
22019 if let Some(overlay) = self.diff_review_overlays.get(overlay_index) {
22020 overlay.prompt_editor.update(cx, |editor, cx| {
22021 editor.clear(window, cx);
22022 });
22023 }
22024
22025 // Refresh the overlay to update the block height for the new comment
22026 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22027
22028 cx.notify();
22029 }
22030
22031 /// Returns the prompt editor for the diff review overlay, if one is active.
22032 /// This is primarily used for testing.
22033 pub fn diff_review_prompt_editor(&self) -> Option<&Entity<Editor>> {
22034 self.diff_review_overlays
22035 .first()
22036 .map(|overlay| &overlay.prompt_editor)
22037 }
22038
22039 /// Returns the line range for the first diff review overlay, if one is active.
22040 /// Returns (start_row, end_row) as physical line numbers in the underlying file.
22041 pub fn diff_review_line_range(&self, cx: &App) -> Option<(u32, u32)> {
22042 let overlay = self.diff_review_overlays.first()?;
22043 let snapshot = self.buffer.read(cx).snapshot(cx);
22044 let start_point = overlay.anchor_range.start.to_point(&snapshot);
22045 let end_point = overlay.anchor_range.end.to_point(&snapshot);
22046 let start_row = snapshot
22047 .point_to_buffer_point(start_point)
22048 .map(|(_, p, _)| p.row)
22049 .unwrap_or(start_point.row);
22050 let end_row = snapshot
22051 .point_to_buffer_point(end_point)
22052 .map(|(_, p, _)| p.row)
22053 .unwrap_or(end_point.row);
22054 Some((start_row, end_row))
22055 }
22056
22057 /// Sets whether the comments section is expanded in the diff review overlay.
22058 /// This is primarily used for testing.
22059 pub fn set_diff_review_comments_expanded(&mut self, expanded: bool, cx: &mut Context<Self>) {
22060 for overlay in &mut self.diff_review_overlays {
22061 overlay.comments_expanded = expanded;
22062 }
22063 cx.notify();
22064 }
22065
22066 /// Compares two DiffHunkKeys for equality by resolving their anchors.
22067 fn hunk_keys_match(a: &DiffHunkKey, b: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> bool {
22068 a.file_path == b.file_path
22069 && a.hunk_start_anchor.to_point(snapshot) == b.hunk_start_anchor.to_point(snapshot)
22070 }
22071
22072 /// Returns comments for a specific hunk, ordered by creation time.
22073 pub fn comments_for_hunk<'a>(
22074 &'a self,
22075 key: &DiffHunkKey,
22076 snapshot: &MultiBufferSnapshot,
22077 ) -> &'a [StoredReviewComment] {
22078 let key_point = key.hunk_start_anchor.to_point(snapshot);
22079 self.stored_review_comments
22080 .iter()
22081 .find(|(k, _)| {
22082 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
22083 })
22084 .map(|(_, comments)| comments.as_slice())
22085 .unwrap_or(&[])
22086 }
22087
22088 /// Returns the total count of stored review comments across all hunks.
22089 pub fn total_review_comment_count(&self) -> usize {
22090 self.stored_review_comments
22091 .iter()
22092 .map(|(_, v)| v.len())
22093 .sum()
22094 }
22095
22096 /// Returns the count of comments for a specific hunk.
22097 pub fn hunk_comment_count(&self, key: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> usize {
22098 let key_point = key.hunk_start_anchor.to_point(snapshot);
22099 self.stored_review_comments
22100 .iter()
22101 .find(|(k, _)| {
22102 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
22103 })
22104 .map(|(_, v)| v.len())
22105 .unwrap_or(0)
22106 }
22107
22108 /// Adds a new review comment to a specific hunk.
22109 pub fn add_review_comment(
22110 &mut self,
22111 hunk_key: DiffHunkKey,
22112 comment: String,
22113 anchor_range: Range<Anchor>,
22114 cx: &mut Context<Self>,
22115 ) -> usize {
22116 let id = self.next_review_comment_id;
22117 self.next_review_comment_id += 1;
22118
22119 let stored_comment = StoredReviewComment::new(id, comment, anchor_range);
22120
22121 let snapshot = self.buffer.read(cx).snapshot(cx);
22122 let key_point = hunk_key.hunk_start_anchor.to_point(&snapshot);
22123
22124 // Find existing entry for this hunk or add a new one
22125 if let Some((_, comments)) = self.stored_review_comments.iter_mut().find(|(k, _)| {
22126 k.file_path == hunk_key.file_path
22127 && k.hunk_start_anchor.to_point(&snapshot) == key_point
22128 }) {
22129 comments.push(stored_comment);
22130 } else {
22131 self.stored_review_comments
22132 .push((hunk_key, vec![stored_comment]));
22133 }
22134
22135 cx.emit(EditorEvent::ReviewCommentsChanged {
22136 total_count: self.total_review_comment_count(),
22137 });
22138 cx.notify();
22139 id
22140 }
22141
22142 /// Removes a review comment by ID from any hunk.
22143 pub fn remove_review_comment(&mut self, id: usize, cx: &mut Context<Self>) -> bool {
22144 for (_, comments) in self.stored_review_comments.iter_mut() {
22145 if let Some(index) = comments.iter().position(|c| c.id == id) {
22146 comments.remove(index);
22147 cx.emit(EditorEvent::ReviewCommentsChanged {
22148 total_count: self.total_review_comment_count(),
22149 });
22150 cx.notify();
22151 return true;
22152 }
22153 }
22154 false
22155 }
22156
22157 /// Updates a review comment's text by ID.
22158 pub fn update_review_comment(
22159 &mut self,
22160 id: usize,
22161 new_comment: String,
22162 cx: &mut Context<Self>,
22163 ) -> bool {
22164 for (_, comments) in self.stored_review_comments.iter_mut() {
22165 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
22166 comment.comment = new_comment;
22167 comment.is_editing = false;
22168 cx.emit(EditorEvent::ReviewCommentsChanged {
22169 total_count: self.total_review_comment_count(),
22170 });
22171 cx.notify();
22172 return true;
22173 }
22174 }
22175 false
22176 }
22177
22178 /// Sets a comment's editing state.
22179 pub fn set_comment_editing(&mut self, id: usize, is_editing: bool, cx: &mut Context<Self>) {
22180 for (_, comments) in self.stored_review_comments.iter_mut() {
22181 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
22182 comment.is_editing = is_editing;
22183 cx.notify();
22184 return;
22185 }
22186 }
22187 }
22188
22189 /// Takes all stored comments from all hunks, clearing the storage.
22190 /// Returns a Vec of (hunk_key, comments) pairs.
22191 pub fn take_all_review_comments(
22192 &mut self,
22193 cx: &mut Context<Self>,
22194 ) -> Vec<(DiffHunkKey, Vec<StoredReviewComment>)> {
22195 // Dismiss all overlays when taking comments (e.g., when sending to agent)
22196 self.dismiss_all_diff_review_overlays(cx);
22197 let comments = std::mem::take(&mut self.stored_review_comments);
22198 // Reset the ID counter since all comments have been taken
22199 self.next_review_comment_id = 0;
22200 cx.emit(EditorEvent::ReviewCommentsChanged { total_count: 0 });
22201 cx.notify();
22202 comments
22203 }
22204
22205 /// Removes review comments whose anchors are no longer valid or whose
22206 /// associated diff hunks no longer exist.
22207 ///
22208 /// This should be called when the buffer changes to prevent orphaned comments
22209 /// from accumulating.
22210 pub fn cleanup_orphaned_review_comments(&mut self, cx: &mut Context<Self>) {
22211 let snapshot = self.buffer.read(cx).snapshot(cx);
22212 let original_count = self.total_review_comment_count();
22213
22214 // Remove comments with invalid hunk anchors
22215 self.stored_review_comments
22216 .retain(|(hunk_key, _)| hunk_key.hunk_start_anchor.is_valid(&snapshot));
22217
22218 // Also clean up individual comments with invalid anchor ranges
22219 for (_, comments) in &mut self.stored_review_comments {
22220 comments.retain(|comment| {
22221 comment.range.start.is_valid(&snapshot) && comment.range.end.is_valid(&snapshot)
22222 });
22223 }
22224
22225 // Remove empty hunk entries
22226 self.stored_review_comments
22227 .retain(|(_, comments)| !comments.is_empty());
22228
22229 let new_count = self.total_review_comment_count();
22230 if new_count != original_count {
22231 cx.emit(EditorEvent::ReviewCommentsChanged {
22232 total_count: new_count,
22233 });
22234 cx.notify();
22235 }
22236 }
22237
22238 /// Toggles the expanded state of the comments section in the overlay.
22239 pub fn toggle_review_comments_expanded(
22240 &mut self,
22241 _: &ToggleReviewCommentsExpanded,
22242 window: &mut Window,
22243 cx: &mut Context<Self>,
22244 ) {
22245 // Find the overlay that currently has focus, or use the first one
22246 let overlay_info = self.diff_review_overlays.iter_mut().find_map(|overlay| {
22247 if overlay.prompt_editor.focus_handle(cx).is_focused(window) {
22248 overlay.comments_expanded = !overlay.comments_expanded;
22249 Some(overlay.hunk_key.clone())
22250 } else {
22251 None
22252 }
22253 });
22254
22255 // If no focused overlay found, toggle the first one
22256 let hunk_key = overlay_info.or_else(|| {
22257 self.diff_review_overlays.first_mut().map(|overlay| {
22258 overlay.comments_expanded = !overlay.comments_expanded;
22259 overlay.hunk_key.clone()
22260 })
22261 });
22262
22263 if let Some(hunk_key) = hunk_key {
22264 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22265 cx.notify();
22266 }
22267 }
22268
22269 /// Handles the EditReviewComment action - sets a comment into editing mode.
22270 pub fn edit_review_comment(
22271 &mut self,
22272 action: &EditReviewComment,
22273 window: &mut Window,
22274 cx: &mut Context<Self>,
22275 ) {
22276 let comment_id = action.id;
22277
22278 // Set the comment to editing mode
22279 self.set_comment_editing(comment_id, true, cx);
22280
22281 // Find the overlay that contains this comment and create an inline editor if needed
22282 // First, find which hunk this comment belongs to
22283 let hunk_key = self
22284 .stored_review_comments
22285 .iter()
22286 .find_map(|(key, comments)| {
22287 if comments.iter().any(|c| c.id == comment_id) {
22288 Some(key.clone())
22289 } else {
22290 None
22291 }
22292 });
22293
22294 let snapshot = self.buffer.read(cx).snapshot(cx);
22295 if let Some(hunk_key) = hunk_key {
22296 if let Some(overlay) = self
22297 .diff_review_overlays
22298 .iter_mut()
22299 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22300 {
22301 if let std::collections::hash_map::Entry::Vacant(entry) =
22302 overlay.inline_edit_editors.entry(comment_id)
22303 {
22304 // Find the comment text
22305 let comment_text = self
22306 .stored_review_comments
22307 .iter()
22308 .flat_map(|(_, comments)| comments)
22309 .find(|c| c.id == comment_id)
22310 .map(|c| c.comment.clone())
22311 .unwrap_or_default();
22312
22313 // Create inline editor
22314 let parent_editor = cx.entity().downgrade();
22315 let inline_editor = cx.new(|cx| {
22316 let mut editor = Editor::single_line(window, cx);
22317 editor.set_text(&*comment_text, window, cx);
22318 // Select all text for easy replacement
22319 editor.select_all(&crate::actions::SelectAll, window, cx);
22320 editor
22321 });
22322
22323 // Register the Newline action to confirm the edit
22324 let subscription = inline_editor.update(cx, |inline_editor, _cx| {
22325 inline_editor.register_action({
22326 let parent_editor = parent_editor.clone();
22327 move |_: &crate::actions::Newline, window, cx| {
22328 if let Some(editor) = parent_editor.upgrade() {
22329 editor.update(cx, |editor, cx| {
22330 editor.confirm_edit_review_comment(comment_id, window, cx);
22331 });
22332 }
22333 }
22334 })
22335 });
22336
22337 // Store the subscription to keep the action handler alive
22338 overlay
22339 .inline_edit_subscriptions
22340 .insert(comment_id, subscription);
22341
22342 // Focus the inline editor
22343 let focus_handle = inline_editor.focus_handle(cx);
22344 window.focus(&focus_handle, cx);
22345
22346 entry.insert(inline_editor);
22347 }
22348 }
22349 }
22350
22351 cx.notify();
22352 }
22353
22354 /// Confirms an inline edit of a review comment.
22355 pub fn confirm_edit_review_comment(
22356 &mut self,
22357 comment_id: usize,
22358 _window: &mut Window,
22359 cx: &mut Context<Self>,
22360 ) {
22361 // Get the new text from the inline editor
22362 // Find the overlay containing this comment's inline editor
22363 let snapshot = self.buffer.read(cx).snapshot(cx);
22364 let hunk_key = self
22365 .stored_review_comments
22366 .iter()
22367 .find_map(|(key, comments)| {
22368 if comments.iter().any(|c| c.id == comment_id) {
22369 Some(key.clone())
22370 } else {
22371 None
22372 }
22373 });
22374
22375 let new_text = hunk_key
22376 .as_ref()
22377 .and_then(|hunk_key| {
22378 self.diff_review_overlays
22379 .iter()
22380 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
22381 })
22382 .as_ref()
22383 .and_then(|overlay| overlay.inline_edit_editors.get(&comment_id))
22384 .map(|editor| editor.read(cx).text(cx).trim().to_string());
22385
22386 if let Some(new_text) = new_text {
22387 if !new_text.is_empty() {
22388 self.update_review_comment(comment_id, new_text, cx);
22389 }
22390 }
22391
22392 // Remove the inline editor and its subscription
22393 if let Some(hunk_key) = hunk_key {
22394 if let Some(overlay) = self
22395 .diff_review_overlays
22396 .iter_mut()
22397 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22398 {
22399 overlay.inline_edit_editors.remove(&comment_id);
22400 overlay.inline_edit_subscriptions.remove(&comment_id);
22401 }
22402 }
22403
22404 // Clear editing state
22405 self.set_comment_editing(comment_id, false, cx);
22406 }
22407
22408 /// Cancels an inline edit of a review comment.
22409 pub fn cancel_edit_review_comment(
22410 &mut self,
22411 comment_id: usize,
22412 _window: &mut Window,
22413 cx: &mut Context<Self>,
22414 ) {
22415 // Find which hunk this comment belongs to
22416 let hunk_key = self
22417 .stored_review_comments
22418 .iter()
22419 .find_map(|(key, comments)| {
22420 if comments.iter().any(|c| c.id == comment_id) {
22421 Some(key.clone())
22422 } else {
22423 None
22424 }
22425 });
22426
22427 // Remove the inline editor and its subscription
22428 if let Some(hunk_key) = hunk_key {
22429 let snapshot = self.buffer.read(cx).snapshot(cx);
22430 if let Some(overlay) = self
22431 .diff_review_overlays
22432 .iter_mut()
22433 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22434 {
22435 overlay.inline_edit_editors.remove(&comment_id);
22436 overlay.inline_edit_subscriptions.remove(&comment_id);
22437 }
22438 }
22439
22440 // Clear editing state
22441 self.set_comment_editing(comment_id, false, cx);
22442 }
22443
22444 /// Action handler for ConfirmEditReviewComment.
22445 pub fn confirm_edit_review_comment_action(
22446 &mut self,
22447 action: &ConfirmEditReviewComment,
22448 window: &mut Window,
22449 cx: &mut Context<Self>,
22450 ) {
22451 self.confirm_edit_review_comment(action.id, window, cx);
22452 }
22453
22454 /// Action handler for CancelEditReviewComment.
22455 pub fn cancel_edit_review_comment_action(
22456 &mut self,
22457 action: &CancelEditReviewComment,
22458 window: &mut Window,
22459 cx: &mut Context<Self>,
22460 ) {
22461 self.cancel_edit_review_comment(action.id, window, cx);
22462 }
22463
22464 /// Handles the DeleteReviewComment action - removes a comment.
22465 pub fn delete_review_comment(
22466 &mut self,
22467 action: &DeleteReviewComment,
22468 window: &mut Window,
22469 cx: &mut Context<Self>,
22470 ) {
22471 // Get the hunk key before removing the comment
22472 // Find the hunk key from the comment itself
22473 let comment_id = action.id;
22474 let hunk_key = self
22475 .stored_review_comments
22476 .iter()
22477 .find_map(|(key, comments)| {
22478 if comments.iter().any(|c| c.id == comment_id) {
22479 Some(key.clone())
22480 } else {
22481 None
22482 }
22483 });
22484
22485 // Also get it from the overlay for refresh purposes
22486 let overlay_hunk_key = self
22487 .diff_review_overlays
22488 .first()
22489 .map(|o| o.hunk_key.clone());
22490
22491 self.remove_review_comment(action.id, cx);
22492
22493 // Refresh the overlay height after removing a comment
22494 if let Some(hunk_key) = hunk_key.or(overlay_hunk_key) {
22495 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22496 }
22497 }
22498
22499 fn render_diff_review_overlay(
22500 prompt_editor: &Entity<Editor>,
22501 hunk_key: &DiffHunkKey,
22502 editor_handle: &WeakEntity<Editor>,
22503 cx: &mut BlockContext,
22504 ) -> AnyElement {
22505 fn format_line_ranges(ranges: &[(u32, u32)]) -> Option<String> {
22506 if ranges.is_empty() {
22507 return None;
22508 }
22509 let formatted: Vec<String> = ranges
22510 .iter()
22511 .map(|(start, end)| {
22512 let start_line = start + 1;
22513 let end_line = end + 1;
22514 if start_line == end_line {
22515 format!("Line {start_line}")
22516 } else {
22517 format!("Lines {start_line}-{end_line}")
22518 }
22519 })
22520 .collect();
22521 // Don't show label for single line in single excerpt
22522 if ranges.len() == 1 && ranges[0].0 == ranges[0].1 {
22523 return None;
22524 }
22525 Some(formatted.join(" ⋯ "))
22526 }
22527
22528 let theme = cx.theme();
22529 let colors = theme.colors();
22530
22531 let (comments, comments_expanded, inline_editors, user_avatar_uri, line_ranges) =
22532 editor_handle
22533 .upgrade()
22534 .map(|editor| {
22535 let editor = editor.read(cx);
22536 let snapshot = editor.buffer().read(cx).snapshot(cx);
22537 let comments = editor.comments_for_hunk(hunk_key, &snapshot).to_vec();
22538 let (expanded, editors, avatar_uri, line_ranges) = editor
22539 .diff_review_overlays
22540 .iter()
22541 .find(|overlay| {
22542 Editor::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot)
22543 })
22544 .map(|o| {
22545 let start_point = o.anchor_range.start.to_point(&snapshot);
22546 let end_point = o.anchor_range.end.to_point(&snapshot);
22547 // Get line ranges per excerpt to detect discontinuities
22548 let buffer_ranges =
22549 snapshot.range_to_buffer_ranges(start_point..end_point);
22550 let ranges: Vec<(u32, u32)> = buffer_ranges
22551 .iter()
22552 .map(|(buffer, range, _)| {
22553 let start = buffer.offset_to_point(range.start.0).row;
22554 let end = buffer.offset_to_point(range.end.0).row;
22555 (start, end)
22556 })
22557 .collect();
22558 (
22559 o.comments_expanded,
22560 o.inline_edit_editors.clone(),
22561 o.user_avatar_uri.clone(),
22562 if ranges.is_empty() {
22563 None
22564 } else {
22565 Some(ranges)
22566 },
22567 )
22568 })
22569 .unwrap_or((true, HashMap::default(), None, None));
22570 (comments, expanded, editors, avatar_uri, line_ranges)
22571 })
22572 .unwrap_or((Vec::new(), true, HashMap::default(), None, None));
22573
22574 let comment_count = comments.len();
22575 let avatar_size = px(20.);
22576 let action_icon_size = IconSize::XSmall;
22577
22578 v_flex()
22579 .w_full()
22580 .bg(colors.editor_background)
22581 .border_b_1()
22582 .border_color(colors.border)
22583 .px_2()
22584 .pb_2()
22585 .gap_2()
22586 // Line range indicator (only shown for multi-line selections or multiple excerpts)
22587 .when_some(line_ranges, |el, ranges| {
22588 let label = format_line_ranges(&ranges);
22589 if let Some(label) = label {
22590 el.child(
22591 h_flex()
22592 .w_full()
22593 .px_2()
22594 .child(Label::new(label).size(LabelSize::Small).color(Color::Muted)),
22595 )
22596 } else {
22597 el
22598 }
22599 })
22600 // Top row: editable input with user's avatar
22601 .child(
22602 h_flex()
22603 .w_full()
22604 .items_center()
22605 .gap_2()
22606 .px_2()
22607 .py_1p5()
22608 .rounded_md()
22609 .bg(colors.surface_background)
22610 .child(
22611 div()
22612 .size(avatar_size)
22613 .flex_shrink_0()
22614 .rounded_full()
22615 .overflow_hidden()
22616 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22617 Avatar::new(avatar_uri.clone())
22618 .size(avatar_size)
22619 .into_any_element()
22620 } else {
22621 Icon::new(IconName::Person)
22622 .size(IconSize::Small)
22623 .color(ui::Color::Muted)
22624 .into_any_element()
22625 }),
22626 )
22627 .child(
22628 div()
22629 .flex_1()
22630 .border_1()
22631 .border_color(colors.border)
22632 .rounded_md()
22633 .bg(colors.editor_background)
22634 .px_2()
22635 .py_1()
22636 .child(prompt_editor.clone()),
22637 )
22638 .child(
22639 h_flex()
22640 .flex_shrink_0()
22641 .gap_1()
22642 .child(
22643 IconButton::new("diff-review-close", IconName::Close)
22644 .icon_color(ui::Color::Muted)
22645 .icon_size(action_icon_size)
22646 .tooltip(Tooltip::text("Close"))
22647 .on_click(|_, window, cx| {
22648 window
22649 .dispatch_action(Box::new(crate::actions::Cancel), cx);
22650 }),
22651 )
22652 .child(
22653 IconButton::new("diff-review-add", IconName::Return)
22654 .icon_color(ui::Color::Muted)
22655 .icon_size(action_icon_size)
22656 .tooltip(Tooltip::text("Add comment"))
22657 .on_click(|_, window, cx| {
22658 window.dispatch_action(
22659 Box::new(crate::actions::SubmitDiffReviewComment),
22660 cx,
22661 );
22662 }),
22663 ),
22664 ),
22665 )
22666 // Expandable comments section (only shown when there are comments)
22667 .when(comment_count > 0, |el| {
22668 el.child(Self::render_comments_section(
22669 comments,
22670 comments_expanded,
22671 inline_editors,
22672 user_avatar_uri,
22673 avatar_size,
22674 action_icon_size,
22675 colors,
22676 ))
22677 })
22678 .into_any_element()
22679 }
22680
22681 fn render_comments_section(
22682 comments: Vec<StoredReviewComment>,
22683 expanded: bool,
22684 inline_editors: HashMap<usize, Entity<Editor>>,
22685 user_avatar_uri: Option<SharedUri>,
22686 avatar_size: Pixels,
22687 action_icon_size: IconSize,
22688 colors: &theme::ThemeColors,
22689 ) -> impl IntoElement {
22690 let comment_count = comments.len();
22691
22692 v_flex()
22693 .w_full()
22694 .gap_1()
22695 // Header with expand/collapse toggle
22696 .child(
22697 h_flex()
22698 .id("review-comments-header")
22699 .w_full()
22700 .items_center()
22701 .gap_1()
22702 .px_2()
22703 .py_1()
22704 .cursor_pointer()
22705 .rounded_md()
22706 .hover(|style| style.bg(colors.ghost_element_hover))
22707 .on_click(|_, window: &mut Window, cx| {
22708 window.dispatch_action(
22709 Box::new(crate::actions::ToggleReviewCommentsExpanded),
22710 cx,
22711 );
22712 })
22713 .child(
22714 Icon::new(if expanded {
22715 IconName::ChevronDown
22716 } else {
22717 IconName::ChevronRight
22718 })
22719 .size(IconSize::Small)
22720 .color(ui::Color::Muted),
22721 )
22722 .child(
22723 Label::new(format!(
22724 "{} Comment{}",
22725 comment_count,
22726 if comment_count == 1 { "" } else { "s" }
22727 ))
22728 .size(LabelSize::Small)
22729 .color(Color::Muted),
22730 ),
22731 )
22732 // Comments list (when expanded)
22733 .when(expanded, |el| {
22734 el.children(comments.into_iter().map(|comment| {
22735 let inline_editor = inline_editors.get(&comment.id).cloned();
22736 Self::render_comment_row(
22737 comment,
22738 inline_editor,
22739 user_avatar_uri.clone(),
22740 avatar_size,
22741 action_icon_size,
22742 colors,
22743 )
22744 }))
22745 })
22746 }
22747
22748 fn render_comment_row(
22749 comment: StoredReviewComment,
22750 inline_editor: Option<Entity<Editor>>,
22751 user_avatar_uri: Option<SharedUri>,
22752 avatar_size: Pixels,
22753 action_icon_size: IconSize,
22754 colors: &theme::ThemeColors,
22755 ) -> impl IntoElement {
22756 let comment_id = comment.id;
22757 let is_editing = inline_editor.is_some();
22758
22759 h_flex()
22760 .w_full()
22761 .items_center()
22762 .gap_2()
22763 .px_2()
22764 .py_1p5()
22765 .rounded_md()
22766 .bg(colors.surface_background)
22767 .child(
22768 div()
22769 .size(avatar_size)
22770 .flex_shrink_0()
22771 .rounded_full()
22772 .overflow_hidden()
22773 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22774 Avatar::new(avatar_uri.clone())
22775 .size(avatar_size)
22776 .into_any_element()
22777 } else {
22778 Icon::new(IconName::Person)
22779 .size(IconSize::Small)
22780 .color(ui::Color::Muted)
22781 .into_any_element()
22782 }),
22783 )
22784 .child(if let Some(editor) = inline_editor {
22785 // Inline edit mode: show an editable text field
22786 div()
22787 .flex_1()
22788 .border_1()
22789 .border_color(colors.border)
22790 .rounded_md()
22791 .bg(colors.editor_background)
22792 .px_2()
22793 .py_1()
22794 .child(editor)
22795 .into_any_element()
22796 } else {
22797 // Display mode: show the comment text
22798 div()
22799 .flex_1()
22800 .text_sm()
22801 .text_color(colors.text)
22802 .child(comment.comment)
22803 .into_any_element()
22804 })
22805 .child(if is_editing {
22806 // Editing mode: show close and confirm buttons
22807 h_flex()
22808 .gap_1()
22809 .child(
22810 IconButton::new(
22811 format!("diff-review-cancel-edit-{comment_id}"),
22812 IconName::Close,
22813 )
22814 .icon_color(ui::Color::Muted)
22815 .icon_size(action_icon_size)
22816 .tooltip(Tooltip::text("Cancel"))
22817 .on_click(move |_, window, cx| {
22818 window.dispatch_action(
22819 Box::new(crate::actions::CancelEditReviewComment {
22820 id: comment_id,
22821 }),
22822 cx,
22823 );
22824 }),
22825 )
22826 .child(
22827 IconButton::new(
22828 format!("diff-review-confirm-edit-{comment_id}"),
22829 IconName::Return,
22830 )
22831 .icon_color(ui::Color::Muted)
22832 .icon_size(action_icon_size)
22833 .tooltip(Tooltip::text("Confirm"))
22834 .on_click(move |_, window, cx| {
22835 window.dispatch_action(
22836 Box::new(crate::actions::ConfirmEditReviewComment {
22837 id: comment_id,
22838 }),
22839 cx,
22840 );
22841 }),
22842 )
22843 .into_any_element()
22844 } else {
22845 // Display mode: no action buttons for now (edit/delete not yet implemented)
22846 gpui::Empty.into_any_element()
22847 })
22848 }
22849
22850 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
22851 if self.display_map.read(cx).masked != masked {
22852 self.display_map.update(cx, |map, _| map.masked = masked);
22853 }
22854 cx.notify()
22855 }
22856
22857 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
22858 self.show_wrap_guides = Some(show_wrap_guides);
22859 cx.notify();
22860 }
22861
22862 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
22863 self.show_indent_guides = Some(show_indent_guides);
22864 cx.notify();
22865 }
22866
22867 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
22868 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
22869 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
22870 && let Some(dir) = file.abs_path(cx).parent()
22871 {
22872 return Some(dir.to_owned());
22873 }
22874 }
22875
22876 None
22877 }
22878
22879 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
22880 self.active_excerpt(cx)?
22881 .1
22882 .read(cx)
22883 .file()
22884 .and_then(|f| f.as_local())
22885 }
22886
22887 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
22888 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22889 let buffer = buffer.read(cx);
22890 if let Some(project_path) = buffer.project_path(cx) {
22891 let project = self.project()?.read(cx);
22892 project.absolute_path(&project_path, cx)
22893 } else {
22894 buffer
22895 .file()
22896 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
22897 }
22898 })
22899 }
22900
22901 pub fn reveal_in_finder(
22902 &mut self,
22903 _: &RevealInFileManager,
22904 _window: &mut Window,
22905 cx: &mut Context<Self>,
22906 ) {
22907 if let Some(path) = self.target_file_abs_path(cx) {
22908 if let Some(project) = self.project() {
22909 project.update(cx, |project, cx| project.reveal_path(&path, cx));
22910 } else {
22911 cx.reveal_path(&path);
22912 }
22913 }
22914 }
22915
22916 pub fn copy_path(
22917 &mut self,
22918 _: &zed_actions::workspace::CopyPath,
22919 _window: &mut Window,
22920 cx: &mut Context<Self>,
22921 ) {
22922 if let Some(path) = self.target_file_abs_path(cx)
22923 && let Some(path) = path.to_str()
22924 {
22925 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22926 } else {
22927 cx.propagate();
22928 }
22929 }
22930
22931 pub fn copy_relative_path(
22932 &mut self,
22933 _: &zed_actions::workspace::CopyRelativePath,
22934 _window: &mut Window,
22935 cx: &mut Context<Self>,
22936 ) {
22937 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22938 let project = self.project()?.read(cx);
22939 let path = buffer.read(cx).file()?.path();
22940 let path = path.display(project.path_style(cx));
22941 Some(path)
22942 }) {
22943 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22944 } else {
22945 cx.propagate();
22946 }
22947 }
22948
22949 /// Returns the project path for the editor's buffer, if any buffer is
22950 /// opened in the editor.
22951 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
22952 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
22953 buffer.read(cx).project_path(cx)
22954 } else {
22955 None
22956 }
22957 }
22958
22959 // Returns true if the editor handled a go-to-line request
22960 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
22961 maybe!({
22962 let breakpoint_store = self.breakpoint_store.as_ref()?;
22963
22964 let (active_stack_frame, debug_line_pane_id) = {
22965 let store = breakpoint_store.read(cx);
22966 let active_stack_frame = store.active_position().cloned();
22967 let debug_line_pane_id = store.active_debug_line_pane_id();
22968 (active_stack_frame, debug_line_pane_id)
22969 };
22970
22971 let Some(active_stack_frame) = active_stack_frame else {
22972 self.clear_row_highlights::<ActiveDebugLine>();
22973 return None;
22974 };
22975
22976 if let Some(debug_line_pane_id) = debug_line_pane_id {
22977 if let Some(workspace) = self
22978 .workspace
22979 .as_ref()
22980 .and_then(|(workspace, _)| workspace.upgrade())
22981 {
22982 let editor_pane_id = workspace
22983 .read(cx)
22984 .pane_for_item_id(cx.entity_id())
22985 .map(|pane| pane.entity_id());
22986
22987 if editor_pane_id.is_some_and(|id| id != debug_line_pane_id) {
22988 self.clear_row_highlights::<ActiveDebugLine>();
22989 return None;
22990 }
22991 }
22992 }
22993
22994 let position = active_stack_frame.position;
22995 let buffer_id = position.buffer_id?;
22996 let snapshot = self
22997 .project
22998 .as_ref()?
22999 .read(cx)
23000 .buffer_for_id(buffer_id, cx)?
23001 .read(cx)
23002 .snapshot();
23003
23004 let mut handled = false;
23005 for (id, _, ExcerptRange { context, .. }) in
23006 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
23007 {
23008 if context.start.cmp(&position, &snapshot).is_ge()
23009 || context.end.cmp(&position, &snapshot).is_lt()
23010 {
23011 continue;
23012 }
23013 let snapshot = self.buffer.read(cx).snapshot(cx);
23014 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
23015
23016 handled = true;
23017 self.clear_row_highlights::<ActiveDebugLine>();
23018
23019 self.go_to_line::<ActiveDebugLine>(
23020 multibuffer_anchor,
23021 Some(cx.theme().colors().editor_debugger_active_line_background),
23022 window,
23023 cx,
23024 );
23025
23026 cx.notify();
23027 }
23028
23029 handled.then_some(())
23030 })
23031 .is_some()
23032 }
23033
23034 pub fn copy_file_name_without_extension(
23035 &mut self,
23036 _: &CopyFileNameWithoutExtension,
23037 _: &mut Window,
23038 cx: &mut Context<Self>,
23039 ) {
23040 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
23041 let file = buffer.read(cx).file()?;
23042 file.path().file_stem()
23043 }) {
23044 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
23045 }
23046 }
23047
23048 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
23049 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
23050 let file = buffer.read(cx).file()?;
23051 Some(file.file_name(cx))
23052 }) {
23053 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
23054 }
23055 }
23056
23057 pub fn toggle_git_blame(
23058 &mut self,
23059 _: &::git::Blame,
23060 window: &mut Window,
23061 cx: &mut Context<Self>,
23062 ) {
23063 self.show_git_blame_gutter = !self.show_git_blame_gutter;
23064
23065 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
23066 self.start_git_blame(true, window, cx);
23067 }
23068
23069 cx.notify();
23070 }
23071
23072 pub fn toggle_git_blame_inline(
23073 &mut self,
23074 _: &ToggleGitBlameInline,
23075 window: &mut Window,
23076 cx: &mut Context<Self>,
23077 ) {
23078 self.toggle_git_blame_inline_internal(true, window, cx);
23079 cx.notify();
23080 }
23081
23082 pub fn open_git_blame_commit(
23083 &mut self,
23084 _: &OpenGitBlameCommit,
23085 window: &mut Window,
23086 cx: &mut Context<Self>,
23087 ) {
23088 self.open_git_blame_commit_internal(window, cx);
23089 }
23090
23091 fn open_git_blame_commit_internal(
23092 &mut self,
23093 window: &mut Window,
23094 cx: &mut Context<Self>,
23095 ) -> Option<()> {
23096 let blame = self.blame.as_ref()?;
23097 let snapshot = self.snapshot(window, cx);
23098 let cursor = self
23099 .selections
23100 .newest::<Point>(&snapshot.display_snapshot)
23101 .head();
23102 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
23103 let (_, blame_entry) = blame
23104 .update(cx, |blame, cx| {
23105 blame
23106 .blame_for_rows(
23107 &[RowInfo {
23108 buffer_id: Some(buffer.remote_id()),
23109 buffer_row: Some(point.row),
23110 ..Default::default()
23111 }],
23112 cx,
23113 )
23114 .next()
23115 })
23116 .flatten()?;
23117 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23118 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
23119 let workspace = self.workspace()?.downgrade();
23120 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
23121 None
23122 }
23123
23124 pub fn git_blame_inline_enabled(&self) -> bool {
23125 self.git_blame_inline_enabled
23126 }
23127
23128 pub fn toggle_selection_menu(
23129 &mut self,
23130 _: &ToggleSelectionMenu,
23131 _: &mut Window,
23132 cx: &mut Context<Self>,
23133 ) {
23134 self.show_selection_menu = self
23135 .show_selection_menu
23136 .map(|show_selections_menu| !show_selections_menu)
23137 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
23138
23139 cx.notify();
23140 }
23141
23142 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
23143 self.show_selection_menu
23144 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
23145 }
23146
23147 fn start_git_blame(
23148 &mut self,
23149 user_triggered: bool,
23150 window: &mut Window,
23151 cx: &mut Context<Self>,
23152 ) {
23153 if let Some(project) = self.project() {
23154 if let Some(buffer) = self.buffer().read(cx).as_singleton()
23155 && buffer.read(cx).file().is_none()
23156 {
23157 return;
23158 }
23159
23160 let focused = self.focus_handle(cx).contains_focused(window, cx);
23161
23162 let project = project.clone();
23163 let blame = cx
23164 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
23165 self.blame_subscription =
23166 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
23167 self.blame = Some(blame);
23168 }
23169 }
23170
23171 fn toggle_git_blame_inline_internal(
23172 &mut self,
23173 user_triggered: bool,
23174 window: &mut Window,
23175 cx: &mut Context<Self>,
23176 ) {
23177 if self.git_blame_inline_enabled {
23178 self.git_blame_inline_enabled = false;
23179 self.show_git_blame_inline = false;
23180 self.show_git_blame_inline_delay_task.take();
23181 } else {
23182 self.git_blame_inline_enabled = true;
23183 self.start_git_blame_inline(user_triggered, window, cx);
23184 }
23185
23186 cx.notify();
23187 }
23188
23189 fn start_git_blame_inline(
23190 &mut self,
23191 user_triggered: bool,
23192 window: &mut Window,
23193 cx: &mut Context<Self>,
23194 ) {
23195 self.start_git_blame(user_triggered, window, cx);
23196
23197 if ProjectSettings::get_global(cx)
23198 .git
23199 .inline_blame_delay()
23200 .is_some()
23201 {
23202 self.start_inline_blame_timer(window, cx);
23203 } else {
23204 self.show_git_blame_inline = true
23205 }
23206 }
23207
23208 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
23209 self.blame.as_ref()
23210 }
23211
23212 pub fn show_git_blame_gutter(&self) -> bool {
23213 self.show_git_blame_gutter
23214 }
23215
23216 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
23217 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
23218 }
23219
23220 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
23221 self.show_git_blame_inline
23222 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
23223 && !self.newest_selection_head_on_empty_line(cx)
23224 && self.has_blame_entries(cx)
23225 }
23226
23227 fn has_blame_entries(&self, cx: &App) -> bool {
23228 self.blame()
23229 .is_some_and(|blame| blame.read(cx).has_generated_entries())
23230 }
23231
23232 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
23233 let cursor_anchor = self.selections.newest_anchor().head();
23234
23235 let snapshot = self.buffer.read(cx).snapshot(cx);
23236 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
23237
23238 snapshot.line_len(buffer_row) == 0
23239 }
23240
23241 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
23242 let buffer_and_selection = maybe!({
23243 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
23244 let selection_range = selection.range();
23245
23246 let multi_buffer = self.buffer().read(cx);
23247 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
23248 let buffer_ranges = multi_buffer_snapshot
23249 .range_to_buffer_ranges(selection_range.start..=selection_range.end);
23250
23251 let (buffer, range, _) = if selection.reversed {
23252 buffer_ranges.first()
23253 } else {
23254 buffer_ranges.last()
23255 }?;
23256
23257 let buffer_range = range.to_point(buffer);
23258
23259 let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else {
23260 return Some((
23261 multi_buffer.buffer(buffer.remote_id()).unwrap(),
23262 buffer_range.start.row..buffer_range.end.row,
23263 ));
23264 };
23265
23266 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
23267 let start =
23268 buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.start, buffer);
23269 let end =
23270 buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.end, buffer);
23271
23272 Some((
23273 multi_buffer.buffer(buffer.remote_id()).unwrap(),
23274 start.row..end.row,
23275 ))
23276 });
23277
23278 let Some((buffer, selection)) = buffer_and_selection else {
23279 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
23280 };
23281
23282 let Some(project) = self.project() else {
23283 return Task::ready(Err(anyhow!("editor does not have project")));
23284 };
23285
23286 project.update(cx, |project, cx| {
23287 project.get_permalink_to_line(&buffer, selection, cx)
23288 })
23289 }
23290
23291 pub fn copy_permalink_to_line(
23292 &mut self,
23293 _: &CopyPermalinkToLine,
23294 window: &mut Window,
23295 cx: &mut Context<Self>,
23296 ) {
23297 let permalink_task = self.get_permalink_to_line(cx);
23298 let workspace = self.workspace();
23299
23300 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
23301 Ok(permalink) => {
23302 cx.update(|_, cx| {
23303 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
23304 })
23305 .ok();
23306 }
23307 Err(err) => {
23308 let message = format!("Failed to copy permalink: {err}");
23309
23310 anyhow::Result::<()>::Err(err).log_err();
23311
23312 if let Some(workspace) = workspace {
23313 workspace
23314 .update_in(cx, |workspace, _, cx| {
23315 struct CopyPermalinkToLine;
23316
23317 workspace.show_toast(
23318 Toast::new(
23319 NotificationId::unique::<CopyPermalinkToLine>(),
23320 message,
23321 ),
23322 cx,
23323 )
23324 })
23325 .ok();
23326 }
23327 }
23328 })
23329 .detach();
23330 }
23331
23332 pub fn copy_file_location(
23333 &mut self,
23334 _: &CopyFileLocation,
23335 _: &mut Window,
23336 cx: &mut Context<Self>,
23337 ) {
23338 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
23339
23340 let start_line = selection.start.row + 1;
23341 let end_line = selection.end.row + 1;
23342
23343 let end_line = if selection.end.column == 0 && end_line > start_line {
23344 end_line - 1
23345 } else {
23346 end_line
23347 };
23348
23349 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
23350 let project = self.project()?.read(cx);
23351 let file = buffer.read(cx).file()?;
23352 let path = file.path().display(project.path_style(cx));
23353
23354 let location = if start_line == end_line {
23355 format!("{path}:{start_line}")
23356 } else {
23357 format!("{path}:{start_line}-{end_line}")
23358 };
23359 Some(location)
23360 }) {
23361 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
23362 }
23363 }
23364
23365 pub fn open_permalink_to_line(
23366 &mut self,
23367 _: &OpenPermalinkToLine,
23368 window: &mut Window,
23369 cx: &mut Context<Self>,
23370 ) {
23371 let permalink_task = self.get_permalink_to_line(cx);
23372 let workspace = self.workspace();
23373
23374 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
23375 Ok(permalink) => {
23376 cx.update(|_, cx| {
23377 cx.open_url(permalink.as_ref());
23378 })
23379 .ok();
23380 }
23381 Err(err) => {
23382 let message = format!("Failed to open permalink: {err}");
23383
23384 anyhow::Result::<()>::Err(err).log_err();
23385
23386 if let Some(workspace) = workspace {
23387 workspace.update(cx, |workspace, cx| {
23388 struct OpenPermalinkToLine;
23389
23390 workspace.show_toast(
23391 Toast::new(NotificationId::unique::<OpenPermalinkToLine>(), message),
23392 cx,
23393 )
23394 });
23395 }
23396 }
23397 })
23398 .detach();
23399 }
23400
23401 pub fn insert_uuid_v4(
23402 &mut self,
23403 _: &InsertUuidV4,
23404 window: &mut Window,
23405 cx: &mut Context<Self>,
23406 ) {
23407 self.insert_uuid(UuidVersion::V4, window, cx);
23408 }
23409
23410 pub fn insert_uuid_v7(
23411 &mut self,
23412 _: &InsertUuidV7,
23413 window: &mut Window,
23414 cx: &mut Context<Self>,
23415 ) {
23416 self.insert_uuid(UuidVersion::V7, window, cx);
23417 }
23418
23419 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
23420 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
23421 self.transact(window, cx, |this, window, cx| {
23422 let edits = this
23423 .selections
23424 .all::<Point>(&this.display_snapshot(cx))
23425 .into_iter()
23426 .map(|selection| {
23427 let uuid = match version {
23428 UuidVersion::V4 => uuid::Uuid::new_v4(),
23429 UuidVersion::V7 => uuid::Uuid::now_v7(),
23430 };
23431
23432 (selection.range(), uuid.to_string())
23433 });
23434 this.edit(edits, cx);
23435 this.refresh_edit_prediction(true, false, window, cx);
23436 });
23437 }
23438
23439 pub fn open_selections_in_multibuffer(
23440 &mut self,
23441 _: &OpenSelectionsInMultibuffer,
23442 window: &mut Window,
23443 cx: &mut Context<Self>,
23444 ) {
23445 let multibuffer = self.buffer.read(cx);
23446
23447 let Some(buffer) = multibuffer.as_singleton() else {
23448 return;
23449 };
23450
23451 let Some(workspace) = self.workspace() else {
23452 return;
23453 };
23454
23455 let title = multibuffer.title(cx).to_string();
23456
23457 let locations = self
23458 .selections
23459 .all_anchors(&self.display_snapshot(cx))
23460 .iter()
23461 .map(|selection| {
23462 (
23463 buffer.clone(),
23464 (selection.start.text_anchor..selection.end.text_anchor)
23465 .to_point(buffer.read(cx)),
23466 )
23467 })
23468 .into_group_map();
23469
23470 cx.spawn_in(window, async move |_, cx| {
23471 workspace.update_in(cx, |workspace, window, cx| {
23472 Self::open_locations_in_multibuffer(
23473 workspace,
23474 locations,
23475 format!("Selections for '{title}'"),
23476 false,
23477 false,
23478 MultibufferSelectionMode::All,
23479 window,
23480 cx,
23481 );
23482 })
23483 })
23484 .detach();
23485 }
23486
23487 /// Adds a row highlight for the given range. If a row has multiple highlights, the
23488 /// last highlight added will be used.
23489 ///
23490 /// If the range ends at the beginning of a line, then that line will not be highlighted.
23491 pub fn highlight_rows<T: 'static>(
23492 &mut self,
23493 range: Range<Anchor>,
23494 color: Hsla,
23495 options: RowHighlightOptions,
23496 cx: &mut Context<Self>,
23497 ) {
23498 let snapshot = self.buffer().read(cx).snapshot(cx);
23499 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23500 let ix = row_highlights.binary_search_by(|highlight| {
23501 Ordering::Equal
23502 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
23503 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
23504 });
23505
23506 if let Err(mut ix) = ix {
23507 let index = post_inc(&mut self.highlight_order);
23508
23509 // If this range intersects with the preceding highlight, then merge it with
23510 // the preceding highlight. Otherwise insert a new highlight.
23511 let mut merged = false;
23512 if ix > 0 {
23513 let prev_highlight = &mut row_highlights[ix - 1];
23514 if prev_highlight
23515 .range
23516 .end
23517 .cmp(&range.start, &snapshot)
23518 .is_ge()
23519 {
23520 ix -= 1;
23521 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
23522 prev_highlight.range.end = range.end;
23523 }
23524 merged = true;
23525 prev_highlight.index = index;
23526 prev_highlight.color = color;
23527 prev_highlight.options = options;
23528 }
23529 }
23530
23531 if !merged {
23532 row_highlights.insert(
23533 ix,
23534 RowHighlight {
23535 range,
23536 index,
23537 color,
23538 options,
23539 type_id: TypeId::of::<T>(),
23540 },
23541 );
23542 }
23543
23544 // If any of the following highlights intersect with this one, merge them.
23545 while let Some(next_highlight) = row_highlights.get(ix + 1) {
23546 let highlight = &row_highlights[ix];
23547 if next_highlight
23548 .range
23549 .start
23550 .cmp(&highlight.range.end, &snapshot)
23551 .is_le()
23552 {
23553 if next_highlight
23554 .range
23555 .end
23556 .cmp(&highlight.range.end, &snapshot)
23557 .is_gt()
23558 {
23559 row_highlights[ix].range.end = next_highlight.range.end;
23560 }
23561 row_highlights.remove(ix + 1);
23562 } else {
23563 break;
23564 }
23565 }
23566 }
23567 }
23568
23569 /// Remove any highlighted row ranges of the given type that intersect the
23570 /// given ranges.
23571 pub fn remove_highlighted_rows<T: 'static>(
23572 &mut self,
23573 ranges_to_remove: Vec<Range<Anchor>>,
23574 cx: &mut Context<Self>,
23575 ) {
23576 let snapshot = self.buffer().read(cx).snapshot(cx);
23577 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23578 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23579 row_highlights.retain(|highlight| {
23580 while let Some(range_to_remove) = ranges_to_remove.peek() {
23581 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
23582 Ordering::Less | Ordering::Equal => {
23583 ranges_to_remove.next();
23584 }
23585 Ordering::Greater => {
23586 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
23587 Ordering::Less | Ordering::Equal => {
23588 return false;
23589 }
23590 Ordering::Greater => break,
23591 }
23592 }
23593 }
23594 }
23595
23596 true
23597 })
23598 }
23599
23600 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
23601 pub fn clear_row_highlights<T: 'static>(&mut self) {
23602 self.highlighted_rows.remove(&TypeId::of::<T>());
23603 }
23604
23605 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
23606 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
23607 self.highlighted_rows
23608 .get(&TypeId::of::<T>())
23609 .map_or(&[] as &[_], |vec| vec.as_slice())
23610 .iter()
23611 .map(|highlight| (highlight.range.clone(), highlight.color))
23612 }
23613
23614 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
23615 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
23616 /// Allows to ignore certain kinds of highlights.
23617 pub fn highlighted_display_rows(
23618 &self,
23619 window: &mut Window,
23620 cx: &mut App,
23621 ) -> BTreeMap<DisplayRow, LineHighlight> {
23622 let snapshot = self.snapshot(window, cx);
23623 let mut used_highlight_orders = HashMap::default();
23624 self.highlighted_rows
23625 .iter()
23626 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
23627 .fold(
23628 BTreeMap::<DisplayRow, LineHighlight>::new(),
23629 |mut unique_rows, highlight| {
23630 let start = highlight.range.start.to_display_point(&snapshot);
23631 let end = highlight.range.end.to_display_point(&snapshot);
23632 let start_row = start.row().0;
23633 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
23634 {
23635 end.row().0.saturating_sub(1)
23636 } else {
23637 end.row().0
23638 };
23639 for row in start_row..=end_row {
23640 let used_index =
23641 used_highlight_orders.entry(row).or_insert(highlight.index);
23642 if highlight.index >= *used_index {
23643 *used_index = highlight.index;
23644 unique_rows.insert(
23645 DisplayRow(row),
23646 LineHighlight {
23647 include_gutter: highlight.options.include_gutter,
23648 border: None,
23649 background: highlight.color.into(),
23650 type_id: Some(highlight.type_id),
23651 },
23652 );
23653 }
23654 }
23655 unique_rows
23656 },
23657 )
23658 }
23659
23660 pub fn highlighted_display_row_for_autoscroll(
23661 &self,
23662 snapshot: &DisplaySnapshot,
23663 ) -> Option<DisplayRow> {
23664 self.highlighted_rows
23665 .values()
23666 .flat_map(|highlighted_rows| highlighted_rows.iter())
23667 .filter_map(|highlight| {
23668 if highlight.options.autoscroll {
23669 Some(highlight.range.start.to_display_point(snapshot).row())
23670 } else {
23671 None
23672 }
23673 })
23674 .min()
23675 }
23676
23677 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
23678 self.highlight_background(
23679 HighlightKey::SearchWithinRange,
23680 ranges,
23681 |_, colors| colors.colors().editor_document_highlight_read_background,
23682 cx,
23683 )
23684 }
23685
23686 pub fn set_breadcrumb_header(&mut self, new_header: String) {
23687 self.breadcrumb_header = Some(new_header);
23688 }
23689
23690 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
23691 self.clear_background_highlights(HighlightKey::SearchWithinRange, cx);
23692 }
23693
23694 pub fn highlight_background(
23695 &mut self,
23696 key: HighlightKey,
23697 ranges: &[Range<Anchor>],
23698 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
23699 cx: &mut Context<Self>,
23700 ) {
23701 self.background_highlights
23702 .insert(key, (Arc::new(color_fetcher), Arc::from(ranges)));
23703 self.scrollbar_marker_state.dirty = true;
23704 cx.notify();
23705 }
23706
23707 pub fn clear_background_highlights(
23708 &mut self,
23709 key: HighlightKey,
23710 cx: &mut Context<Self>,
23711 ) -> Option<BackgroundHighlight> {
23712 let text_highlights = self.background_highlights.remove(&key)?;
23713 if !text_highlights.1.is_empty() {
23714 self.scrollbar_marker_state.dirty = true;
23715 cx.notify();
23716 }
23717 Some(text_highlights)
23718 }
23719
23720 pub fn highlight_gutter<T: 'static>(
23721 &mut self,
23722 ranges: impl Into<Vec<Range<Anchor>>>,
23723 color_fetcher: fn(&App) -> Hsla,
23724 cx: &mut Context<Self>,
23725 ) {
23726 self.gutter_highlights
23727 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
23728 cx.notify();
23729 }
23730
23731 pub fn clear_gutter_highlights<T: 'static>(
23732 &mut self,
23733 cx: &mut Context<Self>,
23734 ) -> Option<GutterHighlight> {
23735 cx.notify();
23736 self.gutter_highlights.remove(&TypeId::of::<T>())
23737 }
23738
23739 pub fn insert_gutter_highlight<T: 'static>(
23740 &mut self,
23741 range: Range<Anchor>,
23742 color_fetcher: fn(&App) -> Hsla,
23743 cx: &mut Context<Self>,
23744 ) {
23745 let snapshot = self.buffer().read(cx).snapshot(cx);
23746 let mut highlights = self
23747 .gutter_highlights
23748 .remove(&TypeId::of::<T>())
23749 .map(|(_, highlights)| highlights)
23750 .unwrap_or_default();
23751 let ix = highlights.binary_search_by(|highlight| {
23752 Ordering::Equal
23753 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
23754 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
23755 });
23756 if let Err(ix) = ix {
23757 highlights.insert(ix, range);
23758 }
23759 self.gutter_highlights
23760 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
23761 }
23762
23763 pub fn remove_gutter_highlights<T: 'static>(
23764 &mut self,
23765 ranges_to_remove: Vec<Range<Anchor>>,
23766 cx: &mut Context<Self>,
23767 ) {
23768 let snapshot = self.buffer().read(cx).snapshot(cx);
23769 let Some((color_fetcher, mut gutter_highlights)) =
23770 self.gutter_highlights.remove(&TypeId::of::<T>())
23771 else {
23772 return;
23773 };
23774 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23775 gutter_highlights.retain(|highlight| {
23776 while let Some(range_to_remove) = ranges_to_remove.peek() {
23777 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
23778 Ordering::Less | Ordering::Equal => {
23779 ranges_to_remove.next();
23780 }
23781 Ordering::Greater => {
23782 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
23783 Ordering::Less | Ordering::Equal => {
23784 return false;
23785 }
23786 Ordering::Greater => break,
23787 }
23788 }
23789 }
23790 }
23791
23792 true
23793 });
23794 self.gutter_highlights
23795 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
23796 }
23797
23798 #[cfg(any(test, feature = "test-support"))]
23799 pub fn all_text_highlights(
23800 &self,
23801 window: &mut Window,
23802 cx: &mut Context<Self>,
23803 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
23804 let snapshot = self.snapshot(window, cx);
23805 self.display_map.update(cx, |display_map, _| {
23806 display_map
23807 .all_text_highlights()
23808 .map(|(_, highlight)| {
23809 let (style, ranges) = highlight.as_ref();
23810 (
23811 *style,
23812 ranges
23813 .iter()
23814 .map(|range| range.clone().to_display_points(&snapshot))
23815 .collect(),
23816 )
23817 })
23818 .collect()
23819 })
23820 }
23821
23822 #[cfg(any(test, feature = "test-support"))]
23823 pub fn all_text_background_highlights(
23824 &self,
23825 window: &mut Window,
23826 cx: &mut Context<Self>,
23827 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23828 let snapshot = self.snapshot(window, cx);
23829 let buffer = &snapshot.buffer_snapshot();
23830 let start = buffer.anchor_before(MultiBufferOffset(0));
23831 let end = buffer.anchor_after(buffer.len());
23832 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
23833 }
23834
23835 #[cfg(any(test, feature = "test-support"))]
23836 pub fn sorted_background_highlights_in_range(
23837 &self,
23838 search_range: Range<Anchor>,
23839 display_snapshot: &DisplaySnapshot,
23840 theme: &Theme,
23841 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23842 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
23843 res.sort_by(|a, b| {
23844 a.0.start
23845 .cmp(&b.0.start)
23846 .then_with(|| a.0.end.cmp(&b.0.end))
23847 .then_with(|| a.1.cmp(&b.1))
23848 });
23849 res
23850 }
23851
23852 #[cfg(any(test, feature = "test-support"))]
23853 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
23854 let snapshot = self.buffer().read(cx).snapshot(cx);
23855
23856 let highlights = self
23857 .background_highlights
23858 .get(&HighlightKey::BufferSearchHighlights);
23859
23860 if let Some((_color, ranges)) = highlights {
23861 ranges
23862 .iter()
23863 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
23864 .collect_vec()
23865 } else {
23866 vec![]
23867 }
23868 }
23869
23870 fn document_highlights_for_position<'a>(
23871 &'a self,
23872 position: Anchor,
23873 buffer: &'a MultiBufferSnapshot,
23874 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
23875 let read_highlights = self
23876 .background_highlights
23877 .get(&HighlightKey::DocumentHighlightRead)
23878 .map(|h| &h.1);
23879 let write_highlights = self
23880 .background_highlights
23881 .get(&HighlightKey::DocumentHighlightWrite)
23882 .map(|h| &h.1);
23883 let left_position = position.bias_left(buffer);
23884 let right_position = position.bias_right(buffer);
23885 read_highlights
23886 .into_iter()
23887 .chain(write_highlights)
23888 .flat_map(move |ranges| {
23889 let start_ix = match ranges.binary_search_by(|probe| {
23890 let cmp = probe.end.cmp(&left_position, buffer);
23891 if cmp.is_ge() {
23892 Ordering::Greater
23893 } else {
23894 Ordering::Less
23895 }
23896 }) {
23897 Ok(i) | Err(i) => i,
23898 };
23899
23900 ranges[start_ix..]
23901 .iter()
23902 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
23903 })
23904 }
23905
23906 pub fn has_background_highlights(&self, key: HighlightKey) -> bool {
23907 self.background_highlights
23908 .get(&key)
23909 .is_some_and(|(_, highlights)| !highlights.is_empty())
23910 }
23911
23912 /// Returns all background highlights for a given range.
23913 ///
23914 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
23915 pub fn background_highlights_in_range(
23916 &self,
23917 search_range: Range<Anchor>,
23918 display_snapshot: &DisplaySnapshot,
23919 theme: &Theme,
23920 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23921 let mut results = Vec::new();
23922 for (color_fetcher, ranges) in self.background_highlights.values() {
23923 let start_ix = match ranges.binary_search_by(|probe| {
23924 let cmp = probe
23925 .end
23926 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23927 if cmp.is_gt() {
23928 Ordering::Greater
23929 } else {
23930 Ordering::Less
23931 }
23932 }) {
23933 Ok(i) | Err(i) => i,
23934 };
23935 for (index, range) in ranges[start_ix..].iter().enumerate() {
23936 if range
23937 .start
23938 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23939 .is_ge()
23940 {
23941 break;
23942 }
23943
23944 let color = color_fetcher(&(start_ix + index), theme);
23945 let start = range.start.to_display_point(display_snapshot);
23946 let end = range.end.to_display_point(display_snapshot);
23947 results.push((start..end, color))
23948 }
23949 }
23950 results
23951 }
23952
23953 pub fn gutter_highlights_in_range(
23954 &self,
23955 search_range: Range<Anchor>,
23956 display_snapshot: &DisplaySnapshot,
23957 cx: &App,
23958 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23959 let mut results = Vec::new();
23960 for (color_fetcher, ranges) in self.gutter_highlights.values() {
23961 let color = color_fetcher(cx);
23962 let start_ix = match ranges.binary_search_by(|probe| {
23963 let cmp = probe
23964 .end
23965 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23966 if cmp.is_gt() {
23967 Ordering::Greater
23968 } else {
23969 Ordering::Less
23970 }
23971 }) {
23972 Ok(i) | Err(i) => i,
23973 };
23974 for range in &ranges[start_ix..] {
23975 if range
23976 .start
23977 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23978 .is_ge()
23979 {
23980 break;
23981 }
23982
23983 let start = range.start.to_display_point(display_snapshot);
23984 let end = range.end.to_display_point(display_snapshot);
23985 results.push((start..end, color))
23986 }
23987 }
23988 results
23989 }
23990
23991 /// Get the text ranges corresponding to the redaction query
23992 pub fn redacted_ranges(
23993 &self,
23994 search_range: Range<Anchor>,
23995 display_snapshot: &DisplaySnapshot,
23996 cx: &App,
23997 ) -> Vec<Range<DisplayPoint>> {
23998 display_snapshot
23999 .buffer_snapshot()
24000 .redacted_ranges(search_range, |file| {
24001 if let Some(file) = file {
24002 file.is_private()
24003 && EditorSettings::get(
24004 Some(SettingsLocation {
24005 worktree_id: file.worktree_id(cx),
24006 path: file.path().as_ref(),
24007 }),
24008 cx,
24009 )
24010 .redact_private_values
24011 } else {
24012 false
24013 }
24014 })
24015 .map(|range| {
24016 range.start.to_display_point(display_snapshot)
24017 ..range.end.to_display_point(display_snapshot)
24018 })
24019 .collect()
24020 }
24021
24022 pub fn highlight_text_key(
24023 &mut self,
24024 key: HighlightKey,
24025 ranges: Vec<Range<Anchor>>,
24026 style: HighlightStyle,
24027 merge: bool,
24028 cx: &mut Context<Self>,
24029 ) {
24030 self.display_map.update(cx, |map, cx| {
24031 map.highlight_text(key, ranges, style, merge, cx);
24032 });
24033 cx.notify();
24034 }
24035
24036 pub fn highlight_text(
24037 &mut self,
24038 key: HighlightKey,
24039 ranges: Vec<Range<Anchor>>,
24040 style: HighlightStyle,
24041 cx: &mut Context<Self>,
24042 ) {
24043 self.display_map.update(cx, |map, cx| {
24044 map.highlight_text(key, ranges, style, false, cx)
24045 });
24046 cx.notify();
24047 }
24048
24049 pub fn text_highlights<'a>(
24050 &'a self,
24051 key: HighlightKey,
24052 cx: &'a App,
24053 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
24054 self.display_map.read(cx).text_highlights(key)
24055 }
24056
24057 pub fn clear_highlights(&mut self, key: HighlightKey, cx: &mut Context<Self>) {
24058 let cleared = self
24059 .display_map
24060 .update(cx, |map, _| map.clear_highlights(key));
24061 if cleared {
24062 cx.notify();
24063 }
24064 }
24065
24066 pub fn clear_highlights_with(
24067 &mut self,
24068 f: &mut dyn FnMut(&HighlightKey) -> bool,
24069 cx: &mut Context<Self>,
24070 ) {
24071 let cleared = self
24072 .display_map
24073 .update(cx, |map, _| map.clear_highlights_with(f));
24074 if cleared {
24075 cx.notify();
24076 }
24077 }
24078
24079 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
24080 (self.read_only(cx) || self.blink_manager.read(cx).visible())
24081 && self.focus_handle.is_focused(window)
24082 }
24083
24084 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
24085 self.show_cursor_when_unfocused = is_enabled;
24086 cx.notify();
24087 }
24088
24089 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
24090 cx.notify();
24091 }
24092
24093 fn on_debug_session_event(
24094 &mut self,
24095 _session: Entity<Session>,
24096 event: &SessionEvent,
24097 cx: &mut Context<Self>,
24098 ) {
24099 if let SessionEvent::InvalidateInlineValue = event {
24100 self.refresh_inline_values(cx);
24101 }
24102 }
24103
24104 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
24105 let Some(semantics) = self.semantics_provider.clone() else {
24106 return;
24107 };
24108
24109 if !self.inline_value_cache.enabled {
24110 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
24111 self.splice_inlays(&inlays, Vec::new(), cx);
24112 return;
24113 }
24114
24115 let current_execution_position = self
24116 .highlighted_rows
24117 .get(&TypeId::of::<ActiveDebugLine>())
24118 .and_then(|lines| lines.last().map(|line| line.range.end));
24119
24120 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
24121 let inline_values = editor
24122 .update(cx, |editor, cx| {
24123 let Some(current_execution_position) = current_execution_position else {
24124 return Some(Task::ready(Ok(Vec::new())));
24125 };
24126
24127 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
24128 let snapshot = buffer.snapshot(cx);
24129
24130 let excerpt = snapshot.excerpt_containing(
24131 current_execution_position..current_execution_position,
24132 )?;
24133
24134 editor.buffer.read(cx).buffer(excerpt.buffer_id())
24135 })?;
24136
24137 if current_execution_position
24138 .text_anchor
24139 .buffer_id
24140 .is_some_and(|id| id != buffer.read(cx).remote_id())
24141 {
24142 return Some(Task::ready(Ok(Vec::new())));
24143 }
24144
24145 let range =
24146 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
24147
24148 semantics.inline_values(buffer, range, cx)
24149 })
24150 .ok()
24151 .flatten()?
24152 .await
24153 .context("refreshing debugger inlays")
24154 .log_err()?;
24155
24156 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
24157
24158 for (buffer_id, inline_value) in inline_values
24159 .into_iter()
24160 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
24161 {
24162 buffer_inline_values
24163 .entry(buffer_id)
24164 .or_default()
24165 .push(inline_value);
24166 }
24167
24168 editor
24169 .update(cx, |editor, cx| {
24170 let snapshot = editor.buffer.read(cx).snapshot(cx);
24171 let mut new_inlays = Vec::default();
24172
24173 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
24174 let buffer_id = buffer_snapshot.remote_id();
24175 buffer_inline_values
24176 .get(&buffer_id)
24177 .into_iter()
24178 .flatten()
24179 .for_each(|hint| {
24180 let inlay = Inlay::debugger(
24181 post_inc(&mut editor.next_inlay_id),
24182 Anchor::in_buffer(excerpt_id, hint.position),
24183 hint.text(),
24184 );
24185 if !inlay.text().chars().contains(&'\n') {
24186 new_inlays.push(inlay);
24187 }
24188 });
24189 }
24190
24191 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
24192 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
24193
24194 editor.splice_inlays(&inlay_ids, new_inlays, cx);
24195 })
24196 .ok()?;
24197 Some(())
24198 });
24199 }
24200
24201 fn on_buffer_event(
24202 &mut self,
24203 multibuffer: &Entity<MultiBuffer>,
24204 event: &multi_buffer::Event,
24205 window: &mut Window,
24206 cx: &mut Context<Self>,
24207 ) {
24208 match event {
24209 multi_buffer::Event::Edited {
24210 edited_buffer,
24211 is_local,
24212 } => {
24213 self.scrollbar_marker_state.dirty = true;
24214 self.active_indent_guides_state.dirty = true;
24215 self.refresh_active_diagnostics(cx);
24216 self.refresh_code_actions(window, cx);
24217 self.refresh_single_line_folds(window, cx);
24218 let snapshot = self.snapshot(window, cx);
24219 self.refresh_matching_bracket_highlights(&snapshot, cx);
24220 self.refresh_outline_symbols_at_cursor(cx);
24221 self.refresh_sticky_headers(&snapshot, cx);
24222 if *is_local && self.has_active_edit_prediction() {
24223 self.update_visible_edit_prediction(window, cx);
24224 }
24225
24226 // Clean up orphaned review comments after edits
24227 self.cleanup_orphaned_review_comments(cx);
24228
24229 if let Some(buffer) = edited_buffer {
24230 if buffer.read(cx).file().is_none() {
24231 cx.emit(EditorEvent::TitleChanged);
24232 }
24233
24234 if self.project.is_some() {
24235 let buffer_id = buffer.read(cx).remote_id();
24236 self.register_buffer(buffer_id, cx);
24237 self.update_lsp_data(Some(buffer_id), window, cx);
24238 self.refresh_inlay_hints(
24239 InlayHintRefreshReason::BufferEdited(buffer_id),
24240 cx,
24241 );
24242 }
24243 }
24244
24245 cx.emit(EditorEvent::BufferEdited);
24246 cx.emit(SearchEvent::MatchesInvalidated);
24247
24248 let Some(project) = &self.project else { return };
24249 let (telemetry, is_via_ssh) = {
24250 let project = project.read(cx);
24251 let telemetry = project.client().telemetry().clone();
24252 let is_via_ssh = project.is_via_remote_server();
24253 (telemetry, is_via_ssh)
24254 };
24255 telemetry.log_edit_event("editor", is_via_ssh);
24256 }
24257 multi_buffer::Event::ExcerptsAdded {
24258 buffer,
24259 predecessor,
24260 excerpts,
24261 } => {
24262 let buffer_id = buffer.read(cx).remote_id();
24263 if self.buffer.read(cx).diff_for(buffer_id).is_none()
24264 && let Some(project) = &self.project
24265 {
24266 update_uncommitted_diff_for_buffer(
24267 cx.entity(),
24268 project,
24269 [buffer.clone()],
24270 self.buffer.clone(),
24271 cx,
24272 )
24273 .detach();
24274 }
24275 self.semantic_token_state
24276 .invalidate_buffer(&buffer.read(cx).remote_id());
24277 self.update_lsp_data(Some(buffer_id), window, cx);
24278 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
24279 self.refresh_runnables(None, window, cx);
24280 self.colorize_brackets(false, cx);
24281 self.refresh_selected_text_highlights(&self.display_snapshot(cx), true, window, cx);
24282 cx.emit(EditorEvent::ExcerptsAdded {
24283 buffer: buffer.clone(),
24284 predecessor: *predecessor,
24285 excerpts: excerpts.clone(),
24286 });
24287 }
24288 multi_buffer::Event::ExcerptsRemoved {
24289 ids,
24290 removed_buffer_ids,
24291 } => {
24292 if let Some(inlay_hints) = &mut self.inlay_hints {
24293 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
24294 }
24295 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
24296 for buffer_id in removed_buffer_ids {
24297 self.registered_buffers.remove(buffer_id);
24298 self.clear_runnables(Some(*buffer_id));
24299 self.semantic_token_state.invalidate_buffer(buffer_id);
24300 self.display_map.update(cx, |display_map, cx| {
24301 display_map.invalidate_semantic_highlights(*buffer_id);
24302 display_map.clear_lsp_folding_ranges(*buffer_id, cx);
24303 });
24304 }
24305
24306 self.display_map.update(cx, |display_map, cx| {
24307 display_map.unfold_buffers(removed_buffer_ids.iter().copied(), cx);
24308 });
24309
24310 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24311 cx.emit(EditorEvent::ExcerptsRemoved {
24312 ids: ids.clone(),
24313 removed_buffer_ids: removed_buffer_ids.clone(),
24314 });
24315 }
24316 multi_buffer::Event::ExcerptsEdited {
24317 excerpt_ids,
24318 buffer_ids,
24319 } => {
24320 self.display_map.update(cx, |map, cx| {
24321 map.unfold_buffers(buffer_ids.iter().copied(), cx)
24322 });
24323 cx.emit(EditorEvent::ExcerptsEdited {
24324 ids: excerpt_ids.clone(),
24325 });
24326 }
24327 multi_buffer::Event::ExcerptsExpanded { ids } => {
24328 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
24329 self.refresh_document_highlights(cx);
24330 let snapshot = multibuffer.read(cx).snapshot(cx);
24331 for id in ids {
24332 self.bracket_fetched_tree_sitter_chunks.remove(id);
24333 if let Some(buffer) = snapshot.buffer_for_excerpt(*id) {
24334 self.semantic_token_state
24335 .invalidate_buffer(&buffer.remote_id());
24336 }
24337 }
24338 self.colorize_brackets(false, cx);
24339 self.update_lsp_data(None, window, cx);
24340 self.refresh_runnables(None, window, cx);
24341 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
24342 }
24343 multi_buffer::Event::Reparsed(buffer_id) => {
24344 self.refresh_runnables(Some(*buffer_id), window, cx);
24345 self.refresh_selected_text_highlights(&self.display_snapshot(cx), true, window, cx);
24346 self.colorize_brackets(true, cx);
24347 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24348
24349 cx.emit(EditorEvent::Reparsed(*buffer_id));
24350 }
24351 multi_buffer::Event::DiffHunksToggled => {
24352 self.refresh_runnables(None, window, cx);
24353 }
24354 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
24355 if !is_fresh_language {
24356 self.registered_buffers.remove(&buffer_id);
24357 }
24358 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24359 cx.emit(EditorEvent::Reparsed(*buffer_id));
24360 self.update_edit_prediction_settings(cx);
24361 cx.notify();
24362 }
24363 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
24364 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
24365 multi_buffer::Event::FileHandleChanged
24366 | multi_buffer::Event::Reloaded
24367 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
24368 multi_buffer::Event::DiagnosticsUpdated => {
24369 self.update_diagnostics_state(window, cx);
24370 }
24371 _ => {}
24372 };
24373 }
24374
24375 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
24376 if !self.diagnostics_enabled() {
24377 return;
24378 }
24379 self.refresh_active_diagnostics(cx);
24380 self.refresh_inline_diagnostics(true, window, cx);
24381 self.scrollbar_marker_state.dirty = true;
24382 cx.notify();
24383 }
24384
24385 pub fn start_temporary_diff_override(&mut self) {
24386 self.load_diff_task.take();
24387 self.temporary_diff_override = true;
24388 }
24389
24390 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
24391 self.temporary_diff_override = false;
24392 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
24393 self.buffer.update(cx, |buffer, cx| {
24394 buffer.set_all_diff_hunks_collapsed(cx);
24395 });
24396
24397 if let Some(project) = self.project.clone() {
24398 self.load_diff_task = Some(
24399 update_uncommitted_diff_for_buffer(
24400 cx.entity(),
24401 &project,
24402 self.buffer.read(cx).all_buffers(),
24403 self.buffer.clone(),
24404 cx,
24405 )
24406 .shared(),
24407 );
24408 }
24409 }
24410
24411 fn on_display_map_changed(
24412 &mut self,
24413 _: Entity<DisplayMap>,
24414 _: &mut Window,
24415 cx: &mut Context<Self>,
24416 ) {
24417 cx.notify();
24418 }
24419
24420 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
24421 if !self.mode.is_full() {
24422 return None;
24423 }
24424
24425 let theme_settings = theme::ThemeSettings::get_global(cx);
24426 let theme = cx.theme();
24427 let accent_colors = theme.accents().clone();
24428
24429 let accent_overrides = theme_settings
24430 .theme_overrides
24431 .get(theme.name.as_ref())
24432 .map(|theme_style| &theme_style.accents)
24433 .into_iter()
24434 .flatten()
24435 .chain(
24436 theme_settings
24437 .experimental_theme_overrides
24438 .as_ref()
24439 .map(|overrides| &overrides.accents)
24440 .into_iter()
24441 .flatten(),
24442 )
24443 .flat_map(|accent| accent.0.clone().map(SharedString::from))
24444 .collect();
24445
24446 Some(AccentData {
24447 colors: accent_colors,
24448 overrides: accent_overrides,
24449 })
24450 }
24451
24452 fn fetch_applicable_language_settings(
24453 &self,
24454 cx: &App,
24455 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
24456 if !self.mode.is_full() {
24457 return HashMap::default();
24458 }
24459
24460 self.buffer().read(cx).all_buffers().into_iter().fold(
24461 HashMap::default(),
24462 |mut acc, buffer| {
24463 let buffer = buffer.read(cx);
24464 let language = buffer.language().map(|language| language.name());
24465 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
24466 let file = buffer.file();
24467 v.insert(language_settings(language, file, cx).into_owned());
24468 }
24469 acc
24470 },
24471 )
24472 }
24473
24474 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24475 let new_language_settings = self.fetch_applicable_language_settings(cx);
24476 let language_settings_changed = new_language_settings != self.applicable_language_settings;
24477 self.applicable_language_settings = new_language_settings;
24478
24479 let new_accents = self.fetch_accent_data(cx);
24480 let accents_changed = new_accents != self.accent_data;
24481 self.accent_data = new_accents;
24482
24483 if self.diagnostics_enabled() {
24484 let new_severity = EditorSettings::get_global(cx)
24485 .diagnostics_max_severity
24486 .unwrap_or(DiagnosticSeverity::Hint);
24487 self.set_max_diagnostics_severity(new_severity, cx);
24488 }
24489 self.refresh_runnables(None, window, cx);
24490 self.update_edit_prediction_settings(cx);
24491 self.refresh_edit_prediction(true, false, window, cx);
24492 self.refresh_inline_values(cx);
24493
24494 let old_cursor_shape = self.cursor_shape;
24495 let old_show_breadcrumbs = self.show_breadcrumbs;
24496
24497 {
24498 let editor_settings = EditorSettings::get_global(cx);
24499 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
24500 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
24501 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
24502 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
24503 }
24504
24505 if old_cursor_shape != self.cursor_shape {
24506 cx.emit(EditorEvent::CursorShapeChanged);
24507 }
24508
24509 if old_show_breadcrumbs != self.show_breadcrumbs {
24510 cx.emit(EditorEvent::BreadcrumbsChanged);
24511 }
24512
24513 let (restore_unsaved_buffers, show_inline_diagnostics, inline_blame_enabled) = {
24514 let project_settings = ProjectSettings::get_global(cx);
24515 (
24516 project_settings.session.restore_unsaved_buffers,
24517 project_settings.diagnostics.inline.enabled,
24518 project_settings.git.inline_blame.enabled,
24519 )
24520 };
24521 self.buffer_serialization = self
24522 .should_serialize_buffer()
24523 .then(|| BufferSerialization::new(restore_unsaved_buffers));
24524
24525 if self.mode.is_full() {
24526 if self.show_inline_diagnostics != show_inline_diagnostics {
24527 self.show_inline_diagnostics = show_inline_diagnostics;
24528 self.refresh_inline_diagnostics(false, window, cx);
24529 }
24530
24531 if self.git_blame_inline_enabled != inline_blame_enabled {
24532 self.toggle_git_blame_inline_internal(false, window, cx);
24533 }
24534
24535 let minimap_settings = EditorSettings::get_global(cx).minimap;
24536 if self.minimap_visibility != MinimapVisibility::Disabled {
24537 if self.minimap_visibility.settings_visibility()
24538 != minimap_settings.minimap_enabled()
24539 {
24540 self.set_minimap_visibility(
24541 MinimapVisibility::for_mode(self.mode(), cx),
24542 window,
24543 cx,
24544 );
24545 } else if let Some(minimap_entity) = self.minimap.as_ref() {
24546 minimap_entity.update(cx, |minimap_editor, cx| {
24547 minimap_editor.update_minimap_configuration(minimap_settings, cx)
24548 })
24549 }
24550 }
24551
24552 if language_settings_changed || accents_changed {
24553 self.colorize_brackets(true, cx);
24554 }
24555
24556 if language_settings_changed {
24557 self.clear_disabled_lsp_folding_ranges(window, cx);
24558 self.refresh_document_symbols(None, cx);
24559 }
24560
24561 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
24562 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
24563 }) {
24564 if !inlay_splice.is_empty() {
24565 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
24566 }
24567 self.refresh_document_colors(None, window, cx);
24568 }
24569
24570 self.refresh_inlay_hints(
24571 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
24572 self.selections.newest_anchor().head(),
24573 &self.buffer.read(cx).snapshot(cx),
24574 cx,
24575 )),
24576 cx,
24577 );
24578
24579 let new_semantic_token_rules = ProjectSettings::get_global(cx)
24580 .global_lsp_settings
24581 .semantic_token_rules
24582 .clone();
24583 let semantic_token_rules_changed = self
24584 .semantic_token_state
24585 .update_rules(new_semantic_token_rules);
24586 if language_settings_changed || semantic_token_rules_changed {
24587 self.invalidate_semantic_tokens(None);
24588 self.refresh_semantic_tokens(None, None, cx);
24589 }
24590 }
24591
24592 cx.notify();
24593 }
24594
24595 fn theme_changed(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24596 if !self.mode.is_full() {
24597 return;
24598 }
24599
24600 let new_accents = self.fetch_accent_data(cx);
24601 if new_accents != self.accent_data {
24602 self.accent_data = new_accents;
24603 self.colorize_brackets(true, cx);
24604 }
24605
24606 self.invalidate_semantic_tokens(None);
24607 self.refresh_semantic_tokens(None, None, cx);
24608 }
24609
24610 pub fn set_searchable(&mut self, searchable: bool) {
24611 self.searchable = searchable;
24612 }
24613
24614 pub fn searchable(&self) -> bool {
24615 self.searchable
24616 }
24617
24618 pub fn open_excerpts_in_split(
24619 &mut self,
24620 _: &OpenExcerptsSplit,
24621 window: &mut Window,
24622 cx: &mut Context<Self>,
24623 ) {
24624 self.open_excerpts_common(None, true, window, cx)
24625 }
24626
24627 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
24628 self.open_excerpts_common(None, false, window, cx)
24629 }
24630
24631 pub(crate) fn open_excerpts_common(
24632 &mut self,
24633 jump_data: Option<JumpData>,
24634 split: bool,
24635 window: &mut Window,
24636 cx: &mut Context<Self>,
24637 ) {
24638 if self.buffer.read(cx).is_singleton() {
24639 cx.propagate();
24640 return;
24641 }
24642
24643 let mut new_selections_by_buffer = HashMap::default();
24644 match &jump_data {
24645 Some(JumpData::MultiBufferPoint {
24646 excerpt_id,
24647 position,
24648 anchor,
24649 line_offset_from_top,
24650 }) => {
24651 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
24652 if let Some(buffer) = multi_buffer_snapshot
24653 .buffer_id_for_excerpt(*excerpt_id)
24654 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
24655 {
24656 let buffer_snapshot = buffer.read(cx).snapshot();
24657 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
24658 language::ToPoint::to_point(anchor, &buffer_snapshot)
24659 } else {
24660 buffer_snapshot.clip_point(*position, Bias::Left)
24661 };
24662 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
24663 new_selections_by_buffer.insert(
24664 buffer,
24665 (
24666 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
24667 Some(*line_offset_from_top),
24668 ),
24669 );
24670 }
24671 }
24672 Some(JumpData::MultiBufferRow {
24673 row,
24674 line_offset_from_top,
24675 }) => {
24676 let point = MultiBufferPoint::new(row.0, 0);
24677 if let Some((buffer, buffer_point, _)) =
24678 self.buffer.read(cx).point_to_buffer_point(point, cx)
24679 {
24680 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
24681 new_selections_by_buffer
24682 .entry(buffer)
24683 .or_insert((Vec::new(), Some(*line_offset_from_top)))
24684 .0
24685 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
24686 }
24687 }
24688 None => {
24689 let selections = self
24690 .selections
24691 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
24692 let multi_buffer = self.buffer.read(cx);
24693 for selection in selections {
24694 for (snapshot, range, _, anchor) in multi_buffer
24695 .snapshot(cx)
24696 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
24697 {
24698 if let Some(anchor) = anchor {
24699 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
24700 else {
24701 continue;
24702 };
24703 let offset = text::ToOffset::to_offset(
24704 &anchor.text_anchor,
24705 &buffer_handle.read(cx).snapshot(),
24706 );
24707 let range = BufferOffset(offset)..BufferOffset(offset);
24708 new_selections_by_buffer
24709 .entry(buffer_handle)
24710 .or_insert((Vec::new(), None))
24711 .0
24712 .push(range)
24713 } else {
24714 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
24715 else {
24716 continue;
24717 };
24718 new_selections_by_buffer
24719 .entry(buffer_handle)
24720 .or_insert((Vec::new(), None))
24721 .0
24722 .push(range)
24723 }
24724 }
24725 }
24726 }
24727 }
24728
24729 if self.delegate_open_excerpts {
24730 let selections_by_buffer: HashMap<_, _> = new_selections_by_buffer
24731 .into_iter()
24732 .map(|(buffer, value)| (buffer.read(cx).remote_id(), value))
24733 .collect();
24734 if !selections_by_buffer.is_empty() {
24735 cx.emit(EditorEvent::OpenExcerptsRequested {
24736 selections_by_buffer,
24737 split,
24738 });
24739 }
24740 return;
24741 }
24742
24743 let Some(workspace) = self.workspace() else {
24744 cx.propagate();
24745 return;
24746 };
24747
24748 new_selections_by_buffer
24749 .retain(|buffer, _| buffer.read(cx).file().is_none_or(|file| file.can_open()));
24750
24751 if new_selections_by_buffer.is_empty() {
24752 return;
24753 }
24754
24755 Self::open_buffers_in_workspace(
24756 workspace.downgrade(),
24757 new_selections_by_buffer,
24758 split,
24759 window,
24760 cx,
24761 );
24762 }
24763
24764 pub(crate) fn open_buffers_in_workspace(
24765 workspace: WeakEntity<Workspace>,
24766 new_selections_by_buffer: HashMap<
24767 Entity<language::Buffer>,
24768 (Vec<Range<BufferOffset>>, Option<u32>),
24769 >,
24770 split: bool,
24771 window: &mut Window,
24772 cx: &mut App,
24773 ) {
24774 // We defer the pane interaction because we ourselves are a workspace item
24775 // and activating a new item causes the pane to call a method on us reentrantly,
24776 // which panics if we're on the stack.
24777 window.defer(cx, move |window, cx| {
24778 workspace
24779 .update(cx, |workspace, cx| {
24780 let pane = if split {
24781 workspace.adjacent_pane(window, cx)
24782 } else {
24783 workspace.active_pane().clone()
24784 };
24785
24786 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
24787 let buffer_read = buffer.read(cx);
24788 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
24789 (true, project::File::from_dyn(Some(file)).is_some())
24790 } else {
24791 (false, false)
24792 };
24793
24794 // If project file is none workspace.open_project_item will fail to open the excerpt
24795 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
24796 // so we check if there's a tab match in that case first
24797 let editor = (!has_file || !is_project_file)
24798 .then(|| {
24799 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
24800 // so `workspace.open_project_item` will never find them, always opening a new editor.
24801 // Instead, we try to activate the existing editor in the pane first.
24802 let (editor, pane_item_index, pane_item_id) =
24803 pane.read(cx).items().enumerate().find_map(|(i, item)| {
24804 let editor = item.downcast::<Editor>()?;
24805 let singleton_buffer =
24806 editor.read(cx).buffer().read(cx).as_singleton()?;
24807 if singleton_buffer == buffer {
24808 Some((editor, i, item.item_id()))
24809 } else {
24810 None
24811 }
24812 })?;
24813 pane.update(cx, |pane, cx| {
24814 pane.activate_item(pane_item_index, true, true, window, cx);
24815 if !PreviewTabsSettings::get_global(cx)
24816 .enable_preview_from_multibuffer
24817 {
24818 pane.unpreview_item_if_preview(pane_item_id);
24819 }
24820 });
24821 Some(editor)
24822 })
24823 .flatten()
24824 .unwrap_or_else(|| {
24825 let keep_old_preview = PreviewTabsSettings::get_global(cx)
24826 .enable_keep_preview_on_code_navigation;
24827 let allow_new_preview = PreviewTabsSettings::get_global(cx)
24828 .enable_preview_from_multibuffer;
24829 workspace.open_project_item::<Self>(
24830 pane.clone(),
24831 buffer,
24832 true,
24833 true,
24834 keep_old_preview,
24835 allow_new_preview,
24836 window,
24837 cx,
24838 )
24839 });
24840
24841 editor.update(cx, |editor, cx| {
24842 if has_file && !is_project_file {
24843 editor.set_read_only(true);
24844 }
24845 let autoscroll = match scroll_offset {
24846 Some(scroll_offset) => {
24847 Autoscroll::top_relative(scroll_offset as usize)
24848 }
24849 None => Autoscroll::newest(),
24850 };
24851 let nav_history = editor.nav_history.take();
24852 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
24853 let Some((excerpt_id, _, buffer_snapshot)) =
24854 multibuffer_snapshot.as_singleton()
24855 else {
24856 return;
24857 };
24858 editor.change_selections(
24859 SelectionEffects::scroll(autoscroll),
24860 window,
24861 cx,
24862 |s| {
24863 s.select_ranges(ranges.into_iter().map(|range| {
24864 let range = buffer_snapshot.anchor_before(range.start)
24865 ..buffer_snapshot.anchor_after(range.end);
24866 multibuffer_snapshot
24867 .anchor_range_in_excerpt(excerpt_id, range)
24868 .unwrap()
24869 }));
24870 },
24871 );
24872 editor.nav_history = nav_history;
24873 });
24874 }
24875 })
24876 .ok();
24877 });
24878 }
24879
24880 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
24881 let snapshot = self.buffer.read(cx).read(cx);
24882 let (_, ranges) = self.text_highlights(HighlightKey::InputComposition, cx)?;
24883 Some(
24884 ranges
24885 .iter()
24886 .map(move |range| {
24887 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
24888 })
24889 .collect(),
24890 )
24891 }
24892
24893 fn selection_replacement_ranges(
24894 &self,
24895 range: Range<MultiBufferOffsetUtf16>,
24896 cx: &mut App,
24897 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
24898 let selections = self
24899 .selections
24900 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24901 let newest_selection = selections
24902 .iter()
24903 .max_by_key(|selection| selection.id)
24904 .unwrap();
24905 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
24906 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
24907 let snapshot = self.buffer.read(cx).read(cx);
24908 selections
24909 .into_iter()
24910 .map(|mut selection| {
24911 selection.start.0.0 =
24912 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
24913 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
24914 snapshot.clip_offset_utf16(selection.start, Bias::Left)
24915 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
24916 })
24917 .collect()
24918 }
24919
24920 fn report_editor_event(
24921 &self,
24922 reported_event: ReportEditorEvent,
24923 file_extension: Option<String>,
24924 cx: &App,
24925 ) {
24926 if cfg!(any(test, feature = "test-support")) {
24927 return;
24928 }
24929
24930 let Some(project) = &self.project else { return };
24931
24932 // If None, we are in a file without an extension
24933 let file = self
24934 .buffer
24935 .read(cx)
24936 .as_singleton()
24937 .and_then(|b| b.read(cx).file());
24938 let file_extension = file_extension.or(file
24939 .as_ref()
24940 .and_then(|file| Path::new(file.file_name(cx)).extension())
24941 .and_then(|e| e.to_str())
24942 .map(|a| a.to_string()));
24943
24944 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
24945 .map(|vim_mode| vim_mode.0)
24946 .unwrap_or(false);
24947
24948 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
24949 let copilot_enabled = edit_predictions_provider
24950 == language::language_settings::EditPredictionProvider::Copilot;
24951 let copilot_enabled_for_language = self
24952 .buffer
24953 .read(cx)
24954 .language_settings(cx)
24955 .show_edit_predictions;
24956
24957 let project = project.read(cx);
24958 let event_type = reported_event.event_type();
24959
24960 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
24961 telemetry::event!(
24962 event_type,
24963 type = if auto_saved {"autosave"} else {"manual"},
24964 file_extension,
24965 vim_mode,
24966 copilot_enabled,
24967 copilot_enabled_for_language,
24968 edit_predictions_provider,
24969 is_via_ssh = project.is_via_remote_server(),
24970 );
24971 } else {
24972 telemetry::event!(
24973 event_type,
24974 file_extension,
24975 vim_mode,
24976 copilot_enabled,
24977 copilot_enabled_for_language,
24978 edit_predictions_provider,
24979 is_via_ssh = project.is_via_remote_server(),
24980 );
24981 };
24982 }
24983
24984 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
24985 /// with each line being an array of {text, highlight} objects.
24986 fn copy_highlight_json(
24987 &mut self,
24988 _: &CopyHighlightJson,
24989 window: &mut Window,
24990 cx: &mut Context<Self>,
24991 ) {
24992 #[derive(Serialize)]
24993 struct Chunk<'a> {
24994 text: String,
24995 highlight: Option<&'a str>,
24996 }
24997
24998 let snapshot = self.buffer.read(cx).snapshot(cx);
24999 let range = self
25000 .selected_text_range(false, window, cx)
25001 .and_then(|selection| {
25002 if selection.range.is_empty() {
25003 None
25004 } else {
25005 Some(
25006 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
25007 selection.range.start,
25008 )))
25009 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
25010 selection.range.end,
25011 ))),
25012 )
25013 }
25014 })
25015 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
25016
25017 let chunks = snapshot.chunks(range, true);
25018 let mut lines = Vec::new();
25019 let mut line: VecDeque<Chunk> = VecDeque::new();
25020
25021 let Some(style) = self.style.as_ref() else {
25022 return;
25023 };
25024
25025 for chunk in chunks {
25026 let highlight = chunk
25027 .syntax_highlight_id
25028 .and_then(|id| id.name(&style.syntax));
25029 let mut chunk_lines = chunk.text.split('\n').peekable();
25030 while let Some(text) = chunk_lines.next() {
25031 let mut merged_with_last_token = false;
25032 if let Some(last_token) = line.back_mut()
25033 && last_token.highlight == highlight
25034 {
25035 last_token.text.push_str(text);
25036 merged_with_last_token = true;
25037 }
25038
25039 if !merged_with_last_token {
25040 line.push_back(Chunk {
25041 text: text.into(),
25042 highlight,
25043 });
25044 }
25045
25046 if chunk_lines.peek().is_some() {
25047 if line.len() > 1 && line.front().unwrap().text.is_empty() {
25048 line.pop_front();
25049 }
25050 if line.len() > 1 && line.back().unwrap().text.is_empty() {
25051 line.pop_back();
25052 }
25053
25054 lines.push(mem::take(&mut line));
25055 }
25056 }
25057 }
25058
25059 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
25060 return;
25061 };
25062 cx.write_to_clipboard(ClipboardItem::new_string(lines));
25063 }
25064
25065 pub fn open_context_menu(
25066 &mut self,
25067 _: &OpenContextMenu,
25068 window: &mut Window,
25069 cx: &mut Context<Self>,
25070 ) {
25071 self.request_autoscroll(Autoscroll::newest(), cx);
25072 let position = self
25073 .selections
25074 .newest_display(&self.display_snapshot(cx))
25075 .start;
25076 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
25077 }
25078
25079 pub fn replay_insert_event(
25080 &mut self,
25081 text: &str,
25082 relative_utf16_range: Option<Range<isize>>,
25083 window: &mut Window,
25084 cx: &mut Context<Self>,
25085 ) {
25086 if !self.input_enabled {
25087 cx.emit(EditorEvent::InputIgnored { text: text.into() });
25088 return;
25089 }
25090 if let Some(relative_utf16_range) = relative_utf16_range {
25091 let selections = self
25092 .selections
25093 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
25094 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25095 let new_ranges = selections.into_iter().map(|range| {
25096 let start = MultiBufferOffsetUtf16(OffsetUtf16(
25097 range
25098 .head()
25099 .0
25100 .0
25101 .saturating_add_signed(relative_utf16_range.start),
25102 ));
25103 let end = MultiBufferOffsetUtf16(OffsetUtf16(
25104 range
25105 .head()
25106 .0
25107 .0
25108 .saturating_add_signed(relative_utf16_range.end),
25109 ));
25110 start..end
25111 });
25112 s.select_ranges(new_ranges);
25113 });
25114 }
25115
25116 self.handle_input(text, window, cx);
25117 }
25118
25119 pub fn is_focused(&self, window: &Window) -> bool {
25120 self.focus_handle.is_focused(window)
25121 }
25122
25123 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25124 cx.emit(EditorEvent::Focused);
25125
25126 if let Some(descendant) = self
25127 .last_focused_descendant
25128 .take()
25129 .and_then(|descendant| descendant.upgrade())
25130 {
25131 window.focus(&descendant, cx);
25132 } else {
25133 if let Some(blame) = self.blame.as_ref() {
25134 blame.update(cx, GitBlame::focus)
25135 }
25136
25137 self.blink_manager.update(cx, BlinkManager::enable);
25138 self.show_cursor_names(window, cx);
25139 self.buffer.update(cx, |buffer, cx| {
25140 buffer.finalize_last_transaction(cx);
25141 if self.leader_id.is_none() {
25142 buffer.set_active_selections(
25143 &self.selections.disjoint_anchors_arc(),
25144 self.selections.line_mode(),
25145 self.cursor_shape,
25146 cx,
25147 );
25148 }
25149 });
25150
25151 if let Some(position_map) = self.last_position_map.clone()
25152 && !self.mouse_cursor_hidden
25153 {
25154 EditorElement::mouse_moved(
25155 self,
25156 &MouseMoveEvent {
25157 position: window.mouse_position(),
25158 pressed_button: None,
25159 modifiers: window.modifiers(),
25160 },
25161 &position_map,
25162 None,
25163 window,
25164 cx,
25165 );
25166 }
25167 }
25168 }
25169
25170 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
25171 cx.emit(EditorEvent::FocusedIn)
25172 }
25173
25174 fn handle_focus_out(
25175 &mut self,
25176 event: FocusOutEvent,
25177 _window: &mut Window,
25178 cx: &mut Context<Self>,
25179 ) {
25180 if event.blurred != self.focus_handle {
25181 self.last_focused_descendant = Some(event.blurred);
25182 }
25183 self.selection_drag_state = SelectionDragState::None;
25184 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
25185 }
25186
25187 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25188 self.blink_manager.update(cx, BlinkManager::disable);
25189 self.buffer
25190 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
25191
25192 if let Some(blame) = self.blame.as_ref() {
25193 blame.update(cx, GitBlame::blur)
25194 }
25195 if !self.hover_state.focused(window, cx) {
25196 hide_hover(self, cx);
25197 }
25198 if !self
25199 .context_menu
25200 .borrow()
25201 .as_ref()
25202 .is_some_and(|context_menu| context_menu.focused(window, cx))
25203 {
25204 self.hide_context_menu(window, cx);
25205 }
25206 self.take_active_edit_prediction(cx);
25207 cx.emit(EditorEvent::Blurred);
25208 cx.notify();
25209 }
25210
25211 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25212 let mut pending: String = window
25213 .pending_input_keystrokes()
25214 .into_iter()
25215 .flatten()
25216 .filter_map(|keystroke| keystroke.key_char.clone())
25217 .collect();
25218
25219 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
25220 pending = "".to_string();
25221 }
25222
25223 let existing_pending = self
25224 .text_highlights(HighlightKey::PendingInput, cx)
25225 .map(|(_, ranges)| ranges.to_vec());
25226 if existing_pending.is_none() && pending.is_empty() {
25227 return;
25228 }
25229 let transaction =
25230 self.transact(window, cx, |this, window, cx| {
25231 let selections = this
25232 .selections
25233 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
25234 let edits = selections
25235 .iter()
25236 .map(|selection| (selection.end..selection.end, pending.clone()));
25237 this.edit(edits, cx);
25238 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25239 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
25240 sel.start + ix * pending.len()..sel.end + ix * pending.len()
25241 }));
25242 });
25243 if let Some(existing_ranges) = existing_pending {
25244 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
25245 this.edit(edits, cx);
25246 }
25247 });
25248
25249 let snapshot = self.snapshot(window, cx);
25250 let ranges = self
25251 .selections
25252 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
25253 .into_iter()
25254 .map(|selection| {
25255 snapshot.buffer_snapshot().anchor_after(selection.end)
25256 ..snapshot
25257 .buffer_snapshot()
25258 .anchor_before(selection.end + pending.len())
25259 })
25260 .collect();
25261
25262 if pending.is_empty() {
25263 self.clear_highlights(HighlightKey::PendingInput, cx);
25264 } else {
25265 self.highlight_text(
25266 HighlightKey::PendingInput,
25267 ranges,
25268 HighlightStyle {
25269 underline: Some(UnderlineStyle {
25270 thickness: px(1.),
25271 color: None,
25272 wavy: false,
25273 }),
25274 ..Default::default()
25275 },
25276 cx,
25277 );
25278 }
25279
25280 self.ime_transaction = self.ime_transaction.or(transaction);
25281 if let Some(transaction) = self.ime_transaction {
25282 self.buffer.update(cx, |buffer, cx| {
25283 buffer.group_until_transaction(transaction, cx);
25284 });
25285 }
25286
25287 if self
25288 .text_highlights(HighlightKey::PendingInput, cx)
25289 .is_none()
25290 {
25291 self.ime_transaction.take();
25292 }
25293 }
25294
25295 pub fn register_action_renderer(
25296 &mut self,
25297 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
25298 ) -> Subscription {
25299 let id = self.next_editor_action_id.post_inc();
25300 self.editor_actions
25301 .borrow_mut()
25302 .insert(id, Box::new(listener));
25303
25304 let editor_actions = self.editor_actions.clone();
25305 Subscription::new(move || {
25306 editor_actions.borrow_mut().remove(&id);
25307 })
25308 }
25309
25310 pub fn register_action<A: Action>(
25311 &mut self,
25312 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
25313 ) -> Subscription {
25314 let id = self.next_editor_action_id.post_inc();
25315 let listener = Arc::new(listener);
25316 self.editor_actions.borrow_mut().insert(
25317 id,
25318 Box::new(move |_, window, _| {
25319 let listener = listener.clone();
25320 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
25321 let action = action.downcast_ref().unwrap();
25322 if phase == DispatchPhase::Bubble {
25323 listener(action, window, cx)
25324 }
25325 })
25326 }),
25327 );
25328
25329 let editor_actions = self.editor_actions.clone();
25330 Subscription::new(move || {
25331 editor_actions.borrow_mut().remove(&id);
25332 })
25333 }
25334
25335 pub fn file_header_size(&self) -> u32 {
25336 FILE_HEADER_HEIGHT
25337 }
25338
25339 pub fn restore(
25340 &mut self,
25341 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
25342 window: &mut Window,
25343 cx: &mut Context<Self>,
25344 ) {
25345 self.buffer().update(cx, |multi_buffer, cx| {
25346 for (buffer_id, changes) in revert_changes {
25347 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
25348 buffer.update(cx, |buffer, cx| {
25349 buffer.edit(
25350 changes
25351 .into_iter()
25352 .map(|(range, text)| (range, text.to_string())),
25353 None,
25354 cx,
25355 );
25356 });
25357 }
25358 }
25359 });
25360 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25361 selections.refresh()
25362 });
25363 }
25364
25365 pub fn to_pixel_point(
25366 &mut self,
25367 source: Anchor,
25368 editor_snapshot: &EditorSnapshot,
25369 window: &mut Window,
25370 cx: &mut App,
25371 ) -> Option<gpui::Point<Pixels>> {
25372 let source_point = source.to_display_point(editor_snapshot);
25373 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
25374 }
25375
25376 pub fn display_to_pixel_point(
25377 &mut self,
25378 source: DisplayPoint,
25379 editor_snapshot: &EditorSnapshot,
25380 window: &mut Window,
25381 cx: &mut App,
25382 ) -> Option<gpui::Point<Pixels>> {
25383 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
25384 let text_layout_details = self.text_layout_details(window, cx);
25385 let scroll_top = text_layout_details
25386 .scroll_anchor
25387 .scroll_position(editor_snapshot)
25388 .y;
25389
25390 if source.row().as_f64() < scroll_top.floor() {
25391 return None;
25392 }
25393 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
25394 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
25395 Some(gpui::Point::new(source_x, source_y))
25396 }
25397
25398 pub fn has_visible_completions_menu(&self) -> bool {
25399 !self.edit_prediction_preview_is_active()
25400 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
25401 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
25402 })
25403 }
25404
25405 pub fn register_addon<T: Addon>(&mut self, instance: T) {
25406 if self.mode.is_minimap() {
25407 return;
25408 }
25409 self.addons
25410 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
25411 }
25412
25413 pub fn unregister_addon<T: Addon>(&mut self) {
25414 self.addons.remove(&std::any::TypeId::of::<T>());
25415 }
25416
25417 pub fn addon<T: Addon>(&self) -> Option<&T> {
25418 let type_id = std::any::TypeId::of::<T>();
25419 self.addons
25420 .get(&type_id)
25421 .and_then(|item| item.to_any().downcast_ref::<T>())
25422 }
25423
25424 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
25425 let type_id = std::any::TypeId::of::<T>();
25426 self.addons
25427 .get_mut(&type_id)
25428 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
25429 }
25430
25431 fn character_dimensions(&self, window: &mut Window, cx: &mut App) -> CharacterDimensions {
25432 let text_layout_details = self.text_layout_details(window, cx);
25433 let style = &text_layout_details.editor_style;
25434 let font_id = window.text_system().resolve_font(&style.text.font());
25435 let font_size = style.text.font_size.to_pixels(window.rem_size());
25436 let line_height = style.text.line_height_in_pixels(window.rem_size());
25437 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
25438 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
25439
25440 CharacterDimensions {
25441 em_width,
25442 em_advance,
25443 line_height,
25444 }
25445 }
25446
25447 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
25448 self.load_diff_task.clone()
25449 }
25450
25451 fn read_metadata_from_db(
25452 &mut self,
25453 item_id: u64,
25454 workspace_id: WorkspaceId,
25455 window: &mut Window,
25456 cx: &mut Context<Editor>,
25457 ) {
25458 if self.buffer_kind(cx) == ItemBufferKind::Singleton
25459 && !self.mode.is_minimap()
25460 && WorkspaceSettings::get(None, cx).restore_on_startup
25461 != RestoreOnStartupBehavior::EmptyTab
25462 {
25463 let buffer_snapshot = OnceCell::new();
25464
25465 // Get file path for path-based fold lookup
25466 let file_path: Option<Arc<Path>> =
25467 self.buffer().read(cx).as_singleton().and_then(|buffer| {
25468 project::File::from_dyn(buffer.read(cx).file())
25469 .map(|file| Arc::from(file.abs_path(cx)))
25470 });
25471
25472 // Try file_folds (path-based) first, fallback to editor_folds (migration)
25473 let db = EditorDb::global(cx);
25474 let (folds, needs_migration) = if let Some(ref path) = file_path {
25475 if let Some(folds) = db.get_file_folds(workspace_id, path).log_err()
25476 && !folds.is_empty()
25477 {
25478 (Some(folds), false)
25479 } else if let Some(folds) = db.get_editor_folds(item_id, workspace_id).log_err()
25480 && !folds.is_empty()
25481 {
25482 // Found old editor_folds data, will migrate to file_folds
25483 (Some(folds), true)
25484 } else {
25485 (None, false)
25486 }
25487 } else {
25488 // No file path, try editor_folds as fallback
25489 let folds = db.get_editor_folds(item_id, workspace_id).log_err();
25490 (folds.filter(|f| !f.is_empty()), false)
25491 };
25492
25493 if let Some(folds) = folds {
25494 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25495 let snapshot_len = snapshot.len().0;
25496
25497 // Helper: search for fingerprint in buffer, return offset if found
25498 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
25499 // Ensure we start at a character boundary (defensive)
25500 let search_start = snapshot
25501 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
25502 .0;
25503 let search_end = snapshot_len.saturating_sub(fingerprint.len());
25504
25505 let mut byte_offset = search_start;
25506 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
25507 if byte_offset > search_end {
25508 break;
25509 }
25510 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
25511 return Some(byte_offset);
25512 }
25513 byte_offset += ch.len_utf8();
25514 }
25515 None
25516 };
25517
25518 // Track search position to handle duplicate fingerprints correctly.
25519 // Folds are stored in document order, so we advance after each match.
25520 let mut search_start = 0usize;
25521
25522 // Collect db_folds for migration (only folds with valid fingerprints)
25523 let mut db_folds_for_migration: Vec<(usize, usize, String, String)> = Vec::new();
25524
25525 let valid_folds: Vec<_> = folds
25526 .into_iter()
25527 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
25528 // Skip folds without fingerprints (old data before migration)
25529 let sfp = start_fp?;
25530 let efp = end_fp?;
25531 let efp_len = efp.len();
25532
25533 // Fast path: check if fingerprints match at stored offsets
25534 // Note: end_fp is content BEFORE fold end, so check at (stored_end - efp_len)
25535 let start_matches = stored_start < snapshot_len
25536 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
25537 let efp_check_pos = stored_end.saturating_sub(efp_len);
25538 let end_matches = efp_check_pos >= stored_start
25539 && stored_end <= snapshot_len
25540 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
25541
25542 let (new_start, new_end) = if start_matches && end_matches {
25543 // Offsets unchanged, use stored values
25544 (stored_start, stored_end)
25545 } else if sfp == efp {
25546 // Short fold: identical fingerprints can only match once per search
25547 // Use stored fold length to compute new_end
25548 let new_start = find_fingerprint(&sfp, search_start)?;
25549 let fold_len = stored_end - stored_start;
25550 let new_end = new_start + fold_len;
25551 (new_start, new_end)
25552 } else {
25553 // Slow path: search for fingerprints in buffer
25554 let new_start = find_fingerprint(&sfp, search_start)?;
25555 // Search for end_fp after start, then add efp_len to get actual fold end
25556 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
25557 let new_end = efp_pos + efp_len;
25558 (new_start, new_end)
25559 };
25560
25561 // Advance search position for next fold
25562 search_start = new_end;
25563
25564 // Validate fold makes sense (end must be after start)
25565 if new_end <= new_start {
25566 return None;
25567 }
25568
25569 // Collect for migration if needed
25570 if needs_migration {
25571 db_folds_for_migration.push((new_start, new_end, sfp, efp));
25572 }
25573
25574 Some(
25575 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
25576 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
25577 )
25578 })
25579 .collect();
25580
25581 if !valid_folds.is_empty() {
25582 self.fold_ranges(valid_folds, false, window, cx);
25583
25584 // Migrate from editor_folds to file_folds if we loaded from old table
25585 if needs_migration {
25586 if let Some(ref path) = file_path {
25587 let path = path.clone();
25588 let db = EditorDb::global(cx);
25589 cx.spawn(async move |_, _| {
25590 db.save_file_folds(workspace_id, path, db_folds_for_migration)
25591 .await
25592 .log_err();
25593 })
25594 .detach();
25595 }
25596 }
25597 }
25598 }
25599
25600 if let Some(selections) = db.get_editor_selections(item_id, workspace_id).log_err()
25601 && !selections.is_empty()
25602 {
25603 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25604 // skip adding the initial selection to selection history
25605 self.selection_history.mode = SelectionHistoryMode::Skipping;
25606 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25607 s.select_ranges(selections.into_iter().map(|(start, end)| {
25608 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
25609 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
25610 }));
25611 });
25612 self.selection_history.mode = SelectionHistoryMode::Normal;
25613 };
25614 }
25615
25616 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
25617 }
25618
25619 /// Load folds from the file_folds database table by file path.
25620 /// Used when manually opening a file that was previously closed.
25621 fn load_folds_from_db(
25622 &mut self,
25623 workspace_id: WorkspaceId,
25624 file_path: PathBuf,
25625 window: &mut Window,
25626 cx: &mut Context<Editor>,
25627 ) {
25628 if self.mode.is_minimap()
25629 || WorkspaceSettings::get(None, cx).restore_on_startup
25630 == RestoreOnStartupBehavior::EmptyTab
25631 {
25632 return;
25633 }
25634
25635 let Some(folds) = EditorDb::global(cx)
25636 .get_file_folds(workspace_id, &file_path)
25637 .log_err()
25638 else {
25639 return;
25640 };
25641 if folds.is_empty() {
25642 return;
25643 }
25644
25645 let snapshot = self.buffer.read(cx).snapshot(cx);
25646 let snapshot_len = snapshot.len().0;
25647
25648 // Helper: search for fingerprint in buffer, return offset if found
25649 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
25650 let search_start = snapshot
25651 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
25652 .0;
25653 let search_end = snapshot_len.saturating_sub(fingerprint.len());
25654
25655 let mut byte_offset = search_start;
25656 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
25657 if byte_offset > search_end {
25658 break;
25659 }
25660 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
25661 return Some(byte_offset);
25662 }
25663 byte_offset += ch.len_utf8();
25664 }
25665 None
25666 };
25667
25668 let mut search_start = 0usize;
25669
25670 let valid_folds: Vec<_> = folds
25671 .into_iter()
25672 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
25673 let sfp = start_fp?;
25674 let efp = end_fp?;
25675 let efp_len = efp.len();
25676
25677 let start_matches = stored_start < snapshot_len
25678 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
25679 let efp_check_pos = stored_end.saturating_sub(efp_len);
25680 let end_matches = efp_check_pos >= stored_start
25681 && stored_end <= snapshot_len
25682 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
25683
25684 let (new_start, new_end) = if start_matches && end_matches {
25685 (stored_start, stored_end)
25686 } else if sfp == efp {
25687 let new_start = find_fingerprint(&sfp, search_start)?;
25688 let fold_len = stored_end - stored_start;
25689 let new_end = new_start + fold_len;
25690 (new_start, new_end)
25691 } else {
25692 let new_start = find_fingerprint(&sfp, search_start)?;
25693 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
25694 let new_end = efp_pos + efp_len;
25695 (new_start, new_end)
25696 };
25697
25698 search_start = new_end;
25699
25700 if new_end <= new_start {
25701 return None;
25702 }
25703
25704 Some(
25705 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
25706 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
25707 )
25708 })
25709 .collect();
25710
25711 if !valid_folds.is_empty() {
25712 self.fold_ranges(valid_folds, false, window, cx);
25713 }
25714 }
25715
25716 fn lsp_data_enabled(&self) -> bool {
25717 self.enable_lsp_data && self.mode().is_full()
25718 }
25719
25720 fn update_lsp_data(
25721 &mut self,
25722 for_buffer: Option<BufferId>,
25723 window: &mut Window,
25724 cx: &mut Context<'_, Self>,
25725 ) {
25726 if !self.lsp_data_enabled() {
25727 return;
25728 }
25729
25730 if let Some(buffer_id) = for_buffer {
25731 self.pull_diagnostics(buffer_id, window, cx);
25732 }
25733 self.refresh_semantic_tokens(for_buffer, None, cx);
25734 self.refresh_document_colors(for_buffer, window, cx);
25735 self.refresh_folding_ranges(for_buffer, window, cx);
25736 self.refresh_document_symbols(for_buffer, cx);
25737 }
25738
25739 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
25740 if !self.lsp_data_enabled() {
25741 return;
25742 }
25743 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
25744 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
25745 }
25746 }
25747
25748 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
25749 if !self.lsp_data_enabled() {
25750 return;
25751 }
25752
25753 if !self.registered_buffers.contains_key(&buffer_id)
25754 && let Some(project) = self.project.as_ref()
25755 {
25756 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
25757 project.update(cx, |project, cx| {
25758 self.registered_buffers.insert(
25759 buffer_id,
25760 project.register_buffer_with_language_servers(&buffer, cx),
25761 );
25762 });
25763 } else {
25764 self.registered_buffers.remove(&buffer_id);
25765 }
25766 }
25767 }
25768
25769 fn create_style(&self, cx: &App) -> EditorStyle {
25770 let settings = ThemeSettings::get_global(cx);
25771
25772 let mut text_style = match self.mode {
25773 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
25774 color: cx.theme().colors().editor_foreground,
25775 font_family: settings.ui_font.family.clone(),
25776 font_features: settings.ui_font.features.clone(),
25777 font_fallbacks: settings.ui_font.fallbacks.clone(),
25778 font_size: rems(0.875).into(),
25779 font_weight: settings.ui_font.weight,
25780 line_height: relative(settings.buffer_line_height.value()),
25781 ..Default::default()
25782 },
25783 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
25784 color: cx.theme().colors().editor_foreground,
25785 font_family: settings.buffer_font.family.clone(),
25786 font_features: settings.buffer_font.features.clone(),
25787 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25788 font_size: settings.buffer_font_size(cx).into(),
25789 font_weight: settings.buffer_font.weight,
25790 line_height: relative(settings.buffer_line_height.value()),
25791 ..Default::default()
25792 },
25793 };
25794 if let Some(text_style_refinement) = &self.text_style_refinement {
25795 text_style.refine(text_style_refinement)
25796 }
25797
25798 let background = match self.mode {
25799 EditorMode::SingleLine => cx.theme().system().transparent,
25800 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
25801 EditorMode::Full { .. } => cx.theme().colors().editor_background,
25802 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
25803 };
25804
25805 EditorStyle {
25806 background,
25807 border: cx.theme().colors().border,
25808 local_player: cx.theme().players().local(),
25809 text: text_style,
25810 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
25811 syntax: cx.theme().syntax().clone(),
25812 status: cx.theme().status().clone(),
25813 inlay_hints_style: make_inlay_hints_style(cx),
25814 edit_prediction_styles: make_suggestion_styles(cx),
25815 unnecessary_code_fade: settings.unnecessary_code_fade,
25816 show_underlines: self.diagnostics_enabled(),
25817 }
25818 }
25819
25820 fn breadcrumbs_inner(&self, cx: &App) -> Option<Vec<HighlightedText>> {
25821 let multibuffer = self.buffer().read(cx);
25822 let is_singleton = multibuffer.is_singleton();
25823 let (buffer_id, symbols) = self.outline_symbols_at_cursor.as_ref()?;
25824 let buffer = multibuffer.buffer(*buffer_id)?;
25825
25826 let buffer = buffer.read(cx);
25827 // In a multi-buffer layout, we don't want to include the filename in the breadcrumbs
25828 let mut breadcrumbs = if is_singleton {
25829 let text = self.breadcrumb_header.clone().unwrap_or_else(|| {
25830 buffer
25831 .snapshot()
25832 .resolve_file_path(
25833 self.project
25834 .as_ref()
25835 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
25836 .unwrap_or_default(),
25837 cx,
25838 )
25839 .unwrap_or_else(|| {
25840 if multibuffer.is_singleton() {
25841 multibuffer.title(cx).to_string()
25842 } else {
25843 "untitled".to_string()
25844 }
25845 })
25846 });
25847 vec![HighlightedText {
25848 text: text.into(),
25849 highlights: vec![],
25850 }]
25851 } else {
25852 vec![]
25853 };
25854
25855 breadcrumbs.extend(symbols.iter().map(|symbol| HighlightedText {
25856 text: symbol.text.clone().into(),
25857 highlights: symbol.highlight_ranges.clone(),
25858 }));
25859 Some(breadcrumbs)
25860 }
25861
25862 fn disable_lsp_data(&mut self) {
25863 self.enable_lsp_data = false;
25864 }
25865
25866 fn disable_runnables(&mut self) {
25867 self.enable_runnables = false;
25868 }
25869
25870 fn update_data_on_scroll(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) {
25871 self.register_visible_buffers(cx);
25872 self.colorize_brackets(false, cx);
25873 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
25874 if !self.buffer().read(cx).is_singleton() {
25875 self.update_lsp_data(None, window, cx);
25876 self.refresh_runnables(None, window, cx);
25877 }
25878 }
25879}
25880
25881fn edit_for_markdown_paste<'a>(
25882 buffer: &MultiBufferSnapshot,
25883 range: Range<MultiBufferOffset>,
25884 to_insert: &'a str,
25885 url: Option<url::Url>,
25886) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
25887 if url.is_none() {
25888 return (range, Cow::Borrowed(to_insert));
25889 };
25890
25891 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
25892
25893 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
25894 Cow::Borrowed(to_insert)
25895 } else {
25896 Cow::Owned(format!("[{old_text}]({to_insert})"))
25897 };
25898 (range, new_text)
25899}
25900
25901fn process_completion_for_edit(
25902 completion: &Completion,
25903 intent: CompletionIntent,
25904 buffer: &Entity<Buffer>,
25905 cursor_position: &text::Anchor,
25906 cx: &mut Context<Editor>,
25907) -> CompletionEdit {
25908 let buffer = buffer.read(cx);
25909 let buffer_snapshot = buffer.snapshot();
25910 let (snippet, new_text) = if completion.is_snippet() {
25911 let mut snippet_source = completion.new_text.clone();
25912 // Workaround for typescript language server issues so that methods don't expand within
25913 // strings and functions with type expressions. The previous point is used because the query
25914 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
25915 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
25916 let previous_point = if previous_point.column > 0 {
25917 cursor_position.to_previous_offset(&buffer_snapshot)
25918 } else {
25919 cursor_position.to_offset(&buffer_snapshot)
25920 };
25921 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
25922 && scope.prefers_label_for_snippet_in_completion()
25923 && let Some(label) = completion.label()
25924 && matches!(
25925 completion.kind(),
25926 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
25927 )
25928 {
25929 snippet_source = label;
25930 }
25931 match Snippet::parse(&snippet_source).log_err() {
25932 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
25933 None => (None, completion.new_text.clone()),
25934 }
25935 } else {
25936 (None, completion.new_text.clone())
25937 };
25938
25939 let mut range_to_replace = {
25940 let replace_range = &completion.replace_range;
25941 if let CompletionSource::Lsp {
25942 insert_range: Some(insert_range),
25943 ..
25944 } = &completion.source
25945 {
25946 debug_assert_eq!(
25947 insert_range.start, replace_range.start,
25948 "insert_range and replace_range should start at the same position"
25949 );
25950 debug_assert!(
25951 insert_range
25952 .start
25953 .cmp(cursor_position, &buffer_snapshot)
25954 .is_le(),
25955 "insert_range should start before or at cursor position"
25956 );
25957 debug_assert!(
25958 replace_range
25959 .start
25960 .cmp(cursor_position, &buffer_snapshot)
25961 .is_le(),
25962 "replace_range should start before or at cursor position"
25963 );
25964
25965 let should_replace = match intent {
25966 CompletionIntent::CompleteWithInsert => false,
25967 CompletionIntent::CompleteWithReplace => true,
25968 CompletionIntent::Complete | CompletionIntent::Compose => {
25969 let insert_mode =
25970 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
25971 .completions
25972 .lsp_insert_mode;
25973 match insert_mode {
25974 LspInsertMode::Insert => false,
25975 LspInsertMode::Replace => true,
25976 LspInsertMode::ReplaceSubsequence => {
25977 let mut text_to_replace = buffer.chars_for_range(
25978 buffer.anchor_before(replace_range.start)
25979 ..buffer.anchor_after(replace_range.end),
25980 );
25981 let mut current_needle = text_to_replace.next();
25982 for haystack_ch in completion.label.text.chars() {
25983 if let Some(needle_ch) = current_needle
25984 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
25985 {
25986 current_needle = text_to_replace.next();
25987 }
25988 }
25989 current_needle.is_none()
25990 }
25991 LspInsertMode::ReplaceSuffix => {
25992 if replace_range
25993 .end
25994 .cmp(cursor_position, &buffer_snapshot)
25995 .is_gt()
25996 {
25997 let range_after_cursor = *cursor_position..replace_range.end;
25998 let text_after_cursor = buffer
25999 .text_for_range(
26000 buffer.anchor_before(range_after_cursor.start)
26001 ..buffer.anchor_after(range_after_cursor.end),
26002 )
26003 .collect::<String>()
26004 .to_ascii_lowercase();
26005 completion
26006 .label
26007 .text
26008 .to_ascii_lowercase()
26009 .ends_with(&text_after_cursor)
26010 } else {
26011 true
26012 }
26013 }
26014 }
26015 }
26016 };
26017
26018 if should_replace {
26019 replace_range.clone()
26020 } else {
26021 insert_range.clone()
26022 }
26023 } else {
26024 replace_range.clone()
26025 }
26026 };
26027
26028 if range_to_replace
26029 .end
26030 .cmp(cursor_position, &buffer_snapshot)
26031 .is_lt()
26032 {
26033 range_to_replace.end = *cursor_position;
26034 }
26035
26036 let replace_range = range_to_replace.to_offset(buffer);
26037 CompletionEdit {
26038 new_text,
26039 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
26040 snippet,
26041 }
26042}
26043
26044struct CompletionEdit {
26045 new_text: String,
26046 replace_range: Range<BufferOffset>,
26047 snippet: Option<Snippet>,
26048}
26049
26050fn comment_delimiter_for_newline(
26051 start_point: &Point,
26052 buffer: &MultiBufferSnapshot,
26053 language: &LanguageScope,
26054) -> Option<Arc<str>> {
26055 let delimiters = language.line_comment_prefixes();
26056 let max_len_of_delimiter = delimiters.iter().map(|delimiter| delimiter.len()).max()?;
26057 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
26058
26059 let num_of_whitespaces = snapshot
26060 .chars_for_range(range.clone())
26061 .take_while(|c| c.is_whitespace())
26062 .count();
26063 let comment_candidate = snapshot
26064 .chars_for_range(range.clone())
26065 .skip(num_of_whitespaces)
26066 .take(max_len_of_delimiter + 2)
26067 .collect::<String>();
26068 let (delimiter, trimmed_len, is_repl) = delimiters
26069 .iter()
26070 .filter_map(|delimiter| {
26071 let prefix = delimiter.trim_end();
26072 if comment_candidate.starts_with(prefix) {
26073 let is_repl = if let Some(stripped_comment) = comment_candidate.strip_prefix(prefix)
26074 {
26075 stripped_comment.starts_with(" %%")
26076 } else {
26077 false
26078 };
26079 Some((delimiter, prefix.len(), is_repl))
26080 } else {
26081 None
26082 }
26083 })
26084 .max_by_key(|(_, len, _)| *len)?;
26085
26086 if let Some(BlockCommentConfig {
26087 start: block_start, ..
26088 }) = language.block_comment()
26089 {
26090 let block_start_trimmed = block_start.trim_end();
26091 if block_start_trimmed.starts_with(delimiter.trim_end()) {
26092 let line_content = snapshot
26093 .chars_for_range(range.clone())
26094 .skip(num_of_whitespaces)
26095 .take(block_start_trimmed.len())
26096 .collect::<String>();
26097
26098 if line_content.starts_with(block_start_trimmed) {
26099 return None;
26100 }
26101 }
26102 }
26103
26104 let cursor_is_placed_after_comment_marker =
26105 num_of_whitespaces + trimmed_len <= start_point.column as usize;
26106 if cursor_is_placed_after_comment_marker {
26107 if !is_repl {
26108 return Some(delimiter.clone());
26109 }
26110
26111 let line_content_after_cursor: String = snapshot
26112 .chars_for_range(range)
26113 .skip(start_point.column as usize)
26114 .collect();
26115
26116 if line_content_after_cursor.trim().is_empty() {
26117 return None;
26118 } else {
26119 return Some(delimiter.clone());
26120 }
26121 } else {
26122 None
26123 }
26124}
26125
26126fn documentation_delimiter_for_newline(
26127 start_point: &Point,
26128 buffer: &MultiBufferSnapshot,
26129 language: &LanguageScope,
26130 newline_config: &mut NewlineConfig,
26131) -> Option<Arc<str>> {
26132 let BlockCommentConfig {
26133 start: start_tag,
26134 end: end_tag,
26135 prefix: delimiter,
26136 tab_size: len,
26137 } = language.documentation_comment()?;
26138 let is_within_block_comment = buffer
26139 .language_scope_at(*start_point)
26140 .is_some_and(|scope| scope.override_name() == Some("comment"));
26141 if !is_within_block_comment {
26142 return None;
26143 }
26144
26145 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
26146
26147 let num_of_whitespaces = snapshot
26148 .chars_for_range(range.clone())
26149 .take_while(|c| c.is_whitespace())
26150 .count();
26151
26152 // 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.
26153 let column = start_point.column;
26154 let cursor_is_after_start_tag = {
26155 let start_tag_len = start_tag.len();
26156 let start_tag_line = snapshot
26157 .chars_for_range(range.clone())
26158 .skip(num_of_whitespaces)
26159 .take(start_tag_len)
26160 .collect::<String>();
26161 if start_tag_line.starts_with(start_tag.as_ref()) {
26162 num_of_whitespaces + start_tag_len <= column as usize
26163 } else {
26164 false
26165 }
26166 };
26167
26168 let cursor_is_after_delimiter = {
26169 let delimiter_trim = delimiter.trim_end();
26170 let delimiter_line = snapshot
26171 .chars_for_range(range.clone())
26172 .skip(num_of_whitespaces)
26173 .take(delimiter_trim.len())
26174 .collect::<String>();
26175 if delimiter_line.starts_with(delimiter_trim) {
26176 num_of_whitespaces + delimiter_trim.len() <= column as usize
26177 } else {
26178 false
26179 }
26180 };
26181
26182 let mut needs_extra_line = false;
26183 let mut extra_line_additional_indent = IndentSize::spaces(0);
26184
26185 let cursor_is_before_end_tag_if_exists = {
26186 let mut char_position = 0u32;
26187 let mut end_tag_offset = None;
26188
26189 'outer: for chunk in snapshot.text_for_range(range) {
26190 if let Some(byte_pos) = chunk.find(&**end_tag) {
26191 let chars_before_match = chunk[..byte_pos].chars().count() as u32;
26192 end_tag_offset = Some(char_position + chars_before_match);
26193 break 'outer;
26194 }
26195 char_position += chunk.chars().count() as u32;
26196 }
26197
26198 if let Some(end_tag_offset) = end_tag_offset {
26199 let cursor_is_before_end_tag = column <= end_tag_offset;
26200 if cursor_is_after_start_tag {
26201 if cursor_is_before_end_tag {
26202 needs_extra_line = true;
26203 }
26204 let cursor_is_at_start_of_end_tag = column == end_tag_offset;
26205 if cursor_is_at_start_of_end_tag {
26206 extra_line_additional_indent.len = *len;
26207 }
26208 }
26209 cursor_is_before_end_tag
26210 } else {
26211 true
26212 }
26213 };
26214
26215 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
26216 && cursor_is_before_end_tag_if_exists
26217 {
26218 let additional_indent = if cursor_is_after_start_tag {
26219 IndentSize::spaces(*len)
26220 } else {
26221 IndentSize::spaces(0)
26222 };
26223
26224 *newline_config = NewlineConfig::Newline {
26225 additional_indent,
26226 extra_line_additional_indent: if needs_extra_line {
26227 Some(extra_line_additional_indent)
26228 } else {
26229 None
26230 },
26231 prevent_auto_indent: true,
26232 };
26233 Some(delimiter.clone())
26234 } else {
26235 None
26236 }
26237}
26238
26239const ORDERED_LIST_MAX_MARKER_LEN: usize = 16;
26240
26241fn list_delimiter_for_newline(
26242 start_point: &Point,
26243 buffer: &MultiBufferSnapshot,
26244 language: &LanguageScope,
26245 newline_config: &mut NewlineConfig,
26246) -> Option<Arc<str>> {
26247 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
26248
26249 let num_of_whitespaces = snapshot
26250 .chars_for_range(range.clone())
26251 .take_while(|c| c.is_whitespace())
26252 .count();
26253
26254 let task_list_entries: Vec<_> = language
26255 .task_list()
26256 .into_iter()
26257 .flat_map(|config| {
26258 config
26259 .prefixes
26260 .iter()
26261 .map(|prefix| (prefix.as_ref(), config.continuation.as_ref()))
26262 })
26263 .collect();
26264 let unordered_list_entries: Vec<_> = language
26265 .unordered_list()
26266 .iter()
26267 .map(|marker| (marker.as_ref(), marker.as_ref()))
26268 .collect();
26269
26270 let all_entries: Vec<_> = task_list_entries
26271 .into_iter()
26272 .chain(unordered_list_entries)
26273 .collect();
26274
26275 if let Some(max_prefix_len) = all_entries.iter().map(|(p, _)| p.len()).max() {
26276 let candidate: String = snapshot
26277 .chars_for_range(range.clone())
26278 .skip(num_of_whitespaces)
26279 .take(max_prefix_len)
26280 .collect();
26281
26282 if let Some((prefix, continuation)) = all_entries
26283 .iter()
26284 .filter(|(prefix, _)| candidate.starts_with(*prefix))
26285 .max_by_key(|(prefix, _)| prefix.len())
26286 {
26287 let end_of_prefix = num_of_whitespaces + prefix.len();
26288 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
26289 let has_content_after_marker = snapshot
26290 .chars_for_range(range)
26291 .skip(end_of_prefix)
26292 .any(|c| !c.is_whitespace());
26293
26294 if has_content_after_marker && cursor_is_after_prefix {
26295 return Some((*continuation).into());
26296 }
26297
26298 if start_point.column as usize == end_of_prefix {
26299 if num_of_whitespaces == 0 {
26300 *newline_config = NewlineConfig::ClearCurrentLine;
26301 } else {
26302 *newline_config = NewlineConfig::UnindentCurrentLine {
26303 continuation: (*continuation).into(),
26304 };
26305 }
26306 }
26307
26308 return None;
26309 }
26310 }
26311
26312 let candidate: String = snapshot
26313 .chars_for_range(range.clone())
26314 .skip(num_of_whitespaces)
26315 .take(ORDERED_LIST_MAX_MARKER_LEN)
26316 .collect();
26317
26318 for ordered_config in language.ordered_list() {
26319 let regex = match Regex::new(&ordered_config.pattern) {
26320 Ok(r) => r,
26321 Err(_) => continue,
26322 };
26323
26324 if let Some(captures) = regex.captures(&candidate) {
26325 let full_match = captures.get(0)?;
26326 let marker_len = full_match.len();
26327 let end_of_prefix = num_of_whitespaces + marker_len;
26328 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
26329
26330 let has_content_after_marker = snapshot
26331 .chars_for_range(range)
26332 .skip(end_of_prefix)
26333 .any(|c| !c.is_whitespace());
26334
26335 if has_content_after_marker && cursor_is_after_prefix {
26336 let number: u32 = captures.get(1)?.as_str().parse().ok()?;
26337 let continuation = ordered_config
26338 .format
26339 .replace("{1}", &(number + 1).to_string());
26340 return Some(continuation.into());
26341 }
26342
26343 if start_point.column as usize == end_of_prefix {
26344 let continuation = ordered_config.format.replace("{1}", "1");
26345 if num_of_whitespaces == 0 {
26346 *newline_config = NewlineConfig::ClearCurrentLine;
26347 } else {
26348 *newline_config = NewlineConfig::UnindentCurrentLine {
26349 continuation: continuation.into(),
26350 };
26351 }
26352 }
26353
26354 return None;
26355 }
26356 }
26357
26358 None
26359}
26360
26361fn is_list_prefix_row(
26362 row: MultiBufferRow,
26363 buffer: &MultiBufferSnapshot,
26364 language: &LanguageScope,
26365) -> bool {
26366 let Some((snapshot, range)) = buffer.buffer_line_for_row(row) else {
26367 return false;
26368 };
26369
26370 let num_of_whitespaces = snapshot
26371 .chars_for_range(range.clone())
26372 .take_while(|c| c.is_whitespace())
26373 .count();
26374
26375 let task_list_prefixes: Vec<_> = language
26376 .task_list()
26377 .into_iter()
26378 .flat_map(|config| {
26379 config
26380 .prefixes
26381 .iter()
26382 .map(|p| p.as_ref())
26383 .collect::<Vec<_>>()
26384 })
26385 .collect();
26386 let unordered_list_markers: Vec<_> = language
26387 .unordered_list()
26388 .iter()
26389 .map(|marker| marker.as_ref())
26390 .collect();
26391 let all_prefixes: Vec<_> = task_list_prefixes
26392 .into_iter()
26393 .chain(unordered_list_markers)
26394 .collect();
26395 if let Some(max_prefix_len) = all_prefixes.iter().map(|p| p.len()).max() {
26396 let candidate: String = snapshot
26397 .chars_for_range(range.clone())
26398 .skip(num_of_whitespaces)
26399 .take(max_prefix_len)
26400 .collect();
26401 if all_prefixes
26402 .iter()
26403 .any(|prefix| candidate.starts_with(*prefix))
26404 {
26405 return true;
26406 }
26407 }
26408
26409 let ordered_list_candidate: String = snapshot
26410 .chars_for_range(range)
26411 .skip(num_of_whitespaces)
26412 .take(ORDERED_LIST_MAX_MARKER_LEN)
26413 .collect();
26414 for ordered_config in language.ordered_list() {
26415 let regex = match Regex::new(&ordered_config.pattern) {
26416 Ok(r) => r,
26417 Err(_) => continue,
26418 };
26419 if let Some(captures) = regex.captures(&ordered_list_candidate) {
26420 return captures.get(0).is_some();
26421 }
26422 }
26423
26424 false
26425}
26426
26427#[derive(Debug)]
26428enum NewlineConfig {
26429 /// Insert newline with optional additional indent and optional extra blank line
26430 Newline {
26431 additional_indent: IndentSize,
26432 extra_line_additional_indent: Option<IndentSize>,
26433 prevent_auto_indent: bool,
26434 },
26435 /// Clear the current line
26436 ClearCurrentLine,
26437 /// Unindent the current line and add continuation
26438 UnindentCurrentLine { continuation: Arc<str> },
26439}
26440
26441impl NewlineConfig {
26442 fn has_extra_line(&self) -> bool {
26443 matches!(
26444 self,
26445 Self::Newline {
26446 extra_line_additional_indent: Some(_),
26447 ..
26448 }
26449 )
26450 }
26451
26452 fn insert_extra_newline_brackets(
26453 buffer: &MultiBufferSnapshot,
26454 range: Range<MultiBufferOffset>,
26455 language: &language::LanguageScope,
26456 ) -> bool {
26457 let leading_whitespace_len = buffer
26458 .reversed_chars_at(range.start)
26459 .take_while(|c| c.is_whitespace() && *c != '\n')
26460 .map(|c| c.len_utf8())
26461 .sum::<usize>();
26462 let trailing_whitespace_len = buffer
26463 .chars_at(range.end)
26464 .take_while(|c| c.is_whitespace() && *c != '\n')
26465 .map(|c| c.len_utf8())
26466 .sum::<usize>();
26467 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
26468
26469 language.brackets().any(|(pair, enabled)| {
26470 let pair_start = pair.start.trim_end();
26471 let pair_end = pair.end.trim_start();
26472
26473 enabled
26474 && pair.newline
26475 && buffer.contains_str_at(range.end, pair_end)
26476 && buffer.contains_str_at(
26477 range.start.saturating_sub_usize(pair_start.len()),
26478 pair_start,
26479 )
26480 })
26481 }
26482
26483 fn insert_extra_newline_tree_sitter(
26484 buffer: &MultiBufferSnapshot,
26485 range: Range<MultiBufferOffset>,
26486 ) -> bool {
26487 let (buffer, range) = match buffer
26488 .range_to_buffer_ranges(range.start..=range.end)
26489 .as_slice()
26490 {
26491 [(buffer, range, _)] => (*buffer, range.clone()),
26492 _ => return false,
26493 };
26494 let pair = {
26495 let mut result: Option<BracketMatch<usize>> = None;
26496
26497 for pair in buffer
26498 .all_bracket_ranges(range.start.0..range.end.0)
26499 .filter(move |pair| {
26500 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
26501 })
26502 {
26503 let len = pair.close_range.end - pair.open_range.start;
26504
26505 if let Some(existing) = &result {
26506 let existing_len = existing.close_range.end - existing.open_range.start;
26507 if len > existing_len {
26508 continue;
26509 }
26510 }
26511
26512 result = Some(pair);
26513 }
26514
26515 result
26516 };
26517 let Some(pair) = pair else {
26518 return false;
26519 };
26520 pair.newline_only
26521 && buffer
26522 .chars_for_range(pair.open_range.end..range.start.0)
26523 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
26524 .all(|c| c.is_whitespace() && c != '\n')
26525 }
26526}
26527
26528fn update_uncommitted_diff_for_buffer(
26529 editor: Entity<Editor>,
26530 project: &Entity<Project>,
26531 buffers: impl IntoIterator<Item = Entity<Buffer>>,
26532 buffer: Entity<MultiBuffer>,
26533 cx: &mut App,
26534) -> Task<()> {
26535 let mut tasks = Vec::new();
26536 project.update(cx, |project, cx| {
26537 for buffer in buffers {
26538 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
26539 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
26540 }
26541 }
26542 });
26543 cx.spawn(async move |cx| {
26544 let diffs = future::join_all(tasks).await;
26545 if editor.read_with(cx, |editor, _cx| editor.temporary_diff_override) {
26546 return;
26547 }
26548
26549 buffer.update(cx, |buffer, cx| {
26550 for diff in diffs.into_iter().flatten() {
26551 buffer.add_diff(diff, cx);
26552 }
26553 });
26554 })
26555}
26556
26557fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
26558 let tab_size = tab_size.get() as usize;
26559 let mut width = offset;
26560
26561 for ch in text.chars() {
26562 width += if ch == '\t' {
26563 tab_size - (width % tab_size)
26564 } else {
26565 1
26566 };
26567 }
26568
26569 width - offset
26570}
26571
26572#[cfg(test)]
26573mod tests {
26574 use super::*;
26575
26576 #[test]
26577 fn test_string_size_with_expanded_tabs() {
26578 let nz = |val| NonZeroU32::new(val).unwrap();
26579 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
26580 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
26581 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
26582 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
26583 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
26584 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
26585 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
26586 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
26587 }
26588}
26589
26590/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
26591struct WordBreakingTokenizer<'a> {
26592 input: &'a str,
26593}
26594
26595impl<'a> WordBreakingTokenizer<'a> {
26596 fn new(input: &'a str) -> Self {
26597 Self { input }
26598 }
26599}
26600
26601fn is_char_ideographic(ch: char) -> bool {
26602 use unicode_script::Script::*;
26603 use unicode_script::UnicodeScript;
26604 matches!(ch.script(), Han | Tangut | Yi)
26605}
26606
26607fn is_grapheme_ideographic(text: &str) -> bool {
26608 text.chars().any(is_char_ideographic)
26609}
26610
26611fn is_grapheme_whitespace(text: &str) -> bool {
26612 text.chars().any(|x| x.is_whitespace())
26613}
26614
26615fn should_stay_with_preceding_ideograph(text: &str) -> bool {
26616 text.chars()
26617 .next()
26618 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
26619}
26620
26621#[derive(PartialEq, Eq, Debug, Clone, Copy)]
26622enum WordBreakToken<'a> {
26623 Word { token: &'a str, grapheme_len: usize },
26624 InlineWhitespace { token: &'a str, grapheme_len: usize },
26625 Newline,
26626}
26627
26628impl<'a> Iterator for WordBreakingTokenizer<'a> {
26629 /// Yields a span, the count of graphemes in the token, and whether it was
26630 /// whitespace. Note that it also breaks at word boundaries.
26631 type Item = WordBreakToken<'a>;
26632
26633 fn next(&mut self) -> Option<Self::Item> {
26634 use unicode_segmentation::UnicodeSegmentation;
26635 if self.input.is_empty() {
26636 return None;
26637 }
26638
26639 let mut iter = self.input.graphemes(true).peekable();
26640 let mut offset = 0;
26641 let mut grapheme_len = 0;
26642 if let Some(first_grapheme) = iter.next() {
26643 let is_newline = first_grapheme == "\n";
26644 let is_whitespace = is_grapheme_whitespace(first_grapheme);
26645 offset += first_grapheme.len();
26646 grapheme_len += 1;
26647 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
26648 if let Some(grapheme) = iter.peek().copied()
26649 && should_stay_with_preceding_ideograph(grapheme)
26650 {
26651 offset += grapheme.len();
26652 grapheme_len += 1;
26653 }
26654 } else {
26655 let mut words = self.input[offset..].split_word_bound_indices().peekable();
26656 let mut next_word_bound = words.peek().copied();
26657 if next_word_bound.is_some_and(|(i, _)| i == 0) {
26658 next_word_bound = words.next();
26659 }
26660 while let Some(grapheme) = iter.peek().copied() {
26661 if next_word_bound.is_some_and(|(i, _)| i == offset) {
26662 break;
26663 };
26664 if is_grapheme_whitespace(grapheme) != is_whitespace
26665 || (grapheme == "\n") != is_newline
26666 {
26667 break;
26668 };
26669 offset += grapheme.len();
26670 grapheme_len += 1;
26671 iter.next();
26672 }
26673 }
26674 let token = &self.input[..offset];
26675 self.input = &self.input[offset..];
26676 if token == "\n" {
26677 Some(WordBreakToken::Newline)
26678 } else if is_whitespace {
26679 Some(WordBreakToken::InlineWhitespace {
26680 token,
26681 grapheme_len,
26682 })
26683 } else {
26684 Some(WordBreakToken::Word {
26685 token,
26686 grapheme_len,
26687 })
26688 }
26689 } else {
26690 None
26691 }
26692 }
26693}
26694
26695#[test]
26696fn test_word_breaking_tokenizer() {
26697 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
26698 ("", &[]),
26699 (" ", &[whitespace(" ", 2)]),
26700 ("Ʒ", &[word("Ʒ", 1)]),
26701 ("Ǽ", &[word("Ǽ", 1)]),
26702 ("⋑", &[word("⋑", 1)]),
26703 ("⋑⋑", &[word("⋑⋑", 2)]),
26704 (
26705 "原理,进而",
26706 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
26707 ),
26708 (
26709 "hello world",
26710 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
26711 ),
26712 (
26713 "hello, world",
26714 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
26715 ),
26716 (
26717 " hello world",
26718 &[
26719 whitespace(" ", 2),
26720 word("hello", 5),
26721 whitespace(" ", 1),
26722 word("world", 5),
26723 ],
26724 ),
26725 (
26726 "这是什么 \n 钢笔",
26727 &[
26728 word("这", 1),
26729 word("是", 1),
26730 word("什", 1),
26731 word("么", 1),
26732 whitespace(" ", 1),
26733 newline(),
26734 whitespace(" ", 1),
26735 word("钢", 1),
26736 word("笔", 1),
26737 ],
26738 ),
26739 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
26740 ];
26741
26742 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26743 WordBreakToken::Word {
26744 token,
26745 grapheme_len,
26746 }
26747 }
26748
26749 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26750 WordBreakToken::InlineWhitespace {
26751 token,
26752 grapheme_len,
26753 }
26754 }
26755
26756 fn newline() -> WordBreakToken<'static> {
26757 WordBreakToken::Newline
26758 }
26759
26760 for (input, result) in tests {
26761 assert_eq!(
26762 WordBreakingTokenizer::new(input)
26763 .collect::<Vec<_>>()
26764 .as_slice(),
26765 *result,
26766 );
26767 }
26768}
26769
26770fn wrap_with_prefix(
26771 first_line_prefix: String,
26772 subsequent_lines_prefix: String,
26773 unwrapped_text: String,
26774 wrap_column: usize,
26775 tab_size: NonZeroU32,
26776 preserve_existing_whitespace: bool,
26777) -> String {
26778 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
26779 let subsequent_lines_prefix_len =
26780 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
26781 let mut wrapped_text = String::new();
26782 let mut current_line = first_line_prefix;
26783 let mut is_first_line = true;
26784
26785 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
26786 let mut current_line_len = first_line_prefix_len;
26787 let mut in_whitespace = false;
26788 for token in tokenizer {
26789 let have_preceding_whitespace = in_whitespace;
26790 match token {
26791 WordBreakToken::Word {
26792 token,
26793 grapheme_len,
26794 } => {
26795 in_whitespace = false;
26796 let current_prefix_len = if is_first_line {
26797 first_line_prefix_len
26798 } else {
26799 subsequent_lines_prefix_len
26800 };
26801 if current_line_len + grapheme_len > wrap_column
26802 && current_line_len != current_prefix_len
26803 {
26804 wrapped_text.push_str(current_line.trim_end());
26805 wrapped_text.push('\n');
26806 is_first_line = false;
26807 current_line = subsequent_lines_prefix.clone();
26808 current_line_len = subsequent_lines_prefix_len;
26809 }
26810 current_line.push_str(token);
26811 current_line_len += grapheme_len;
26812 }
26813 WordBreakToken::InlineWhitespace {
26814 mut token,
26815 mut grapheme_len,
26816 } => {
26817 in_whitespace = true;
26818 if have_preceding_whitespace && !preserve_existing_whitespace {
26819 continue;
26820 }
26821 if !preserve_existing_whitespace {
26822 // Keep a single whitespace grapheme as-is
26823 if let Some(first) =
26824 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
26825 {
26826 token = first;
26827 } else {
26828 token = " ";
26829 }
26830 grapheme_len = 1;
26831 }
26832 let current_prefix_len = if is_first_line {
26833 first_line_prefix_len
26834 } else {
26835 subsequent_lines_prefix_len
26836 };
26837 if current_line_len + grapheme_len > wrap_column {
26838 wrapped_text.push_str(current_line.trim_end());
26839 wrapped_text.push('\n');
26840 is_first_line = false;
26841 current_line = subsequent_lines_prefix.clone();
26842 current_line_len = subsequent_lines_prefix_len;
26843 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
26844 current_line.push_str(token);
26845 current_line_len += grapheme_len;
26846 }
26847 }
26848 WordBreakToken::Newline => {
26849 in_whitespace = true;
26850 let current_prefix_len = if is_first_line {
26851 first_line_prefix_len
26852 } else {
26853 subsequent_lines_prefix_len
26854 };
26855 if preserve_existing_whitespace {
26856 wrapped_text.push_str(current_line.trim_end());
26857 wrapped_text.push('\n');
26858 is_first_line = false;
26859 current_line = subsequent_lines_prefix.clone();
26860 current_line_len = subsequent_lines_prefix_len;
26861 } else if have_preceding_whitespace {
26862 continue;
26863 } else if current_line_len + 1 > wrap_column
26864 && current_line_len != current_prefix_len
26865 {
26866 wrapped_text.push_str(current_line.trim_end());
26867 wrapped_text.push('\n');
26868 is_first_line = false;
26869 current_line = subsequent_lines_prefix.clone();
26870 current_line_len = subsequent_lines_prefix_len;
26871 } else if current_line_len != current_prefix_len {
26872 current_line.push(' ');
26873 current_line_len += 1;
26874 }
26875 }
26876 }
26877 }
26878
26879 if !current_line.is_empty() {
26880 wrapped_text.push_str(¤t_line);
26881 }
26882 wrapped_text
26883}
26884
26885#[test]
26886fn test_wrap_with_prefix() {
26887 assert_eq!(
26888 wrap_with_prefix(
26889 "# ".to_string(),
26890 "# ".to_string(),
26891 "abcdefg".to_string(),
26892 4,
26893 NonZeroU32::new(4).unwrap(),
26894 false,
26895 ),
26896 "# abcdefg"
26897 );
26898 assert_eq!(
26899 wrap_with_prefix(
26900 "".to_string(),
26901 "".to_string(),
26902 "\thello world".to_string(),
26903 8,
26904 NonZeroU32::new(4).unwrap(),
26905 false,
26906 ),
26907 "hello\nworld"
26908 );
26909 assert_eq!(
26910 wrap_with_prefix(
26911 "// ".to_string(),
26912 "// ".to_string(),
26913 "xx \nyy zz aa bb cc".to_string(),
26914 12,
26915 NonZeroU32::new(4).unwrap(),
26916 false,
26917 ),
26918 "// xx yy zz\n// aa bb cc"
26919 );
26920 assert_eq!(
26921 wrap_with_prefix(
26922 String::new(),
26923 String::new(),
26924 "这是什么 \n 钢笔".to_string(),
26925 3,
26926 NonZeroU32::new(4).unwrap(),
26927 false,
26928 ),
26929 "这是什\n么 钢\n笔"
26930 );
26931 assert_eq!(
26932 wrap_with_prefix(
26933 String::new(),
26934 String::new(),
26935 format!("foo{}bar", '\u{2009}'), // thin space
26936 80,
26937 NonZeroU32::new(4).unwrap(),
26938 false,
26939 ),
26940 format!("foo{}bar", '\u{2009}')
26941 );
26942}
26943
26944pub trait CollaborationHub {
26945 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
26946 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
26947 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
26948}
26949
26950impl CollaborationHub for Entity<Project> {
26951 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
26952 self.read(cx).collaborators()
26953 }
26954
26955 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
26956 self.read(cx).user_store().read(cx).participant_indices()
26957 }
26958
26959 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
26960 let this = self.read(cx);
26961 let user_ids = this.collaborators().values().map(|c| c.user_id);
26962 this.user_store().read(cx).participant_names(user_ids, cx)
26963 }
26964}
26965
26966pub trait SemanticsProvider {
26967 fn hover(
26968 &self,
26969 buffer: &Entity<Buffer>,
26970 position: text::Anchor,
26971 cx: &mut App,
26972 ) -> Option<Task<Option<Vec<project::Hover>>>>;
26973
26974 fn inline_values(
26975 &self,
26976 buffer_handle: Entity<Buffer>,
26977 range: Range<text::Anchor>,
26978 cx: &mut App,
26979 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
26980
26981 fn applicable_inlay_chunks(
26982 &self,
26983 buffer: &Entity<Buffer>,
26984 ranges: &[Range<text::Anchor>],
26985 cx: &mut App,
26986 ) -> Vec<Range<BufferRow>>;
26987
26988 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
26989
26990 fn inlay_hints(
26991 &self,
26992 invalidate: InvalidationStrategy,
26993 buffer: Entity<Buffer>,
26994 ranges: Vec<Range<text::Anchor>>,
26995 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
26996 cx: &mut App,
26997 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
26998
26999 fn semantic_tokens(
27000 &self,
27001 buffer: Entity<Buffer>,
27002 refresh: Option<RefreshForServer>,
27003 cx: &mut App,
27004 ) -> Option<Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>>>;
27005
27006 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
27007
27008 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
27009
27010 fn document_highlights(
27011 &self,
27012 buffer: &Entity<Buffer>,
27013 position: text::Anchor,
27014 cx: &mut App,
27015 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
27016
27017 fn definitions(
27018 &self,
27019 buffer: &Entity<Buffer>,
27020 position: text::Anchor,
27021 kind: GotoDefinitionKind,
27022 cx: &mut App,
27023 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
27024
27025 fn range_for_rename(
27026 &self,
27027 buffer: &Entity<Buffer>,
27028 position: text::Anchor,
27029 cx: &mut App,
27030 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
27031
27032 fn perform_rename(
27033 &self,
27034 buffer: &Entity<Buffer>,
27035 position: text::Anchor,
27036 new_name: String,
27037 cx: &mut App,
27038 ) -> Option<Task<Result<ProjectTransaction>>>;
27039}
27040
27041pub trait CompletionProvider {
27042 fn completions(
27043 &self,
27044 excerpt_id: ExcerptId,
27045 buffer: &Entity<Buffer>,
27046 buffer_position: text::Anchor,
27047 trigger: CompletionContext,
27048 window: &mut Window,
27049 cx: &mut Context<Editor>,
27050 ) -> Task<Result<Vec<CompletionResponse>>>;
27051
27052 fn resolve_completions(
27053 &self,
27054 _buffer: Entity<Buffer>,
27055 _completion_indices: Vec<usize>,
27056 _completions: Rc<RefCell<Box<[Completion]>>>,
27057 _cx: &mut Context<Editor>,
27058 ) -> Task<Result<bool>> {
27059 Task::ready(Ok(false))
27060 }
27061
27062 fn apply_additional_edits_for_completion(
27063 &self,
27064 _buffer: Entity<Buffer>,
27065 _completions: Rc<RefCell<Box<[Completion]>>>,
27066 _completion_index: usize,
27067 _push_to_history: bool,
27068 _all_commit_ranges: Vec<Range<language::Anchor>>,
27069 _cx: &mut Context<Editor>,
27070 ) -> Task<Result<Option<language::Transaction>>> {
27071 Task::ready(Ok(None))
27072 }
27073
27074 fn is_completion_trigger(
27075 &self,
27076 buffer: &Entity<Buffer>,
27077 position: language::Anchor,
27078 text: &str,
27079 trigger_in_words: bool,
27080 cx: &mut Context<Editor>,
27081 ) -> bool;
27082
27083 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
27084
27085 fn sort_completions(&self) -> bool {
27086 true
27087 }
27088
27089 fn filter_completions(&self) -> bool {
27090 true
27091 }
27092
27093 fn show_snippets(&self) -> bool {
27094 false
27095 }
27096}
27097
27098pub trait CodeActionProvider {
27099 fn id(&self) -> Arc<str>;
27100
27101 fn code_actions(
27102 &self,
27103 buffer: &Entity<Buffer>,
27104 range: Range<text::Anchor>,
27105 window: &mut Window,
27106 cx: &mut App,
27107 ) -> Task<Result<Vec<CodeAction>>>;
27108
27109 fn apply_code_action(
27110 &self,
27111 buffer_handle: Entity<Buffer>,
27112 action: CodeAction,
27113 excerpt_id: ExcerptId,
27114 push_to_history: bool,
27115 window: &mut Window,
27116 cx: &mut App,
27117 ) -> Task<Result<ProjectTransaction>>;
27118}
27119
27120impl CodeActionProvider for Entity<Project> {
27121 fn id(&self) -> Arc<str> {
27122 "project".into()
27123 }
27124
27125 fn code_actions(
27126 &self,
27127 buffer: &Entity<Buffer>,
27128 range: Range<text::Anchor>,
27129 _window: &mut Window,
27130 cx: &mut App,
27131 ) -> Task<Result<Vec<CodeAction>>> {
27132 self.update(cx, |project, cx| {
27133 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
27134 let code_actions = project.code_actions(buffer, range, None, cx);
27135 cx.background_spawn(async move {
27136 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
27137 Ok(code_lens_actions
27138 .context("code lens fetch")?
27139 .into_iter()
27140 .flatten()
27141 .chain(
27142 code_actions
27143 .context("code action fetch")?
27144 .into_iter()
27145 .flatten(),
27146 )
27147 .collect())
27148 })
27149 })
27150 }
27151
27152 fn apply_code_action(
27153 &self,
27154 buffer_handle: Entity<Buffer>,
27155 action: CodeAction,
27156 _excerpt_id: ExcerptId,
27157 push_to_history: bool,
27158 _window: &mut Window,
27159 cx: &mut App,
27160 ) -> Task<Result<ProjectTransaction>> {
27161 self.update(cx, |project, cx| {
27162 project.apply_code_action(buffer_handle, action, push_to_history, cx)
27163 })
27164 }
27165}
27166
27167fn snippet_completions(
27168 project: &Project,
27169 buffer: &Entity<Buffer>,
27170 buffer_anchor: text::Anchor,
27171 classifier: CharClassifier,
27172 cx: &mut App,
27173) -> Task<Result<CompletionResponse>> {
27174 let languages = buffer.read(cx).languages_at(buffer_anchor);
27175 let snippet_store = project.snippets().read(cx);
27176
27177 let scopes: Vec<_> = languages
27178 .iter()
27179 .filter_map(|language| {
27180 let language_name = language.lsp_id();
27181 let snippets = snippet_store.snippets_for(Some(language_name), cx);
27182
27183 if snippets.is_empty() {
27184 None
27185 } else {
27186 Some((language.default_scope(), snippets))
27187 }
27188 })
27189 .collect();
27190
27191 if scopes.is_empty() {
27192 return Task::ready(Ok(CompletionResponse {
27193 completions: vec![],
27194 display_options: CompletionDisplayOptions::default(),
27195 is_incomplete: false,
27196 }));
27197 }
27198
27199 let snapshot = buffer.read(cx).text_snapshot();
27200 let executor = cx.background_executor().clone();
27201
27202 cx.background_spawn(async move {
27203 let is_word_char = |c| classifier.is_word(c);
27204
27205 let mut is_incomplete = false;
27206 let mut completions: Vec<Completion> = Vec::new();
27207
27208 const MAX_PREFIX_LEN: usize = 128;
27209 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
27210 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
27211 let window_start = snapshot.clip_offset(window_start, Bias::Left);
27212
27213 let max_buffer_window: String = snapshot
27214 .text_for_range(window_start..buffer_offset)
27215 .collect();
27216
27217 if max_buffer_window.is_empty() {
27218 return Ok(CompletionResponse {
27219 completions: vec![],
27220 display_options: CompletionDisplayOptions::default(),
27221 is_incomplete: true,
27222 });
27223 }
27224
27225 for (_scope, snippets) in scopes.into_iter() {
27226 // Sort snippets by word count to match longer snippet prefixes first.
27227 let mut sorted_snippet_candidates = snippets
27228 .iter()
27229 .enumerate()
27230 .flat_map(|(snippet_ix, snippet)| {
27231 snippet
27232 .prefix
27233 .iter()
27234 .enumerate()
27235 .map(move |(prefix_ix, prefix)| {
27236 let word_count =
27237 snippet_candidate_suffixes(prefix, &is_word_char).count();
27238 ((snippet_ix, prefix_ix), prefix, word_count)
27239 })
27240 })
27241 .collect_vec();
27242 sorted_snippet_candidates
27243 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
27244
27245 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
27246
27247 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, &is_word_char)
27248 .take(
27249 sorted_snippet_candidates
27250 .first()
27251 .map(|(_, _, word_count)| *word_count)
27252 .unwrap_or_default(),
27253 )
27254 .collect_vec();
27255
27256 const MAX_RESULTS: usize = 100;
27257 // Each match also remembers how many characters from the buffer it consumed
27258 let mut matches: Vec<(StringMatch, usize)> = vec![];
27259
27260 let mut snippet_list_cutoff_index = 0;
27261 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
27262 let word_count = buffer_index + 1;
27263 // Increase `snippet_list_cutoff_index` until we have all of the
27264 // snippets with sufficiently many words.
27265 while sorted_snippet_candidates
27266 .get(snippet_list_cutoff_index)
27267 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
27268 *snippet_word_count >= word_count
27269 })
27270 {
27271 snippet_list_cutoff_index += 1;
27272 }
27273
27274 // Take only the candidates with at least `word_count` many words
27275 let snippet_candidates_at_word_len =
27276 &sorted_snippet_candidates[..snippet_list_cutoff_index];
27277
27278 let candidates = snippet_candidates_at_word_len
27279 .iter()
27280 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
27281 .enumerate() // index in `sorted_snippet_candidates`
27282 // First char must match
27283 .filter(|(_ix, prefix)| {
27284 itertools::equal(
27285 prefix
27286 .chars()
27287 .next()
27288 .into_iter()
27289 .flat_map(|c| c.to_lowercase()),
27290 buffer_window
27291 .chars()
27292 .next()
27293 .into_iter()
27294 .flat_map(|c| c.to_lowercase()),
27295 )
27296 })
27297 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
27298 .collect::<Vec<StringMatchCandidate>>();
27299
27300 matches.extend(
27301 fuzzy::match_strings(
27302 &candidates,
27303 &buffer_window,
27304 buffer_window.chars().any(|c| c.is_uppercase()),
27305 true,
27306 MAX_RESULTS - matches.len(), // always prioritize longer snippets
27307 &Default::default(),
27308 executor.clone(),
27309 )
27310 .await
27311 .into_iter()
27312 .map(|string_match| (string_match, buffer_window.len())),
27313 );
27314
27315 if matches.len() >= MAX_RESULTS {
27316 break;
27317 }
27318 }
27319
27320 let to_lsp = |point: &text::Anchor| {
27321 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
27322 point_to_lsp(end)
27323 };
27324 let lsp_end = to_lsp(&buffer_anchor);
27325
27326 if matches.len() >= MAX_RESULTS {
27327 is_incomplete = true;
27328 }
27329
27330 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
27331 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
27332 sorted_snippet_candidates[string_match.candidate_id];
27333 let snippet = &snippets[snippet_index];
27334 let start = buffer_offset - buffer_window_len;
27335 let start = snapshot.anchor_before(start);
27336 let range = start..buffer_anchor;
27337 let lsp_start = to_lsp(&start);
27338 let lsp_range = lsp::Range {
27339 start: lsp_start,
27340 end: lsp_end,
27341 };
27342 Completion {
27343 replace_range: range,
27344 new_text: snippet.body.clone(),
27345 source: CompletionSource::Lsp {
27346 insert_range: None,
27347 server_id: LanguageServerId(usize::MAX),
27348 resolved: true,
27349 lsp_completion: Box::new(lsp::CompletionItem {
27350 label: snippet.prefix.first().unwrap().clone(),
27351 kind: Some(CompletionItemKind::SNIPPET),
27352 label_details: snippet.description.as_ref().map(|description| {
27353 lsp::CompletionItemLabelDetails {
27354 detail: Some(description.clone()),
27355 description: None,
27356 }
27357 }),
27358 insert_text_format: Some(InsertTextFormat::SNIPPET),
27359 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
27360 lsp::InsertReplaceEdit {
27361 new_text: snippet.body.clone(),
27362 insert: lsp_range,
27363 replace: lsp_range,
27364 },
27365 )),
27366 filter_text: Some(snippet.body.clone()),
27367 sort_text: Some(char::MAX.to_string()),
27368 ..lsp::CompletionItem::default()
27369 }),
27370 lsp_defaults: None,
27371 },
27372 label: CodeLabel {
27373 text: matching_prefix.clone(),
27374 runs: Vec::new(),
27375 filter_range: 0..matching_prefix.len(),
27376 },
27377 icon_path: None,
27378 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
27379 single_line: snippet.name.clone().into(),
27380 plain_text: snippet
27381 .description
27382 .clone()
27383 .map(|description| description.into()),
27384 }),
27385 insert_text_mode: None,
27386 confirm: None,
27387 match_start: Some(start),
27388 snippet_deduplication_key: Some((snippet_index, prefix_index)),
27389 }
27390 }));
27391 }
27392
27393 Ok(CompletionResponse {
27394 completions,
27395 display_options: CompletionDisplayOptions::default(),
27396 is_incomplete,
27397 })
27398 })
27399}
27400
27401impl CompletionProvider for Entity<Project> {
27402 fn completions(
27403 &self,
27404 _excerpt_id: ExcerptId,
27405 buffer: &Entity<Buffer>,
27406 buffer_position: text::Anchor,
27407 options: CompletionContext,
27408 _window: &mut Window,
27409 cx: &mut Context<Editor>,
27410 ) -> Task<Result<Vec<CompletionResponse>>> {
27411 self.update(cx, |project, cx| {
27412 let task = project.completions(buffer, buffer_position, options, cx);
27413 cx.background_spawn(task)
27414 })
27415 }
27416
27417 fn resolve_completions(
27418 &self,
27419 buffer: Entity<Buffer>,
27420 completion_indices: Vec<usize>,
27421 completions: Rc<RefCell<Box<[Completion]>>>,
27422 cx: &mut Context<Editor>,
27423 ) -> Task<Result<bool>> {
27424 self.update(cx, |project, cx| {
27425 project.lsp_store().update(cx, |lsp_store, cx| {
27426 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
27427 })
27428 })
27429 }
27430
27431 fn apply_additional_edits_for_completion(
27432 &self,
27433 buffer: Entity<Buffer>,
27434 completions: Rc<RefCell<Box<[Completion]>>>,
27435 completion_index: usize,
27436 push_to_history: bool,
27437 all_commit_ranges: Vec<Range<language::Anchor>>,
27438 cx: &mut Context<Editor>,
27439 ) -> Task<Result<Option<language::Transaction>>> {
27440 self.update(cx, |project, cx| {
27441 project.lsp_store().update(cx, |lsp_store, cx| {
27442 lsp_store.apply_additional_edits_for_completion(
27443 buffer,
27444 completions,
27445 completion_index,
27446 push_to_history,
27447 all_commit_ranges,
27448 cx,
27449 )
27450 })
27451 })
27452 }
27453
27454 fn is_completion_trigger(
27455 &self,
27456 buffer: &Entity<Buffer>,
27457 position: language::Anchor,
27458 text: &str,
27459 trigger_in_words: bool,
27460 cx: &mut Context<Editor>,
27461 ) -> bool {
27462 let mut chars = text.chars();
27463 let char = if let Some(char) = chars.next() {
27464 char
27465 } else {
27466 return false;
27467 };
27468 if chars.next().is_some() {
27469 return false;
27470 }
27471
27472 let buffer = buffer.read(cx);
27473 let snapshot = buffer.snapshot();
27474 let classifier = snapshot
27475 .char_classifier_at(position)
27476 .scope_context(Some(CharScopeContext::Completion));
27477 if trigger_in_words && classifier.is_word(char) {
27478 return true;
27479 }
27480
27481 buffer.completion_triggers().contains(text)
27482 }
27483
27484 fn show_snippets(&self) -> bool {
27485 true
27486 }
27487}
27488
27489impl SemanticsProvider for WeakEntity<Project> {
27490 fn hover(
27491 &self,
27492 buffer: &Entity<Buffer>,
27493 position: text::Anchor,
27494 cx: &mut App,
27495 ) -> Option<Task<Option<Vec<project::Hover>>>> {
27496 self.update(cx, |project, cx| project.hover(buffer, position, cx))
27497 .ok()
27498 }
27499
27500 fn document_highlights(
27501 &self,
27502 buffer: &Entity<Buffer>,
27503 position: text::Anchor,
27504 cx: &mut App,
27505 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
27506 self.update(cx, |project, cx| {
27507 project.document_highlights(buffer, position, cx)
27508 })
27509 .ok()
27510 }
27511
27512 fn definitions(
27513 &self,
27514 buffer: &Entity<Buffer>,
27515 position: text::Anchor,
27516 kind: GotoDefinitionKind,
27517 cx: &mut App,
27518 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
27519 self.update(cx, |project, cx| match kind {
27520 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
27521 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
27522 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
27523 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
27524 })
27525 .ok()
27526 }
27527
27528 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27529 self.update(cx, |project, cx| {
27530 if project
27531 .active_debug_session(cx)
27532 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
27533 {
27534 return true;
27535 }
27536
27537 buffer.update(cx, |buffer, cx| {
27538 project.any_language_server_supports_inlay_hints(buffer, cx)
27539 })
27540 })
27541 .unwrap_or(false)
27542 }
27543
27544 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27545 self.update(cx, |project, cx| {
27546 buffer.update(cx, |buffer, cx| {
27547 project.any_language_server_supports_semantic_tokens(buffer, cx)
27548 })
27549 })
27550 .unwrap_or(false)
27551 }
27552
27553 fn inline_values(
27554 &self,
27555 buffer_handle: Entity<Buffer>,
27556 range: Range<text::Anchor>,
27557 cx: &mut App,
27558 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
27559 self.update(cx, |project, cx| {
27560 let (session, active_stack_frame) = project.active_debug_session(cx)?;
27561
27562 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
27563 })
27564 .ok()
27565 .flatten()
27566 }
27567
27568 fn applicable_inlay_chunks(
27569 &self,
27570 buffer: &Entity<Buffer>,
27571 ranges: &[Range<text::Anchor>],
27572 cx: &mut App,
27573 ) -> Vec<Range<BufferRow>> {
27574 self.update(cx, |project, cx| {
27575 project.lsp_store().update(cx, |lsp_store, cx| {
27576 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
27577 })
27578 })
27579 .unwrap_or_default()
27580 }
27581
27582 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
27583 self.update(cx, |project, cx| {
27584 project.lsp_store().update(cx, |lsp_store, _| {
27585 lsp_store.invalidate_inlay_hints(for_buffers)
27586 })
27587 })
27588 .ok();
27589 }
27590
27591 fn inlay_hints(
27592 &self,
27593 invalidate: InvalidationStrategy,
27594 buffer: Entity<Buffer>,
27595 ranges: Vec<Range<text::Anchor>>,
27596 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
27597 cx: &mut App,
27598 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
27599 self.update(cx, |project, cx| {
27600 project.lsp_store().update(cx, |lsp_store, cx| {
27601 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
27602 })
27603 })
27604 .ok()
27605 }
27606
27607 fn semantic_tokens(
27608 &self,
27609 buffer: Entity<Buffer>,
27610 refresh: Option<RefreshForServer>,
27611 cx: &mut App,
27612 ) -> Option<Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>>> {
27613 self.update(cx, |this, cx| {
27614 this.lsp_store().update(cx, |lsp_store, cx| {
27615 lsp_store.semantic_tokens(buffer, refresh, cx)
27616 })
27617 })
27618 .ok()
27619 }
27620
27621 fn range_for_rename(
27622 &self,
27623 buffer: &Entity<Buffer>,
27624 position: text::Anchor,
27625 cx: &mut App,
27626 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
27627 self.update(cx, |project, cx| {
27628 let buffer = buffer.clone();
27629 let task = project.prepare_rename(buffer.clone(), position, cx);
27630 cx.spawn(async move |_, cx| {
27631 Ok(match task.await? {
27632 PrepareRenameResponse::Success(range) => Some(range),
27633 PrepareRenameResponse::InvalidPosition => None,
27634 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
27635 // Fallback on using TreeSitter info to determine identifier range
27636 buffer.read_with(cx, |buffer, _| {
27637 let snapshot = buffer.snapshot();
27638 let (range, kind) = snapshot.surrounding_word(position, None);
27639 if kind != Some(CharKind::Word) {
27640 return None;
27641 }
27642 Some(
27643 snapshot.anchor_before(range.start)
27644 ..snapshot.anchor_after(range.end),
27645 )
27646 })
27647 }
27648 })
27649 })
27650 })
27651 .ok()
27652 }
27653
27654 fn perform_rename(
27655 &self,
27656 buffer: &Entity<Buffer>,
27657 position: text::Anchor,
27658 new_name: String,
27659 cx: &mut App,
27660 ) -> Option<Task<Result<ProjectTransaction>>> {
27661 self.update(cx, |project, cx| {
27662 project.perform_rename(buffer.clone(), position, new_name, cx)
27663 })
27664 .ok()
27665 }
27666}
27667
27668fn consume_contiguous_rows(
27669 contiguous_row_selections: &mut Vec<Selection<Point>>,
27670 selection: &Selection<Point>,
27671 display_map: &DisplaySnapshot,
27672 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
27673) -> (MultiBufferRow, MultiBufferRow) {
27674 contiguous_row_selections.push(selection.clone());
27675 let start_row = starting_row(selection, display_map);
27676 let mut end_row = ending_row(selection, display_map);
27677
27678 while let Some(next_selection) = selections.peek() {
27679 if next_selection.start.row <= end_row.0 {
27680 end_row = ending_row(next_selection, display_map);
27681 contiguous_row_selections.push(selections.next().unwrap().clone());
27682 } else {
27683 break;
27684 }
27685 }
27686 (start_row, end_row)
27687}
27688
27689fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27690 if selection.start.column > 0 {
27691 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
27692 } else {
27693 MultiBufferRow(selection.start.row)
27694 }
27695}
27696
27697fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27698 if next_selection.end.column > 0 || next_selection.is_empty() {
27699 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
27700 } else {
27701 MultiBufferRow(next_selection.end.row)
27702 }
27703}
27704
27705impl EditorSnapshot {
27706 pub fn remote_selections_in_range<'a>(
27707 &'a self,
27708 range: &'a Range<Anchor>,
27709 collaboration_hub: &dyn CollaborationHub,
27710 cx: &'a App,
27711 ) -> impl 'a + Iterator<Item = RemoteSelection> {
27712 let participant_names = collaboration_hub.user_names(cx);
27713 let participant_indices = collaboration_hub.user_participant_indices(cx);
27714 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
27715 let collaborators_by_replica_id = collaborators_by_peer_id
27716 .values()
27717 .map(|collaborator| (collaborator.replica_id, collaborator))
27718 .collect::<HashMap<_, _>>();
27719 self.buffer_snapshot()
27720 .selections_in_range(range, false)
27721 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
27722 if replica_id == ReplicaId::AGENT {
27723 Some(RemoteSelection {
27724 replica_id,
27725 selection,
27726 cursor_shape,
27727 line_mode,
27728 collaborator_id: CollaboratorId::Agent,
27729 user_name: Some("Agent".into()),
27730 color: cx.theme().players().agent(),
27731 })
27732 } else {
27733 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
27734 let participant_index = participant_indices.get(&collaborator.user_id).copied();
27735 let user_name = participant_names.get(&collaborator.user_id).cloned();
27736 Some(RemoteSelection {
27737 replica_id,
27738 selection,
27739 cursor_shape,
27740 line_mode,
27741 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
27742 user_name,
27743 color: if let Some(index) = participant_index {
27744 cx.theme().players().color_for_participant(index.0)
27745 } else {
27746 cx.theme().players().absent()
27747 },
27748 })
27749 }
27750 })
27751 }
27752
27753 pub fn hunks_for_ranges(
27754 &self,
27755 ranges: impl IntoIterator<Item = Range<Point>>,
27756 ) -> Vec<MultiBufferDiffHunk> {
27757 let mut hunks = Vec::new();
27758 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
27759 HashMap::default();
27760 for query_range in ranges {
27761 let query_rows =
27762 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
27763 for hunk in self.buffer_snapshot().diff_hunks_in_range(
27764 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
27765 ) {
27766 // Include deleted hunks that are adjacent to the query range, because
27767 // otherwise they would be missed.
27768 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
27769 if hunk.status().is_deleted() {
27770 intersects_range |= hunk.row_range.start == query_rows.end;
27771 intersects_range |= hunk.row_range.end == query_rows.start;
27772 }
27773 if intersects_range {
27774 if !processed_buffer_rows
27775 .entry(hunk.buffer_id)
27776 .or_default()
27777 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
27778 {
27779 continue;
27780 }
27781 hunks.push(hunk);
27782 }
27783 }
27784 }
27785
27786 hunks
27787 }
27788
27789 fn display_diff_hunks_for_rows<'a>(
27790 &'a self,
27791 display_rows: Range<DisplayRow>,
27792 folded_buffers: &'a HashSet<BufferId>,
27793 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
27794 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
27795 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
27796
27797 self.buffer_snapshot()
27798 .diff_hunks_in_range(buffer_start..buffer_end)
27799 .filter_map(|hunk| {
27800 if folded_buffers.contains(&hunk.buffer_id)
27801 || (hunk.row_range.is_empty() && self.buffer.all_diff_hunks_expanded())
27802 {
27803 return None;
27804 }
27805
27806 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
27807 let hunk_end_point = if hunk.row_range.end > hunk.row_range.start {
27808 let last_row = MultiBufferRow(hunk.row_range.end.0 - 1);
27809 let line_len = self.buffer_snapshot().line_len(last_row);
27810 Point::new(last_row.0, line_len)
27811 } else {
27812 Point::new(hunk.row_range.end.0, 0)
27813 };
27814
27815 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
27816 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
27817
27818 let display_hunk = if hunk_display_start.column() != 0 {
27819 DisplayDiffHunk::Folded {
27820 display_row: hunk_display_start.row(),
27821 }
27822 } else {
27823 let mut end_row = hunk_display_end.row();
27824 if hunk.row_range.end > hunk.row_range.start || hunk_display_end.column() > 0 {
27825 end_row.0 += 1;
27826 }
27827 let is_created_file = hunk.is_created_file();
27828
27829 DisplayDiffHunk::Unfolded {
27830 status: hunk.status(),
27831 diff_base_byte_range: hunk.diff_base_byte_range.start.0
27832 ..hunk.diff_base_byte_range.end.0,
27833 word_diffs: hunk.word_diffs,
27834 display_row_range: hunk_display_start.row()..end_row,
27835 multi_buffer_range: Anchor::range_in_buffer(
27836 hunk.excerpt_id,
27837 hunk.buffer_range,
27838 ),
27839 is_created_file,
27840 }
27841 };
27842
27843 Some(display_hunk)
27844 })
27845 }
27846
27847 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
27848 self.display_snapshot
27849 .buffer_snapshot()
27850 .language_at(position)
27851 }
27852
27853 pub fn is_focused(&self) -> bool {
27854 self.is_focused
27855 }
27856
27857 pub fn placeholder_text(&self) -> Option<String> {
27858 self.placeholder_display_snapshot
27859 .as_ref()
27860 .map(|display_map| display_map.text())
27861 }
27862
27863 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
27864 self.scroll_anchor.scroll_position(&self.display_snapshot)
27865 }
27866
27867 pub fn gutter_dimensions(
27868 &self,
27869 font_id: FontId,
27870 font_size: Pixels,
27871 style: &EditorStyle,
27872 window: &mut Window,
27873 cx: &App,
27874 ) -> GutterDimensions {
27875 if self.show_gutter
27876 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
27877 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
27878 {
27879 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
27880 matches!(
27881 ProjectSettings::get_global(cx).git.git_gutter,
27882 GitGutterSetting::TrackedFiles
27883 )
27884 });
27885 let gutter_settings = EditorSettings::get_global(cx).gutter;
27886 let show_line_numbers = self
27887 .show_line_numbers
27888 .unwrap_or(gutter_settings.line_numbers);
27889 let line_gutter_width = if show_line_numbers {
27890 // Avoid flicker-like gutter resizes when the line number gains another digit by
27891 // only resizing the gutter on files with > 10**min_line_number_digits lines.
27892 let min_width_for_number_on_gutter =
27893 ch_advance * gutter_settings.min_line_number_digits as f32;
27894 self.max_line_number_width(style, window)
27895 .max(min_width_for_number_on_gutter)
27896 } else {
27897 0.0.into()
27898 };
27899
27900 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
27901 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
27902
27903 let git_blame_entries_width =
27904 self.git_blame_gutter_max_author_length
27905 .map(|max_author_length| {
27906 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
27907 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
27908
27909 /// The number of characters to dedicate to gaps and margins.
27910 const SPACING_WIDTH: usize = 4;
27911
27912 let max_char_count = max_author_length.min(renderer.max_author_length())
27913 + ::git::SHORT_SHA_LENGTH
27914 + MAX_RELATIVE_TIMESTAMP.len()
27915 + SPACING_WIDTH;
27916
27917 ch_advance * max_char_count
27918 });
27919
27920 let is_singleton = self.buffer_snapshot().is_singleton();
27921
27922 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
27923 left_padding += if !is_singleton {
27924 ch_width * 4.0
27925 } else if show_runnables || show_breakpoints {
27926 ch_width * 3.0
27927 } else if show_git_gutter && show_line_numbers {
27928 ch_width * 2.0
27929 } else if show_git_gutter || show_line_numbers {
27930 ch_width
27931 } else {
27932 px(0.)
27933 };
27934
27935 let shows_folds = is_singleton && gutter_settings.folds;
27936
27937 let right_padding = if shows_folds && show_line_numbers {
27938 ch_width * 4.0
27939 } else if shows_folds || (!is_singleton && show_line_numbers) {
27940 ch_width * 3.0
27941 } else if show_line_numbers {
27942 ch_width
27943 } else {
27944 px(0.)
27945 };
27946
27947 GutterDimensions {
27948 left_padding,
27949 right_padding,
27950 width: line_gutter_width + left_padding + right_padding,
27951 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
27952 git_blame_entries_width,
27953 }
27954 } else if self.offset_content {
27955 GutterDimensions::default_with_margin(font_id, font_size, cx)
27956 } else {
27957 GutterDimensions::default()
27958 }
27959 }
27960
27961 pub fn render_crease_toggle(
27962 &self,
27963 buffer_row: MultiBufferRow,
27964 row_contains_cursor: bool,
27965 editor: Entity<Editor>,
27966 window: &mut Window,
27967 cx: &mut App,
27968 ) -> Option<AnyElement> {
27969 let folded = self.is_line_folded(buffer_row);
27970 let mut is_foldable = false;
27971
27972 if let Some(crease) = self
27973 .crease_snapshot
27974 .query_row(buffer_row, self.buffer_snapshot())
27975 {
27976 is_foldable = true;
27977 match crease {
27978 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
27979 if let Some(render_toggle) = render_toggle {
27980 let toggle_callback =
27981 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
27982 if folded {
27983 editor.update(cx, |editor, cx| {
27984 editor.fold_at(buffer_row, window, cx)
27985 });
27986 } else {
27987 editor.update(cx, |editor, cx| {
27988 editor.unfold_at(buffer_row, window, cx)
27989 });
27990 }
27991 });
27992 return Some((render_toggle)(
27993 buffer_row,
27994 folded,
27995 toggle_callback,
27996 window,
27997 cx,
27998 ));
27999 }
28000 }
28001 }
28002 }
28003
28004 is_foldable |= !self.use_lsp_folding_ranges && self.starts_indent(buffer_row);
28005
28006 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
28007 Some(
28008 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
28009 .toggle_state(folded)
28010 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
28011 if folded {
28012 this.unfold_at(buffer_row, window, cx);
28013 } else {
28014 this.fold_at(buffer_row, window, cx);
28015 }
28016 }))
28017 .into_any_element(),
28018 )
28019 } else {
28020 None
28021 }
28022 }
28023
28024 pub fn render_crease_trailer(
28025 &self,
28026 buffer_row: MultiBufferRow,
28027 window: &mut Window,
28028 cx: &mut App,
28029 ) -> Option<AnyElement> {
28030 let folded = self.is_line_folded(buffer_row);
28031 if let Crease::Inline { render_trailer, .. } = self
28032 .crease_snapshot
28033 .query_row(buffer_row, self.buffer_snapshot())?
28034 {
28035 let render_trailer = render_trailer.as_ref()?;
28036 Some(render_trailer(buffer_row, folded, window, cx))
28037 } else {
28038 None
28039 }
28040 }
28041
28042 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
28043 let digit_count = self.widest_line_number().ilog10() + 1;
28044 column_pixels(style, digit_count as usize, window)
28045 }
28046
28047 /// Returns the line delta from `base` to `line` in the multibuffer, ignoring wrapped lines.
28048 ///
28049 /// This is positive if `base` is before `line`.
28050 fn relative_line_delta(
28051 &self,
28052 current_selection_head: DisplayRow,
28053 first_visible_row: DisplayRow,
28054 consider_wrapped_lines: bool,
28055 ) -> i64 {
28056 let current_selection_head = current_selection_head.as_display_point().to_point(self);
28057 let first_visible_row = first_visible_row.as_display_point().to_point(self);
28058
28059 if consider_wrapped_lines {
28060 let wrap_snapshot = self.wrap_snapshot();
28061 let base_wrap_row = wrap_snapshot
28062 .make_wrap_point(current_selection_head, Bias::Left)
28063 .row();
28064 let wrap_row = wrap_snapshot
28065 .make_wrap_point(first_visible_row, Bias::Left)
28066 .row();
28067
28068 wrap_row.0 as i64 - base_wrap_row.0 as i64
28069 } else {
28070 let fold_snapshot = self.fold_snapshot();
28071 let base_fold_row = fold_snapshot
28072 .to_fold_point(self.to_inlay_point(current_selection_head), Bias::Left)
28073 .row();
28074 let fold_row = fold_snapshot
28075 .to_fold_point(self.to_inlay_point(first_visible_row), Bias::Left)
28076 .row();
28077
28078 fold_row as i64 - base_fold_row as i64
28079 }
28080 }
28081
28082 /// Returns the unsigned relative line number to display for each row in `rows`.
28083 ///
28084 /// Wrapped rows are excluded from the hashmap if `count_relative_lines` is `false`.
28085 pub fn calculate_relative_line_numbers(
28086 &self,
28087 rows: &Range<DisplayRow>,
28088 current_selection_head: DisplayRow,
28089 count_wrapped_lines: bool,
28090 ) -> HashMap<DisplayRow, u32> {
28091 let initial_offset =
28092 self.relative_line_delta(current_selection_head, rows.start, count_wrapped_lines);
28093
28094 self.row_infos(rows.start)
28095 .take(rows.len())
28096 .enumerate()
28097 .map(|(i, row_info)| (DisplayRow(rows.start.0 + i as u32), row_info))
28098 .filter(|(_row, row_info)| {
28099 row_info.buffer_row.is_some()
28100 || (count_wrapped_lines && row_info.wrapped_buffer_row.is_some())
28101 })
28102 .enumerate()
28103 .filter_map(|(i, (row, row_info))| {
28104 // We want to ensure here that the current line has absolute
28105 // numbering, even if we are in a soft-wrapped line. With the
28106 // exception that if we are in a deleted line, we should number this
28107 // relative with 0, as otherwise it would have no line number at all
28108 let relative_line_number = (initial_offset + i as i64).unsigned_abs() as u32;
28109
28110 (relative_line_number != 0
28111 || row_info
28112 .diff_status
28113 .is_some_and(|status| status.is_deleted()))
28114 .then_some((row, relative_line_number))
28115 })
28116 .collect()
28117 }
28118}
28119
28120pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
28121 let font_size = style.text.font_size.to_pixels(window.rem_size());
28122 let layout = window.text_system().shape_line(
28123 SharedString::from(" ".repeat(column)),
28124 font_size,
28125 &[TextRun {
28126 len: column,
28127 font: style.text.font(),
28128 color: Hsla::default(),
28129 ..Default::default()
28130 }],
28131 None,
28132 );
28133
28134 layout.width
28135}
28136
28137impl Deref for EditorSnapshot {
28138 type Target = DisplaySnapshot;
28139
28140 fn deref(&self) -> &Self::Target {
28141 &self.display_snapshot
28142 }
28143}
28144
28145#[derive(Clone, Debug, PartialEq, Eq)]
28146pub enum EditorEvent {
28147 /// Emitted when the stored review comments change (added, removed, or updated).
28148 ReviewCommentsChanged {
28149 /// The new total count of review comments.
28150 total_count: usize,
28151 },
28152 InputIgnored {
28153 text: Arc<str>,
28154 },
28155 InputHandled {
28156 utf16_range_to_replace: Option<Range<isize>>,
28157 text: Arc<str>,
28158 },
28159 ExcerptsAdded {
28160 buffer: Entity<Buffer>,
28161 predecessor: ExcerptId,
28162 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
28163 },
28164 ExcerptsRemoved {
28165 ids: Vec<ExcerptId>,
28166 removed_buffer_ids: Vec<BufferId>,
28167 },
28168 BufferFoldToggled {
28169 ids: Vec<ExcerptId>,
28170 folded: bool,
28171 },
28172 ExcerptsEdited {
28173 ids: Vec<ExcerptId>,
28174 },
28175 ExcerptsExpanded {
28176 ids: Vec<ExcerptId>,
28177 },
28178 ExpandExcerptsRequested {
28179 excerpt_ids: Vec<ExcerptId>,
28180 lines: u32,
28181 direction: ExpandExcerptDirection,
28182 },
28183 StageOrUnstageRequested {
28184 stage: bool,
28185 hunks: Vec<MultiBufferDiffHunk>,
28186 },
28187 OpenExcerptsRequested {
28188 selections_by_buffer: HashMap<BufferId, (Vec<Range<BufferOffset>>, Option<u32>)>,
28189 split: bool,
28190 },
28191 RestoreRequested {
28192 hunks: Vec<MultiBufferDiffHunk>,
28193 },
28194 BufferEdited,
28195 Edited {
28196 transaction_id: clock::Lamport,
28197 },
28198 Reparsed(BufferId),
28199 Focused,
28200 FocusedIn,
28201 Blurred,
28202 DirtyChanged,
28203 Saved,
28204 TitleChanged,
28205 SelectionsChanged {
28206 local: bool,
28207 },
28208 ScrollPositionChanged {
28209 local: bool,
28210 autoscroll: bool,
28211 },
28212 TransactionUndone {
28213 transaction_id: clock::Lamport,
28214 },
28215 TransactionBegun {
28216 transaction_id: clock::Lamport,
28217 },
28218 CursorShapeChanged,
28219 BreadcrumbsChanged,
28220 OutlineSymbolsChanged,
28221 PushedToNavHistory {
28222 anchor: Anchor,
28223 is_deactivate: bool,
28224 },
28225}
28226
28227impl EventEmitter<EditorEvent> for Editor {}
28228
28229impl Focusable for Editor {
28230 fn focus_handle(&self, _cx: &App) -> FocusHandle {
28231 self.focus_handle.clone()
28232 }
28233}
28234
28235impl Render for Editor {
28236 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28237 EditorElement::new(&cx.entity(), self.create_style(cx))
28238 }
28239}
28240
28241impl EntityInputHandler for Editor {
28242 fn text_for_range(
28243 &mut self,
28244 range_utf16: Range<usize>,
28245 adjusted_range: &mut Option<Range<usize>>,
28246 _: &mut Window,
28247 cx: &mut Context<Self>,
28248 ) -> Option<String> {
28249 let snapshot = self.buffer.read(cx).read(cx);
28250 let start = snapshot.clip_offset_utf16(
28251 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
28252 Bias::Left,
28253 );
28254 let end = snapshot.clip_offset_utf16(
28255 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
28256 Bias::Right,
28257 );
28258 if (start.0.0..end.0.0) != range_utf16 {
28259 adjusted_range.replace(start.0.0..end.0.0);
28260 }
28261 Some(snapshot.text_for_range(start..end).collect())
28262 }
28263
28264 fn selected_text_range(
28265 &mut self,
28266 ignore_disabled_input: bool,
28267 _: &mut Window,
28268 cx: &mut Context<Self>,
28269 ) -> Option<UTF16Selection> {
28270 // Prevent the IME menu from appearing when holding down an alphabetic key
28271 // while input is disabled.
28272 if !ignore_disabled_input && !self.input_enabled {
28273 return None;
28274 }
28275
28276 let selection = self
28277 .selections
28278 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
28279 let range = selection.range();
28280
28281 Some(UTF16Selection {
28282 range: range.start.0.0..range.end.0.0,
28283 reversed: selection.reversed,
28284 })
28285 }
28286
28287 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
28288 let snapshot = self.buffer.read(cx).read(cx);
28289 let range = self
28290 .text_highlights(HighlightKey::InputComposition, cx)?
28291 .1
28292 .first()?;
28293 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
28294 }
28295
28296 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
28297 self.clear_highlights(HighlightKey::InputComposition, cx);
28298 self.ime_transaction.take();
28299 }
28300
28301 fn replace_text_in_range(
28302 &mut self,
28303 range_utf16: Option<Range<usize>>,
28304 text: &str,
28305 window: &mut Window,
28306 cx: &mut Context<Self>,
28307 ) {
28308 if !self.input_enabled {
28309 cx.emit(EditorEvent::InputIgnored { text: text.into() });
28310 return;
28311 }
28312
28313 self.transact(window, cx, |this, window, cx| {
28314 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
28315 if let Some(marked_ranges) = this.marked_text_ranges(cx) {
28316 // During IME composition, macOS reports the replacement range
28317 // relative to the first marked region (the only one visible via
28318 // marked_text_range). The correct targets for replacement are the
28319 // marked ranges themselves — one per cursor — so use them directly.
28320 Some(marked_ranges)
28321 } else if range_utf16.start == range_utf16.end {
28322 // An empty replacement range means "insert at cursor" with no text
28323 // to replace. macOS reports the cursor position from its own
28324 // (single-cursor) view of the buffer, which diverges from our actual
28325 // cursor positions after multi-cursor edits have shifted offsets.
28326 // Treating this as range_utf16=None lets each cursor insert in place.
28327 None
28328 } else {
28329 // Outside of IME composition (e.g. Accessibility Keyboard word
28330 // completion), the range is an absolute document offset for the
28331 // newest cursor. Fan it out to all cursors via
28332 // selection_replacement_ranges, which applies the delta relative
28333 // to the newest selection to every cursor.
28334 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
28335 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
28336 Some(this.selection_replacement_ranges(range_utf16, cx))
28337 }
28338 } else {
28339 this.marked_text_ranges(cx)
28340 };
28341
28342 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
28343 let newest_selection_id = this.selections.newest_anchor().id;
28344 this.selections
28345 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
28346 .iter()
28347 .zip(ranges_to_replace.iter())
28348 .find_map(|(selection, range)| {
28349 if selection.id == newest_selection_id {
28350 Some(
28351 (range.start.0.0 as isize - selection.head().0.0 as isize)
28352 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
28353 )
28354 } else {
28355 None
28356 }
28357 })
28358 });
28359
28360 cx.emit(EditorEvent::InputHandled {
28361 utf16_range_to_replace: range_to_replace,
28362 text: text.into(),
28363 });
28364
28365 if let Some(new_selected_ranges) = new_selected_ranges {
28366 // Only backspace if at least one range covers actual text. When all
28367 // ranges are empty (e.g. a trailing-space insertion from Accessibility
28368 // Keyboard sends replacementRange=cursor..cursor), backspace would
28369 // incorrectly delete the character just before the cursor.
28370 let should_backspace = new_selected_ranges.iter().any(|r| r.start != r.end);
28371 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
28372 selections.select_ranges(new_selected_ranges)
28373 });
28374 if should_backspace {
28375 this.backspace(&Default::default(), window, cx);
28376 }
28377 }
28378
28379 this.handle_input(text, window, cx);
28380 });
28381
28382 if let Some(transaction) = self.ime_transaction {
28383 self.buffer.update(cx, |buffer, cx| {
28384 buffer.group_until_transaction(transaction, cx);
28385 });
28386 }
28387
28388 self.unmark_text(window, cx);
28389 }
28390
28391 fn replace_and_mark_text_in_range(
28392 &mut self,
28393 range_utf16: Option<Range<usize>>,
28394 text: &str,
28395 new_selected_range_utf16: Option<Range<usize>>,
28396 window: &mut Window,
28397 cx: &mut Context<Self>,
28398 ) {
28399 if !self.input_enabled {
28400 return;
28401 }
28402
28403 let transaction = self.transact(window, cx, |this, window, cx| {
28404 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
28405 let snapshot = this.buffer.read(cx).read(cx);
28406 if let Some(relative_range_utf16) = range_utf16.as_ref() {
28407 for marked_range in &mut marked_ranges {
28408 marked_range.end = marked_range.start + relative_range_utf16.end;
28409 marked_range.start += relative_range_utf16.start;
28410 marked_range.start =
28411 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
28412 marked_range.end =
28413 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
28414 }
28415 }
28416 Some(marked_ranges)
28417 } else if let Some(range_utf16) = range_utf16 {
28418 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
28419 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
28420 Some(this.selection_replacement_ranges(range_utf16, cx))
28421 } else {
28422 None
28423 };
28424
28425 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
28426 let newest_selection_id = this.selections.newest_anchor().id;
28427 this.selections
28428 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
28429 .iter()
28430 .zip(ranges_to_replace.iter())
28431 .find_map(|(selection, range)| {
28432 if selection.id == newest_selection_id {
28433 Some(
28434 (range.start.0.0 as isize - selection.head().0.0 as isize)
28435 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
28436 )
28437 } else {
28438 None
28439 }
28440 })
28441 });
28442
28443 cx.emit(EditorEvent::InputHandled {
28444 utf16_range_to_replace: range_to_replace,
28445 text: text.into(),
28446 });
28447
28448 if let Some(ranges) = ranges_to_replace {
28449 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28450 s.select_ranges(ranges)
28451 });
28452 }
28453
28454 let marked_ranges = {
28455 let snapshot = this.buffer.read(cx).read(cx);
28456 this.selections
28457 .disjoint_anchors_arc()
28458 .iter()
28459 .map(|selection| {
28460 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
28461 })
28462 .collect::<Vec<_>>()
28463 };
28464
28465 if text.is_empty() {
28466 this.unmark_text(window, cx);
28467 } else {
28468 this.highlight_text(
28469 HighlightKey::InputComposition,
28470 marked_ranges.clone(),
28471 HighlightStyle {
28472 underline: Some(UnderlineStyle {
28473 thickness: px(1.),
28474 color: None,
28475 wavy: false,
28476 }),
28477 ..Default::default()
28478 },
28479 cx,
28480 );
28481 }
28482
28483 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
28484 let use_autoclose = this.use_autoclose;
28485 let use_auto_surround = this.use_auto_surround;
28486 this.set_use_autoclose(false);
28487 this.set_use_auto_surround(false);
28488 this.handle_input(text, window, cx);
28489 this.set_use_autoclose(use_autoclose);
28490 this.set_use_auto_surround(use_auto_surround);
28491
28492 if let Some(new_selected_range) = new_selected_range_utf16 {
28493 let snapshot = this.buffer.read(cx).read(cx);
28494 let new_selected_ranges = marked_ranges
28495 .into_iter()
28496 .map(|marked_range| {
28497 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
28498 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
28499 insertion_start.0 + new_selected_range.start,
28500 ));
28501 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
28502 insertion_start.0 + new_selected_range.end,
28503 ));
28504 snapshot.clip_offset_utf16(new_start, Bias::Left)
28505 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
28506 })
28507 .collect::<Vec<_>>();
28508
28509 drop(snapshot);
28510 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
28511 selections.select_ranges(new_selected_ranges)
28512 });
28513 }
28514 });
28515
28516 self.ime_transaction = self.ime_transaction.or(transaction);
28517 if let Some(transaction) = self.ime_transaction {
28518 self.buffer.update(cx, |buffer, cx| {
28519 buffer.group_until_transaction(transaction, cx);
28520 });
28521 }
28522
28523 if self
28524 .text_highlights(HighlightKey::InputComposition, cx)
28525 .is_none()
28526 {
28527 self.ime_transaction.take();
28528 }
28529 }
28530
28531 fn bounds_for_range(
28532 &mut self,
28533 range_utf16: Range<usize>,
28534 element_bounds: gpui::Bounds<Pixels>,
28535 window: &mut Window,
28536 cx: &mut Context<Self>,
28537 ) -> Option<gpui::Bounds<Pixels>> {
28538 let text_layout_details = self.text_layout_details(window, cx);
28539 let CharacterDimensions {
28540 em_width,
28541 em_advance,
28542 line_height,
28543 } = self.character_dimensions(window, cx);
28544
28545 let snapshot = self.snapshot(window, cx);
28546 let scroll_position = snapshot.scroll_position();
28547 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
28548
28549 let start =
28550 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
28551 let x = Pixels::from(
28552 ScrollOffset::from(
28553 snapshot.x_for_display_point(start, &text_layout_details)
28554 + self.gutter_dimensions.full_width(),
28555 ) - scroll_left,
28556 );
28557 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
28558
28559 Some(Bounds {
28560 origin: element_bounds.origin + point(x, y),
28561 size: size(em_width, line_height),
28562 })
28563 }
28564
28565 fn character_index_for_point(
28566 &mut self,
28567 point: gpui::Point<Pixels>,
28568 _window: &mut Window,
28569 _cx: &mut Context<Self>,
28570 ) -> Option<usize> {
28571 let position_map = self.last_position_map.as_ref()?;
28572 if !position_map.text_hitbox.contains(&point) {
28573 return None;
28574 }
28575 let display_point = position_map.point_for_position(point).previous_valid;
28576 let anchor = position_map
28577 .snapshot
28578 .display_point_to_anchor(display_point, Bias::Left);
28579 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
28580 Some(utf16_offset.0.0)
28581 }
28582
28583 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
28584 self.expects_character_input
28585 }
28586}
28587
28588trait SelectionExt {
28589 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
28590 fn spanned_rows(
28591 &self,
28592 include_end_if_at_line_start: bool,
28593 map: &DisplaySnapshot,
28594 ) -> Range<MultiBufferRow>;
28595}
28596
28597impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
28598 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
28599 let start = self
28600 .start
28601 .to_point(map.buffer_snapshot())
28602 .to_display_point(map);
28603 let end = self
28604 .end
28605 .to_point(map.buffer_snapshot())
28606 .to_display_point(map);
28607 if self.reversed {
28608 end..start
28609 } else {
28610 start..end
28611 }
28612 }
28613
28614 fn spanned_rows(
28615 &self,
28616 include_end_if_at_line_start: bool,
28617 map: &DisplaySnapshot,
28618 ) -> Range<MultiBufferRow> {
28619 let start = self.start.to_point(map.buffer_snapshot());
28620 let mut end = self.end.to_point(map.buffer_snapshot());
28621 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
28622 end.row -= 1;
28623 }
28624
28625 let buffer_start = map.prev_line_boundary(start).0;
28626 let buffer_end = map.next_line_boundary(end).0;
28627 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
28628 }
28629}
28630
28631impl<T: InvalidationRegion> InvalidationStack<T> {
28632 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
28633 where
28634 S: Clone + ToOffset,
28635 {
28636 while let Some(region) = self.last() {
28637 let all_selections_inside_invalidation_ranges =
28638 if selections.len() == region.ranges().len() {
28639 selections
28640 .iter()
28641 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
28642 .all(|(selection, invalidation_range)| {
28643 let head = selection.head().to_offset(buffer);
28644 invalidation_range.start <= head && invalidation_range.end >= head
28645 })
28646 } else {
28647 false
28648 };
28649
28650 if all_selections_inside_invalidation_ranges {
28651 break;
28652 } else {
28653 self.pop();
28654 }
28655 }
28656 }
28657}
28658
28659#[derive(Clone)]
28660struct ErasedEditorImpl(Entity<Editor>);
28661
28662impl ui_input::ErasedEditor for ErasedEditorImpl {
28663 fn text(&self, cx: &App) -> String {
28664 self.0.read(cx).text(cx)
28665 }
28666
28667 fn set_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28668 self.0.update(cx, |this, cx| {
28669 this.set_text(text, window, cx);
28670 })
28671 }
28672
28673 fn clear(&self, window: &mut Window, cx: &mut App) {
28674 self.0.update(cx, |this, cx| this.clear(window, cx));
28675 }
28676
28677 fn set_placeholder_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28678 self.0.update(cx, |this, cx| {
28679 this.set_placeholder_text(text, window, cx);
28680 });
28681 }
28682
28683 fn focus_handle(&self, cx: &App) -> FocusHandle {
28684 self.0.read(cx).focus_handle(cx)
28685 }
28686
28687 fn render(&self, _: &mut Window, cx: &App) -> AnyElement {
28688 let settings = ThemeSettings::get_global(cx);
28689 let theme_color = cx.theme().colors();
28690
28691 let text_style = TextStyle {
28692 font_family: settings.ui_font.family.clone(),
28693 font_features: settings.ui_font.features.clone(),
28694 font_size: rems(0.875).into(),
28695 font_weight: settings.ui_font.weight,
28696 font_style: FontStyle::Normal,
28697 line_height: relative(1.2),
28698 color: theme_color.text,
28699 ..Default::default()
28700 };
28701 let editor_style = EditorStyle {
28702 background: theme_color.ghost_element_background,
28703 local_player: cx.theme().players().local(),
28704 syntax: cx.theme().syntax().clone(),
28705 text: text_style,
28706 ..Default::default()
28707 };
28708 EditorElement::new(&self.0, editor_style).into_any()
28709 }
28710
28711 fn as_any(&self) -> &dyn Any {
28712 &self.0
28713 }
28714
28715 fn move_selection_to_end(&self, window: &mut Window, cx: &mut App) {
28716 self.0.update(cx, |editor, cx| {
28717 let editor_offset = editor.buffer().read(cx).len(cx);
28718 editor.change_selections(
28719 SelectionEffects::scroll(Autoscroll::Next),
28720 window,
28721 cx,
28722 |s| s.select_ranges(Some(editor_offset..editor_offset)),
28723 );
28724 });
28725 }
28726
28727 fn subscribe(
28728 &self,
28729 mut callback: Box<dyn FnMut(ui_input::ErasedEditorEvent, &mut Window, &mut App) + 'static>,
28730 window: &mut Window,
28731 cx: &mut App,
28732 ) -> Subscription {
28733 window.subscribe(&self.0, cx, move |_, event: &EditorEvent, window, cx| {
28734 let event = match event {
28735 EditorEvent::BufferEdited => ui_input::ErasedEditorEvent::BufferEdited,
28736 EditorEvent::Blurred => ui_input::ErasedEditorEvent::Blurred,
28737 _ => return,
28738 };
28739 (callback)(event, window, cx);
28740 })
28741 }
28742
28743 fn set_masked(&self, masked: bool, _window: &mut Window, cx: &mut App) {
28744 self.0.update(cx, |editor, cx| {
28745 editor.set_masked(masked, cx);
28746 });
28747 }
28748}
28749impl<T> Default for InvalidationStack<T> {
28750 fn default() -> Self {
28751 Self(Default::default())
28752 }
28753}
28754
28755impl<T> Deref for InvalidationStack<T> {
28756 type Target = Vec<T>;
28757
28758 fn deref(&self) -> &Self::Target {
28759 &self.0
28760 }
28761}
28762
28763impl<T> DerefMut for InvalidationStack<T> {
28764 fn deref_mut(&mut self) -> &mut Self::Target {
28765 &mut self.0
28766 }
28767}
28768
28769impl InvalidationRegion for SnippetState {
28770 fn ranges(&self) -> &[Range<Anchor>] {
28771 &self.ranges[self.active_index]
28772 }
28773}
28774
28775fn edit_prediction_edit_text(
28776 current_snapshot: &BufferSnapshot,
28777 edits: &[(Range<Anchor>, impl AsRef<str>)],
28778 edit_preview: &EditPreview,
28779 include_deletions: bool,
28780 cx: &App,
28781) -> HighlightedText {
28782 let edits = edits
28783 .iter()
28784 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
28785 .collect::<Vec<_>>();
28786
28787 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
28788}
28789
28790fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
28791 // Fallback for providers that don't provide edit_preview (like Copilot)
28792 // Just show the raw edit text with basic styling
28793 let mut text = String::new();
28794 let mut highlights = Vec::new();
28795
28796 let insertion_highlight_style = HighlightStyle {
28797 color: Some(cx.theme().colors().text),
28798 ..Default::default()
28799 };
28800
28801 for (_, edit_text) in edits {
28802 let start_offset = text.len();
28803 text.push_str(edit_text);
28804 let end_offset = text.len();
28805
28806 if start_offset < end_offset {
28807 highlights.push((start_offset..end_offset, insertion_highlight_style));
28808 }
28809 }
28810
28811 HighlightedText {
28812 text: text.into(),
28813 highlights,
28814 }
28815}
28816
28817pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
28818 match severity {
28819 lsp::DiagnosticSeverity::ERROR => colors.error,
28820 lsp::DiagnosticSeverity::WARNING => colors.warning,
28821 lsp::DiagnosticSeverity::INFORMATION => colors.info,
28822 lsp::DiagnosticSeverity::HINT => colors.info,
28823 _ => colors.ignored,
28824 }
28825}
28826
28827pub fn styled_runs_for_code_label<'a>(
28828 label: &'a CodeLabel,
28829 syntax_theme: &'a theme::SyntaxTheme,
28830 local_player: &'a theme::PlayerColor,
28831) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
28832 let fade_out = HighlightStyle {
28833 fade_out: Some(0.35),
28834 ..Default::default()
28835 };
28836
28837 let mut prev_end = label.filter_range.end;
28838 label
28839 .runs
28840 .iter()
28841 .enumerate()
28842 .flat_map(move |(ix, (range, highlight_id))| {
28843 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
28844 HighlightStyle {
28845 color: Some(local_player.cursor),
28846 ..Default::default()
28847 }
28848 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
28849 HighlightStyle {
28850 background_color: Some(local_player.selection),
28851 ..Default::default()
28852 }
28853 } else if let Some(style) = highlight_id.style(syntax_theme) {
28854 style
28855 } else {
28856 return Default::default();
28857 };
28858 let muted_style = style.highlight(fade_out);
28859
28860 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
28861 if range.start >= label.filter_range.end {
28862 if range.start > prev_end {
28863 runs.push((prev_end..range.start, fade_out));
28864 }
28865 runs.push((range.clone(), muted_style));
28866 } else if range.end <= label.filter_range.end {
28867 runs.push((range.clone(), style));
28868 } else {
28869 runs.push((range.start..label.filter_range.end, style));
28870 runs.push((label.filter_range.end..range.end, muted_style));
28871 }
28872 prev_end = cmp::max(prev_end, range.end);
28873
28874 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
28875 runs.push((prev_end..label.text.len(), fade_out));
28876 }
28877
28878 runs
28879 })
28880}
28881
28882pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
28883 let mut prev_index = 0;
28884 let mut prev_codepoint: Option<char> = None;
28885 text.char_indices()
28886 .chain([(text.len(), '\0')])
28887 .filter_map(move |(index, codepoint)| {
28888 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28889 let is_boundary = index == text.len()
28890 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
28891 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
28892 if is_boundary {
28893 let chunk = &text[prev_index..index];
28894 prev_index = index;
28895 Some(chunk)
28896 } else {
28897 None
28898 }
28899 })
28900}
28901
28902/// Given a string of text immediately before the cursor, iterates over possible
28903/// strings a snippet could match to. More precisely: returns an iterator over
28904/// suffixes of `text` created by splitting at word boundaries (before & after
28905/// every non-word character).
28906///
28907/// Shorter suffixes are returned first.
28908pub(crate) fn snippet_candidate_suffixes<'a>(
28909 text: &'a str,
28910 is_word_char: &'a dyn Fn(char) -> bool,
28911) -> impl std::iter::Iterator<Item = &'a str> + 'a {
28912 let mut prev_index = text.len();
28913 let mut prev_codepoint = None;
28914 text.char_indices()
28915 .rev()
28916 .chain([(0, '\0')])
28917 .filter_map(move |(index, codepoint)| {
28918 let prev_index = std::mem::replace(&mut prev_index, index);
28919 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28920 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
28921 None
28922 } else {
28923 let chunk = &text[prev_index..]; // go to end of string
28924 Some(chunk)
28925 }
28926 })
28927}
28928
28929pub trait RangeToAnchorExt: Sized {
28930 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
28931
28932 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
28933 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
28934 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
28935 }
28936}
28937
28938impl<T: ToOffset> RangeToAnchorExt for Range<T> {
28939 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
28940 let start_offset = self.start.to_offset(snapshot);
28941 let end_offset = self.end.to_offset(snapshot);
28942 if start_offset == end_offset {
28943 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
28944 } else {
28945 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
28946 }
28947 }
28948}
28949
28950pub trait RowExt {
28951 fn as_f64(&self) -> f64;
28952
28953 fn next_row(&self) -> Self;
28954
28955 fn previous_row(&self) -> Self;
28956
28957 fn minus(&self, other: Self) -> u32;
28958}
28959
28960impl RowExt for DisplayRow {
28961 fn as_f64(&self) -> f64 {
28962 self.0 as _
28963 }
28964
28965 fn next_row(&self) -> Self {
28966 Self(self.0 + 1)
28967 }
28968
28969 fn previous_row(&self) -> Self {
28970 Self(self.0.saturating_sub(1))
28971 }
28972
28973 fn minus(&self, other: Self) -> u32 {
28974 self.0 - other.0
28975 }
28976}
28977
28978impl RowExt for MultiBufferRow {
28979 fn as_f64(&self) -> f64 {
28980 self.0 as _
28981 }
28982
28983 fn next_row(&self) -> Self {
28984 Self(self.0 + 1)
28985 }
28986
28987 fn previous_row(&self) -> Self {
28988 Self(self.0.saturating_sub(1))
28989 }
28990
28991 fn minus(&self, other: Self) -> u32 {
28992 self.0 - other.0
28993 }
28994}
28995
28996trait RowRangeExt {
28997 type Row;
28998
28999 fn len(&self) -> usize;
29000
29001 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
29002}
29003
29004impl RowRangeExt for Range<MultiBufferRow> {
29005 type Row = MultiBufferRow;
29006
29007 fn len(&self) -> usize {
29008 (self.end.0 - self.start.0) as usize
29009 }
29010
29011 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
29012 (self.start.0..self.end.0).map(MultiBufferRow)
29013 }
29014}
29015
29016impl RowRangeExt for Range<DisplayRow> {
29017 type Row = DisplayRow;
29018
29019 fn len(&self) -> usize {
29020 (self.end.0 - self.start.0) as usize
29021 }
29022
29023 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
29024 (self.start.0..self.end.0).map(DisplayRow)
29025 }
29026}
29027
29028/// If select range has more than one line, we
29029/// just point the cursor to range.start.
29030fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
29031 if range.start.row == range.end.row {
29032 range
29033 } else {
29034 range.start..range.start
29035 }
29036}
29037pub struct KillRing(ClipboardItem);
29038impl Global for KillRing {}
29039
29040const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
29041
29042enum BreakpointPromptEditAction {
29043 Log,
29044 Condition,
29045 HitCondition,
29046}
29047
29048struct BreakpointPromptEditor {
29049 pub(crate) prompt: Entity<Editor>,
29050 editor: WeakEntity<Editor>,
29051 breakpoint_anchor: Anchor,
29052 breakpoint: Breakpoint,
29053 edit_action: BreakpointPromptEditAction,
29054 block_ids: HashSet<CustomBlockId>,
29055 editor_margins: Arc<Mutex<EditorMargins>>,
29056 _subscriptions: Vec<Subscription>,
29057}
29058
29059impl BreakpointPromptEditor {
29060 const MAX_LINES: u8 = 4;
29061
29062 fn new(
29063 editor: WeakEntity<Editor>,
29064 breakpoint_anchor: Anchor,
29065 breakpoint: Breakpoint,
29066 edit_action: BreakpointPromptEditAction,
29067 window: &mut Window,
29068 cx: &mut Context<Self>,
29069 ) -> Self {
29070 let base_text = match edit_action {
29071 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
29072 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
29073 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
29074 }
29075 .map(|msg| msg.to_string())
29076 .unwrap_or_default();
29077
29078 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
29079 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
29080
29081 let prompt = cx.new(|cx| {
29082 let mut prompt = Editor::new(
29083 EditorMode::AutoHeight {
29084 min_lines: 1,
29085 max_lines: Some(Self::MAX_LINES as usize),
29086 },
29087 buffer,
29088 None,
29089 window,
29090 cx,
29091 );
29092 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
29093 prompt.set_show_cursor_when_unfocused(false, cx);
29094 prompt.set_placeholder_text(
29095 match edit_action {
29096 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
29097 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
29098 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
29099 },
29100 window,
29101 cx,
29102 );
29103
29104 prompt
29105 });
29106
29107 Self {
29108 prompt,
29109 editor,
29110 breakpoint_anchor,
29111 breakpoint,
29112 edit_action,
29113 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
29114 block_ids: Default::default(),
29115 _subscriptions: vec![],
29116 }
29117 }
29118
29119 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
29120 self.block_ids.extend(block_ids)
29121 }
29122
29123 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
29124 if let Some(editor) = self.editor.upgrade() {
29125 let message = self
29126 .prompt
29127 .read(cx)
29128 .buffer
29129 .read(cx)
29130 .as_singleton()
29131 .expect("A multi buffer in breakpoint prompt isn't possible")
29132 .read(cx)
29133 .as_rope()
29134 .to_string();
29135
29136 editor.update(cx, |editor, cx| {
29137 editor.edit_breakpoint_at_anchor(
29138 self.breakpoint_anchor,
29139 self.breakpoint.clone(),
29140 match self.edit_action {
29141 BreakpointPromptEditAction::Log => {
29142 BreakpointEditAction::EditLogMessage(message.into())
29143 }
29144 BreakpointPromptEditAction::Condition => {
29145 BreakpointEditAction::EditCondition(message.into())
29146 }
29147 BreakpointPromptEditAction::HitCondition => {
29148 BreakpointEditAction::EditHitCondition(message.into())
29149 }
29150 },
29151 cx,
29152 );
29153
29154 editor.remove_blocks(self.block_ids.clone(), None, cx);
29155 cx.focus_self(window);
29156 });
29157 }
29158 }
29159
29160 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
29161 self.editor
29162 .update(cx, |editor, cx| {
29163 editor.remove_blocks(self.block_ids.clone(), None, cx);
29164 window.focus(&editor.focus_handle, cx);
29165 })
29166 .log_err();
29167 }
29168
29169 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
29170 let settings = ThemeSettings::get_global(cx);
29171 let text_style = TextStyle {
29172 color: if self.prompt.read(cx).read_only(cx) {
29173 cx.theme().colors().text_disabled
29174 } else {
29175 cx.theme().colors().text
29176 },
29177 font_family: settings.buffer_font.family.clone(),
29178 font_fallbacks: settings.buffer_font.fallbacks.clone(),
29179 font_size: settings.buffer_font_size(cx).into(),
29180 font_weight: settings.buffer_font.weight,
29181 line_height: relative(settings.buffer_line_height.value()),
29182 ..Default::default()
29183 };
29184 EditorElement::new(
29185 &self.prompt,
29186 EditorStyle {
29187 background: cx.theme().colors().editor_background,
29188 local_player: cx.theme().players().local(),
29189 text: text_style,
29190 ..Default::default()
29191 },
29192 )
29193 }
29194
29195 fn render_close_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
29196 let focus_handle = self.prompt.focus_handle(cx);
29197 IconButton::new("cancel", IconName::Close)
29198 .icon_color(Color::Muted)
29199 .shape(IconButtonShape::Square)
29200 .tooltip(move |_window, cx| {
29201 Tooltip::for_action_in("Cancel", &menu::Cancel, &focus_handle, cx)
29202 })
29203 .on_click(cx.listener(|this, _, window, cx| {
29204 this.cancel(&menu::Cancel, window, cx);
29205 }))
29206 }
29207
29208 fn render_confirm_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
29209 let focus_handle = self.prompt.focus_handle(cx);
29210 IconButton::new("confirm", IconName::Return)
29211 .icon_color(Color::Muted)
29212 .shape(IconButtonShape::Square)
29213 .tooltip(move |_window, cx| {
29214 Tooltip::for_action_in("Confirm", &menu::Confirm, &focus_handle, cx)
29215 })
29216 .on_click(cx.listener(|this, _, window, cx| {
29217 this.confirm(&menu::Confirm, window, cx);
29218 }))
29219 }
29220}
29221
29222impl Render for BreakpointPromptEditor {
29223 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
29224 let ui_font_size = ThemeSettings::get_global(cx).ui_font_size(cx);
29225 let editor_margins = *self.editor_margins.lock();
29226 let gutter_dimensions = editor_margins.gutter;
29227 let left_gutter_width = gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0);
29228 let right_padding = editor_margins.right + px(9.);
29229 h_flex()
29230 .key_context("Editor")
29231 .bg(cx.theme().colors().editor_background)
29232 .border_y_1()
29233 .border_color(cx.theme().status().info_border)
29234 .size_full()
29235 .py(window.line_height() / 2.5)
29236 .pr(right_padding)
29237 .on_action(cx.listener(Self::confirm))
29238 .on_action(cx.listener(Self::cancel))
29239 .child(
29240 WithRemSize::new(ui_font_size)
29241 .h_full()
29242 .w(left_gutter_width)
29243 .flex()
29244 .flex_row()
29245 .flex_shrink_0()
29246 .items_center()
29247 .justify_center()
29248 .gap_1()
29249 .child(self.render_close_button(cx)),
29250 )
29251 .child(
29252 h_flex()
29253 .w_full()
29254 .justify_between()
29255 .child(div().flex_1().child(self.render_prompt_editor(cx)))
29256 .child(
29257 WithRemSize::new(ui_font_size)
29258 .flex()
29259 .flex_row()
29260 .items_center()
29261 .child(self.render_confirm_button(cx)),
29262 ),
29263 )
29264 }
29265}
29266
29267impl Focusable for BreakpointPromptEditor {
29268 fn focus_handle(&self, cx: &App) -> FocusHandle {
29269 self.prompt.focus_handle(cx)
29270 }
29271}
29272
29273fn all_edits_insertions_or_deletions(
29274 edits: &Vec<(Range<Anchor>, Arc<str>)>,
29275 snapshot: &MultiBufferSnapshot,
29276) -> bool {
29277 let mut all_insertions = true;
29278 let mut all_deletions = true;
29279
29280 for (range, new_text) in edits.iter() {
29281 let range_is_empty = range.to_offset(snapshot).is_empty();
29282 let text_is_empty = new_text.is_empty();
29283
29284 if range_is_empty != text_is_empty {
29285 if range_is_empty {
29286 all_deletions = false;
29287 } else {
29288 all_insertions = false;
29289 }
29290 } else {
29291 return false;
29292 }
29293
29294 if !all_insertions && !all_deletions {
29295 return false;
29296 }
29297 }
29298 all_insertions || all_deletions
29299}
29300
29301struct MissingEditPredictionKeybindingTooltip;
29302
29303impl Render for MissingEditPredictionKeybindingTooltip {
29304 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
29305 ui::tooltip_container(cx, |container, cx| {
29306 container
29307 .flex_shrink_0()
29308 .max_w_80()
29309 .min_h(rems_from_px(124.))
29310 .justify_between()
29311 .child(
29312 v_flex()
29313 .flex_1()
29314 .text_ui_sm(cx)
29315 .child(Label::new("Conflict with Accept Keybinding"))
29316 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
29317 )
29318 .child(
29319 h_flex()
29320 .pb_1()
29321 .gap_1()
29322 .items_end()
29323 .w_full()
29324 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
29325 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
29326 }))
29327 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
29328 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
29329 })),
29330 )
29331 })
29332 }
29333}
29334
29335#[derive(Debug, Clone, Copy, PartialEq)]
29336pub struct LineHighlight {
29337 pub background: Background,
29338 pub border: Option<gpui::Hsla>,
29339 pub include_gutter: bool,
29340 pub type_id: Option<TypeId>,
29341}
29342
29343struct LineManipulationResult {
29344 pub new_text: String,
29345 pub line_count_before: usize,
29346 pub line_count_after: usize,
29347}
29348
29349fn render_diff_hunk_controls(
29350 row: u32,
29351 status: &DiffHunkStatus,
29352 hunk_range: Range<Anchor>,
29353 is_created_file: bool,
29354 line_height: Pixels,
29355 editor: &Entity<Editor>,
29356 _window: &mut Window,
29357 cx: &mut App,
29358) -> AnyElement {
29359 h_flex()
29360 .h(line_height)
29361 .mr_1()
29362 .gap_1()
29363 .px_0p5()
29364 .pb_1()
29365 .border_x_1()
29366 .border_b_1()
29367 .border_color(cx.theme().colors().border_variant)
29368 .rounded_b_lg()
29369 .bg(cx.theme().colors().editor_background)
29370 .gap_1()
29371 .block_mouse_except_scroll()
29372 .shadow_md()
29373 .child(if status.has_secondary_hunk() {
29374 Button::new(("stage", row as u64), "Stage")
29375 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
29376 .tooltip({
29377 let focus_handle = editor.focus_handle(cx);
29378 move |_window, cx| {
29379 Tooltip::for_action_in(
29380 "Stage Hunk",
29381 &::git::ToggleStaged,
29382 &focus_handle,
29383 cx,
29384 )
29385 }
29386 })
29387 .on_click({
29388 let editor = editor.clone();
29389 move |_event, _window, cx| {
29390 editor.update(cx, |editor, cx| {
29391 editor.stage_or_unstage_diff_hunks(
29392 true,
29393 vec![hunk_range.start..hunk_range.start],
29394 cx,
29395 );
29396 });
29397 }
29398 })
29399 } else {
29400 Button::new(("unstage", row as u64), "Unstage")
29401 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
29402 .tooltip({
29403 let focus_handle = editor.focus_handle(cx);
29404 move |_window, cx| {
29405 Tooltip::for_action_in(
29406 "Unstage Hunk",
29407 &::git::ToggleStaged,
29408 &focus_handle,
29409 cx,
29410 )
29411 }
29412 })
29413 .on_click({
29414 let editor = editor.clone();
29415 move |_event, _window, cx| {
29416 editor.update(cx, |editor, cx| {
29417 editor.stage_or_unstage_diff_hunks(
29418 false,
29419 vec![hunk_range.start..hunk_range.start],
29420 cx,
29421 );
29422 });
29423 }
29424 })
29425 })
29426 .child(
29427 Button::new(("restore", row as u64), "Restore")
29428 .tooltip({
29429 let focus_handle = editor.focus_handle(cx);
29430 move |_window, cx| {
29431 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
29432 }
29433 })
29434 .on_click({
29435 let editor = editor.clone();
29436 move |_event, window, cx| {
29437 editor.update(cx, |editor, cx| {
29438 let snapshot = editor.snapshot(window, cx);
29439 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
29440 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
29441 });
29442 }
29443 })
29444 .disabled(is_created_file),
29445 )
29446 .when(
29447 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
29448 |el| {
29449 el.child(
29450 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
29451 .shape(IconButtonShape::Square)
29452 .icon_size(IconSize::Small)
29453 // .disabled(!has_multiple_hunks)
29454 .tooltip({
29455 let focus_handle = editor.focus_handle(cx);
29456 move |_window, cx| {
29457 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
29458 }
29459 })
29460 .on_click({
29461 let editor = editor.clone();
29462 move |_event, window, cx| {
29463 editor.update(cx, |editor, cx| {
29464 let snapshot = editor.snapshot(window, cx);
29465 let position =
29466 hunk_range.end.to_point(&snapshot.buffer_snapshot());
29467 editor.go_to_hunk_before_or_after_position(
29468 &snapshot,
29469 position,
29470 Direction::Next,
29471 true,
29472 window,
29473 cx,
29474 );
29475 editor.expand_selected_diff_hunks(cx);
29476 });
29477 }
29478 }),
29479 )
29480 .child(
29481 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
29482 .shape(IconButtonShape::Square)
29483 .icon_size(IconSize::Small)
29484 // .disabled(!has_multiple_hunks)
29485 .tooltip({
29486 let focus_handle = editor.focus_handle(cx);
29487 move |_window, cx| {
29488 Tooltip::for_action_in(
29489 "Previous Hunk",
29490 &GoToPreviousHunk,
29491 &focus_handle,
29492 cx,
29493 )
29494 }
29495 })
29496 .on_click({
29497 let editor = editor.clone();
29498 move |_event, window, cx| {
29499 editor.update(cx, |editor, cx| {
29500 let snapshot = editor.snapshot(window, cx);
29501 let point =
29502 hunk_range.start.to_point(&snapshot.buffer_snapshot());
29503 editor.go_to_hunk_before_or_after_position(
29504 &snapshot,
29505 point,
29506 Direction::Prev,
29507 true,
29508 window,
29509 cx,
29510 );
29511 editor.expand_selected_diff_hunks(cx);
29512 });
29513 }
29514 }),
29515 )
29516 },
29517 )
29518 .into_any_element()
29519}
29520
29521pub fn multibuffer_context_lines(cx: &App) -> u32 {
29522 EditorSettings::try_get(cx)
29523 .map(|settings| settings.excerpt_context_lines)
29524 .unwrap_or(2)
29525 .min(32)
29526}