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_block_comment_tests;
52#[cfg(test)]
53mod editor_tests;
54mod signature_help;
55#[cfg(any(test, feature = "test-support"))]
56pub mod test;
57
58pub(crate) use actions::*;
59pub use display_map::{
60 ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder, HighlightKey,
61 SemanticTokenHighlight,
62};
63pub use edit_prediction_types::Direction;
64pub use editor_settings::{
65 CompletionDetailAlignment, CurrentLineHighlight, DiffViewStyle, DocumentColorsRenderMode,
66 EditorSettings, EditorSettingsScrollbarProxy, HideMouseMode, ScrollBeyondLastLine,
67 ScrollbarAxes, SearchSettings, ShowMinimap, ui_scrollbar_settings_from_raw,
68};
69pub use element::{
70 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
71 render_breadcrumb_text,
72};
73pub use git::blame::BlameRenderer;
74pub use hover_popover::hover_markdown_style;
75pub use inlays::Inlay;
76pub use items::MAX_TAB_TITLE_LEN;
77pub use linked_editing_ranges::LinkedEdits;
78pub use lsp::CompletionContext;
79pub use lsp_ext::lsp_tasks;
80pub use multi_buffer::{
81 Anchor, AnchorRangeExt, BufferOffset, ExcerptRange, MBTextSummary, MultiBuffer,
82 MultiBufferOffset, MultiBufferOffsetUtf16, MultiBufferSnapshot, PathKey, RowInfo, ToOffset,
83 ToPoint,
84};
85pub use split::{SplittableEditor, ToggleSplitDiff};
86pub use split_editor_view::SplitEditorView;
87pub use text::Bias;
88
89use ::git::{Restore, blame::BlameEntry, commit::ParsedCommitMessage, status::FileStatus};
90use aho_corasick::{AhoCorasick, AhoCorasickBuilder, BuildError};
91use anyhow::{Context as _, Result, anyhow, bail};
92use blink_manager::BlinkManager;
93use buffer_diff::DiffHunkStatus;
94use client::{Collaborator, ParticipantIndex, parse_zed_link};
95use clock::ReplicaId;
96use code_context_menus::{
97 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
98 CompletionsMenu, ContextMenuOrigin,
99};
100use collections::{BTreeMap, HashMap, HashSet, VecDeque};
101use convert_case::{Case, Casing};
102use dap::TelemetrySpawnLocation;
103use display_map::*;
104use document_colors::LspColorData;
105use edit_prediction_types::{
106 EditPredictionDelegate, EditPredictionDelegateHandle, EditPredictionDiscardReason,
107 EditPredictionGranularity, SuggestionDisplayType,
108};
109use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
110use element::{LineWithInvisibles, PositionMap, layout_line};
111use futures::{
112 FutureExt,
113 future::{self, Shared, join},
114};
115use fuzzy::{StringMatch, StringMatchCandidate};
116use git::blame::{GitBlame, GlobalBlameRenderer};
117use gpui::{
118 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
119 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
120 DispatchPhase, Edges, Entity, EntityId, EntityInputHandler, EventEmitter, FocusHandle,
121 FocusOutEvent, Focusable, FontId, FontStyle, FontWeight, Global, HighlightStyle, Hsla,
122 KeyContext, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, PaintQuad, ParentElement,
123 Pixels, PressureStage, Render, ScrollHandle, SharedString, SharedUri, Size, Stateful, Styled,
124 Subscription, Task, TextRun, TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle,
125 UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window, div, point, prelude::*,
126 pulsating_between, px, relative, size,
127};
128use hover_links::{HoverLink, HoveredLinkState, find_file};
129use hover_popover::{HoverState, hide_hover};
130use indent_guides::ActiveIndentGuidesState;
131use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
132use itertools::{Either, Itertools};
133use language::{
134 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
135 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
136 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
137 IndentSize, Language, LanguageAwareStyling, LanguageName, LanguageRegistry, LanguageScope,
138 LocalFile, OffsetRangeExt, OutlineItem, Point, Selection, SelectionGoal, TextObject,
139 TransactionId, TreeSitterOptions, WordsQuery,
140 language_settings::{
141 self, AllLanguageSettings, LanguageSettings, LspInsertMode, RewrapBehavior,
142 WordsCompletionMode, all_language_settings,
143 },
144 point_from_lsp, point_to_lsp, text_diff_with_options,
145};
146use linked_editing_ranges::refresh_linked_ranges;
147use lsp::{
148 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
149 LanguageServerId,
150};
151use markdown::Markdown;
152use mouse_context_menu::MouseContextMenu;
153use movement::TextLayoutDetails;
154use multi_buffer::{
155 ExcerptBoundaryInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint,
156 MultiBufferRow,
157};
158use parking_lot::Mutex;
159use persistence::EditorDb;
160use project::{
161 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
162 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
163 InvalidationStrategy, Location, LocationLink, LspAction, PrepareRenameResponse, Project,
164 ProjectItem, ProjectPath, ProjectTransaction,
165 debugger::{
166 breakpoint_store::{
167 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
168 BreakpointStore, BreakpointStoreEvent,
169 },
170 session::{Session, SessionEvent},
171 },
172 git_store::GitStoreEvent,
173 lsp_store::{
174 BufferSemanticTokens, CacheInlayHints, CompletionDocumentation, FormatTrigger,
175 LspFormatTarget, OpenLspBufferHandle, RefreshForServer,
176 },
177 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
178};
179use rand::seq::SliceRandom;
180use regex::Regex;
181use rpc::{ErrorCode, ErrorExt, proto::PeerId};
182use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, SharedScrollAnchor};
183use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
184use serde::{Deserialize, Serialize};
185use settings::{
186 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
187 update_settings_file,
188};
189use smallvec::{SmallVec, smallvec};
190use snippet::Snippet;
191use std::{
192 any::{Any, TypeId},
193 borrow::Cow,
194 cell::{OnceCell, RefCell},
195 cmp::{self, Ordering, Reverse},
196 collections::hash_map,
197 iter::{self, Peekable},
198 mem,
199 num::NonZeroU32,
200 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
201 path::{Path, PathBuf},
202 rc::Rc,
203 sync::Arc,
204 time::{Duration, Instant},
205};
206use task::TaskVariables;
207use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _, ToPoint as _};
208use theme::{
209 AccentColors, ActiveTheme, GlobalTheme, PlayerColor, StatusColors, SyntaxTheme, Theme,
210};
211use theme_settings::{ThemeSettings, observe_buffer_font_size_adjustment};
212use ui::{
213 Avatar, ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape,
214 IconName, IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
215 utils::WithRemSize,
216};
217use ui_input::ErasedEditor;
218use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
219use workspace::{
220 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, NavigationEntry, OpenInTerminal,
221 OpenTerminal, Pane, RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection,
222 TabBarSettings, Toast, ViewId, Workspace, WorkspaceId, WorkspaceSettings,
223 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
224 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
225 searchable::SearchEvent,
226};
227pub use zed_actions::editor::RevealInFileManager;
228use zed_actions::editor::{MoveDown, MoveUp};
229
230use crate::{
231 code_context_menus::CompletionsMenuSource,
232 editor_settings::MultiCursorModifier,
233 hover_links::{find_url, find_url_from_range},
234 inlays::{
235 InlineValueCache,
236 inlay_hints::{LspInlayHintData, inlay_hint_settings},
237 },
238 runnables::{ResolvedTasks, RunnableData, RunnableTasks},
239 scroll::{ScrollOffset, ScrollPixelOffset},
240 selections_collection::resolve_selections_wrapping_blocks,
241 semantic_tokens::SemanticTokenState,
242 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
243};
244
245pub const FILE_HEADER_HEIGHT: u32 = 2;
246pub const BUFFER_HEADER_PADDING: Rems = rems(0.25);
247pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
248const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
249const MAX_LINE_LEN: usize = 1024;
250const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
251const MAX_SELECTION_HISTORY_LEN: usize = 1024;
252pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
253#[doc(hidden)]
254pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
255pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
256
257pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
258pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
259pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
260pub const LSP_REQUEST_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(50);
261
262pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
263pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
264
265pub type RenderDiffHunkControlsFn = Arc<
266 dyn Fn(
267 u32,
268 &DiffHunkStatus,
269 Range<Anchor>,
270 bool,
271 Pixels,
272 &Entity<Editor>,
273 &mut Window,
274 &mut App,
275 ) -> AnyElement,
276>;
277
278enum ReportEditorEvent {
279 Saved { auto_saved: bool },
280 EditorOpened,
281 Closed,
282}
283
284impl ReportEditorEvent {
285 pub fn event_type(&self) -> &'static str {
286 match self {
287 Self::Saved { .. } => "Editor Saved",
288 Self::EditorOpened => "Editor Opened",
289 Self::Closed => "Editor Closed",
290 }
291 }
292}
293
294pub enum ActiveDebugLine {}
295pub enum DebugStackFrameLine {}
296
297pub enum ConflictsOuter {}
298pub enum ConflictsOurs {}
299pub enum ConflictsTheirs {}
300pub enum ConflictsOursMarker {}
301pub enum ConflictsTheirsMarker {}
302
303pub struct HunkAddedColor;
304pub struct HunkRemovedColor;
305
306#[derive(Debug, Copy, Clone, PartialEq, Eq)]
307pub enum Navigated {
308 Yes,
309 No,
310}
311
312impl Navigated {
313 pub fn from_bool(yes: bool) -> Navigated {
314 if yes { Navigated::Yes } else { Navigated::No }
315 }
316}
317
318#[derive(Debug, Clone, PartialEq, Eq)]
319enum DisplayDiffHunk {
320 Folded {
321 display_row: DisplayRow,
322 },
323 Unfolded {
324 is_created_file: bool,
325 diff_base_byte_range: Range<usize>,
326 display_row_range: Range<DisplayRow>,
327 multi_buffer_range: Range<Anchor>,
328 status: DiffHunkStatus,
329 word_diffs: Vec<Range<MultiBufferOffset>>,
330 },
331}
332
333pub enum HideMouseCursorOrigin {
334 TypingAction,
335 MovementAction,
336}
337
338pub fn init(cx: &mut App) {
339 cx.set_global(GlobalBlameRenderer(Arc::new(())));
340 cx.set_global(breadcrumbs::RenderBreadcrumbText(render_breadcrumb_text));
341
342 workspace::register_project_item::<Editor>(cx);
343 workspace::FollowableViewRegistry::register::<Editor>(cx);
344 workspace::register_serializable_item::<Editor>(cx);
345
346 cx.observe_new(
347 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
348 workspace.register_action(Editor::new_file);
349 workspace.register_action(Editor::new_file_split);
350 workspace.register_action(Editor::new_file_vertical);
351 workspace.register_action(Editor::new_file_horizontal);
352 workspace.register_action(Editor::cancel_language_server_work);
353 workspace.register_action(Editor::toggle_focus);
354 },
355 )
356 .detach();
357
358 cx.on_action(move |_: &workspace::NewFile, cx| {
359 let app_state = workspace::AppState::global(cx);
360 workspace::open_new(
361 Default::default(),
362 app_state,
363 cx,
364 |workspace, window, cx| Editor::new_file(workspace, &Default::default(), window, cx),
365 )
366 .detach_and_log_err(cx);
367 })
368 .on_action(move |_: &workspace::NewWindow, cx| {
369 let app_state = workspace::AppState::global(cx);
370 workspace::open_new(
371 Default::default(),
372 app_state,
373 cx,
374 |workspace, window, cx| {
375 cx.activate(true);
376 Editor::new_file(workspace, &Default::default(), window, cx)
377 },
378 )
379 .detach_and_log_err(cx);
380 });
381 _ = ui_input::ERASED_EDITOR_FACTORY.set(|window, cx| {
382 Arc::new(ErasedEditorImpl(
383 cx.new(|cx| Editor::single_line(window, cx)),
384 )) as Arc<dyn ErasedEditor>
385 });
386 _ = multi_buffer::EXCERPT_CONTEXT_LINES.set(multibuffer_context_lines);
387}
388
389pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
390 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
391}
392
393pub trait DiagnosticRenderer {
394 fn render_group(
395 &self,
396 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
397 buffer_id: BufferId,
398 snapshot: EditorSnapshot,
399 editor: WeakEntity<Editor>,
400 language_registry: Option<Arc<LanguageRegistry>>,
401 cx: &mut App,
402 ) -> Vec<BlockProperties<Anchor>>;
403
404 fn render_hover(
405 &self,
406 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
407 range: Range<Point>,
408 buffer_id: BufferId,
409 language_registry: Option<Arc<LanguageRegistry>>,
410 cx: &mut App,
411 ) -> Option<Entity<markdown::Markdown>>;
412
413 fn open_link(
414 &self,
415 editor: &mut Editor,
416 link: SharedString,
417 window: &mut Window,
418 cx: &mut Context<Editor>,
419 );
420}
421
422pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
423
424impl GlobalDiagnosticRenderer {
425 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
426 cx.try_global::<Self>().map(|g| g.0.clone())
427 }
428}
429
430impl gpui::Global for GlobalDiagnosticRenderer {}
431pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
432 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
433}
434
435pub struct SearchWithinRange;
436
437trait InvalidationRegion {
438 fn ranges(&self) -> &[Range<Anchor>];
439}
440
441#[derive(Clone, Debug, PartialEq)]
442pub enum SelectPhase {
443 Begin {
444 position: DisplayPoint,
445 add: bool,
446 click_count: usize,
447 },
448 BeginColumnar {
449 position: DisplayPoint,
450 reset: bool,
451 mode: ColumnarMode,
452 goal_column: u32,
453 },
454 Extend {
455 position: DisplayPoint,
456 click_count: usize,
457 },
458 Update {
459 position: DisplayPoint,
460 goal_column: u32,
461 scroll_delta: gpui::Point<f32>,
462 },
463 End,
464}
465
466#[derive(Clone, Debug, PartialEq)]
467pub enum ColumnarMode {
468 FromMouse,
469 FromSelection,
470}
471
472#[derive(Clone, Debug)]
473pub enum SelectMode {
474 Character,
475 Word(Range<Anchor>),
476 Line(Range<Anchor>),
477 All,
478}
479
480#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
481pub enum SizingBehavior {
482 /// The editor will layout itself using `size_full` and will include the vertical
483 /// scroll margin as requested by user settings.
484 #[default]
485 Default,
486 /// The editor will layout itself using `size_full`, but will not have any
487 /// vertical overscroll.
488 ExcludeOverscrollMargin,
489 /// The editor will request a vertical size according to its content and will be
490 /// layouted without a vertical scroll margin.
491 SizeByContent,
492}
493
494#[derive(Clone, PartialEq, Eq, Debug)]
495pub enum EditorMode {
496 SingleLine,
497 AutoHeight {
498 min_lines: usize,
499 max_lines: Option<usize>,
500 },
501 Full {
502 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
503 scale_ui_elements_with_buffer_font_size: bool,
504 /// When set to `true`, the editor will render a background for the active line.
505 show_active_line_background: bool,
506 /// Determines the sizing behavior for this editor
507 sizing_behavior: SizingBehavior,
508 },
509 Minimap {
510 parent: WeakEntity<Editor>,
511 },
512}
513
514impl EditorMode {
515 pub fn full() -> Self {
516 Self::Full {
517 scale_ui_elements_with_buffer_font_size: true,
518 show_active_line_background: true,
519 sizing_behavior: SizingBehavior::Default,
520 }
521 }
522
523 #[inline]
524 pub fn is_full(&self) -> bool {
525 matches!(self, Self::Full { .. })
526 }
527
528 #[inline]
529 pub fn is_single_line(&self) -> bool {
530 matches!(self, Self::SingleLine { .. })
531 }
532
533 #[inline]
534 fn is_minimap(&self) -> bool {
535 matches!(self, Self::Minimap { .. })
536 }
537}
538
539#[derive(Copy, Clone, Debug)]
540pub enum SoftWrap {
541 /// Prefer not to wrap at all.
542 ///
543 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
544 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
545 GitDiff,
546 /// Prefer a single line generally, unless an overly long line is encountered.
547 None,
548 /// Soft wrap lines that exceed the editor width.
549 EditorWidth,
550 /// Soft wrap lines at the preferred line length.
551 Column(u32),
552 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
553 Bounded(u32),
554}
555
556#[derive(Clone)]
557pub struct EditorStyle {
558 pub background: Hsla,
559 pub border: Hsla,
560 pub local_player: PlayerColor,
561 pub text: TextStyle,
562 pub scrollbar_width: Pixels,
563 pub syntax: Arc<SyntaxTheme>,
564 pub status: StatusColors,
565 pub inlay_hints_style: HighlightStyle,
566 pub edit_prediction_styles: EditPredictionStyles,
567 pub unnecessary_code_fade: f32,
568 pub show_underlines: bool,
569}
570
571impl Default for EditorStyle {
572 fn default() -> Self {
573 Self {
574 background: Hsla::default(),
575 border: Hsla::default(),
576 local_player: PlayerColor::default(),
577 text: TextStyle::default(),
578 scrollbar_width: Pixels::default(),
579 syntax: Default::default(),
580 // HACK: Status colors don't have a real default.
581 // We should look into removing the status colors from the editor
582 // style and retrieve them directly from the theme.
583 status: StatusColors::dark(),
584 inlay_hints_style: HighlightStyle::default(),
585 edit_prediction_styles: EditPredictionStyles {
586 insertion: HighlightStyle::default(),
587 whitespace: HighlightStyle::default(),
588 },
589 unnecessary_code_fade: Default::default(),
590 show_underlines: true,
591 }
592 }
593}
594
595pub fn make_inlay_hints_style(cx: &App) -> HighlightStyle {
596 let show_background = AllLanguageSettings::get_global(cx)
597 .defaults
598 .inlay_hints
599 .show_background;
600
601 let mut style = cx
602 .theme()
603 .syntax()
604 .style_for_name("hint")
605 .unwrap_or_default();
606
607 if style.color.is_none() {
608 style.color = Some(cx.theme().status().hint);
609 }
610
611 if !show_background {
612 style.background_color = None;
613 return style;
614 }
615
616 if style.background_color.is_none() {
617 style.background_color = Some(cx.theme().status().hint_background);
618 }
619
620 style
621}
622
623pub fn make_suggestion_styles(cx: &App) -> EditPredictionStyles {
624 EditPredictionStyles {
625 insertion: HighlightStyle {
626 color: Some(cx.theme().status().predictive),
627 ..HighlightStyle::default()
628 },
629 whitespace: HighlightStyle {
630 background_color: Some(cx.theme().status().created_background),
631 ..HighlightStyle::default()
632 },
633 }
634}
635
636type CompletionId = usize;
637
638pub(crate) enum EditDisplayMode {
639 TabAccept,
640 DiffPopover,
641 Inline,
642}
643
644enum EditPrediction {
645 Edit {
646 // TODO could be a language::Anchor?
647 edits: Vec<(Range<Anchor>, Arc<str>)>,
648 /// Predicted cursor position as (anchor, offset_from_anchor).
649 /// The anchor is in multibuffer coordinates; after applying edits,
650 /// resolve the anchor and add the offset to get the final cursor position.
651 cursor_position: Option<(Anchor, usize)>,
652 edit_preview: Option<EditPreview>,
653 display_mode: EditDisplayMode,
654 snapshot: BufferSnapshot,
655 },
656 /// Move to a specific location in the active editor
657 MoveWithin {
658 target: Anchor,
659 snapshot: BufferSnapshot,
660 },
661 /// Move to a specific location in a different editor (not the active one)
662 MoveOutside {
663 target: language::Anchor,
664 snapshot: BufferSnapshot,
665 },
666}
667
668struct EditPredictionState {
669 inlay_ids: Vec<InlayId>,
670 completion: EditPrediction,
671 completion_id: Option<SharedString>,
672 invalidation_range: Option<Range<Anchor>>,
673}
674
675enum EditPredictionSettings {
676 Disabled,
677 Enabled {
678 show_in_menu: bool,
679 preview_requires_modifier: bool,
680 },
681}
682
683#[derive(Debug, Clone)]
684struct InlineDiagnostic {
685 message: SharedString,
686 group_id: usize,
687 is_primary: bool,
688 start: Point,
689 severity: lsp::DiagnosticSeverity,
690}
691
692pub enum MenuEditPredictionsPolicy {
693 Never,
694 ByProvider,
695}
696
697pub enum EditPredictionPreview {
698 /// Modifier is not pressed
699 Inactive { released_too_fast: bool },
700 /// Modifier pressed
701 Active {
702 since: Instant,
703 previous_scroll_position: Option<SharedScrollAnchor>,
704 },
705}
706
707#[derive(Copy, Clone, Eq, PartialEq)]
708enum EditPredictionKeybindSurface {
709 Inline,
710 CursorPopoverCompact,
711 CursorPopoverExpanded,
712}
713
714#[derive(Copy, Clone, Eq, PartialEq, Debug)]
715enum EditPredictionKeybindAction {
716 Accept,
717 Preview,
718}
719
720struct EditPredictionKeybindDisplay {
721 #[cfg(test)]
722 accept_keystroke: Option<gpui::KeybindingKeystroke>,
723 #[cfg(test)]
724 preview_keystroke: Option<gpui::KeybindingKeystroke>,
725 displayed_keystroke: Option<gpui::KeybindingKeystroke>,
726 action: EditPredictionKeybindAction,
727 missing_accept_keystroke: bool,
728 show_hold_label: bool,
729}
730
731impl EditPredictionPreview {
732 pub fn released_too_fast(&self) -> bool {
733 match self {
734 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
735 EditPredictionPreview::Active { .. } => false,
736 }
737 }
738
739 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<SharedScrollAnchor>) {
740 if let EditPredictionPreview::Active {
741 previous_scroll_position,
742 ..
743 } = self
744 {
745 *previous_scroll_position = scroll_position;
746 }
747 }
748}
749
750pub struct ContextMenuOptions {
751 pub min_entries_visible: usize,
752 pub max_entries_visible: usize,
753 pub placement: Option<ContextMenuPlacement>,
754}
755
756#[derive(Debug, Clone, PartialEq, Eq)]
757pub enum ContextMenuPlacement {
758 Above,
759 Below,
760}
761
762#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
763struct EditorActionId(usize);
764
765impl EditorActionId {
766 pub fn post_inc(&mut self) -> Self {
767 let answer = self.0;
768
769 *self = Self(answer + 1);
770
771 Self(answer)
772 }
773}
774
775// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
776// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
777
778type BackgroundHighlight = (
779 Arc<dyn Fn(&usize, &Theme) -> Hsla + Send + Sync>,
780 Arc<[Range<Anchor>]>,
781);
782type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
783
784#[derive(Default)]
785struct ScrollbarMarkerState {
786 scrollbar_size: Size<Pixels>,
787 dirty: bool,
788 markers: Arc<[PaintQuad]>,
789 pending_refresh: Option<Task<Result<()>>>,
790}
791
792impl ScrollbarMarkerState {
793 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
794 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
795 }
796}
797
798#[derive(Clone, Copy, PartialEq, Eq)]
799pub enum MinimapVisibility {
800 Disabled,
801 Enabled {
802 /// The configuration currently present in the users settings.
803 setting_configuration: bool,
804 /// Whether to override the currently set visibility from the users setting.
805 toggle_override: bool,
806 },
807}
808
809impl MinimapVisibility {
810 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
811 if mode.is_full() {
812 Self::Enabled {
813 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
814 toggle_override: false,
815 }
816 } else {
817 Self::Disabled
818 }
819 }
820
821 fn hidden(&self) -> Self {
822 match *self {
823 Self::Enabled {
824 setting_configuration,
825 ..
826 } => Self::Enabled {
827 setting_configuration,
828 toggle_override: setting_configuration,
829 },
830 Self::Disabled => Self::Disabled,
831 }
832 }
833
834 fn disabled(&self) -> bool {
835 matches!(*self, Self::Disabled)
836 }
837
838 fn settings_visibility(&self) -> bool {
839 match *self {
840 Self::Enabled {
841 setting_configuration,
842 ..
843 } => setting_configuration,
844 _ => false,
845 }
846 }
847
848 fn visible(&self) -> bool {
849 match *self {
850 Self::Enabled {
851 setting_configuration,
852 toggle_override,
853 } => setting_configuration ^ toggle_override,
854 _ => false,
855 }
856 }
857
858 fn toggle_visibility(&self) -> Self {
859 match *self {
860 Self::Enabled {
861 toggle_override,
862 setting_configuration,
863 } => Self::Enabled {
864 setting_configuration,
865 toggle_override: !toggle_override,
866 },
867 Self::Disabled => Self::Disabled,
868 }
869 }
870}
871
872#[derive(Debug, Clone, Copy, PartialEq, Eq)]
873pub enum BufferSerialization {
874 All,
875 NonDirtyBuffers,
876}
877
878impl BufferSerialization {
879 fn new(restore_unsaved_buffers: bool) -> Self {
880 if restore_unsaved_buffers {
881 Self::All
882 } else {
883 Self::NonDirtyBuffers
884 }
885 }
886}
887
888/// Addons allow storing per-editor state in other crates (e.g. Vim)
889pub trait Addon: 'static {
890 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
891
892 fn render_buffer_header_controls(
893 &self,
894 _: &ExcerptBoundaryInfo,
895 _: &language::BufferSnapshot,
896 _: &Window,
897 _: &App,
898 ) -> Option<AnyElement> {
899 None
900 }
901
902 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
903 None
904 }
905
906 fn to_any(&self) -> &dyn std::any::Any;
907
908 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
909 None
910 }
911}
912
913struct ChangeLocation {
914 current: Option<Vec<Anchor>>,
915 original: Vec<Anchor>,
916}
917impl ChangeLocation {
918 fn locations(&self) -> &[Anchor] {
919 self.current.as_ref().unwrap_or(&self.original)
920 }
921}
922
923/// A set of caret positions, registered when the editor was edited.
924pub struct ChangeList {
925 changes: Vec<ChangeLocation>,
926 /// Currently "selected" change.
927 position: Option<usize>,
928}
929
930impl ChangeList {
931 pub fn new() -> Self {
932 Self {
933 changes: Vec::new(),
934 position: None,
935 }
936 }
937
938 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
939 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
940 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
941 if self.changes.is_empty() {
942 return None;
943 }
944
945 let prev = self.position.unwrap_or(self.changes.len());
946 let next = if direction == Direction::Prev {
947 prev.saturating_sub(count)
948 } else {
949 (prev + count).min(self.changes.len() - 1)
950 };
951 self.position = Some(next);
952 self.changes.get(next).map(|change| change.locations())
953 }
954
955 /// Adds a new change to the list, resetting the change list position.
956 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
957 self.position.take();
958 if let Some(last) = self.changes.last_mut()
959 && group
960 {
961 last.current = Some(new_positions)
962 } else {
963 self.changes.push(ChangeLocation {
964 original: new_positions,
965 current: None,
966 });
967 }
968 }
969
970 pub fn last(&self) -> Option<&[Anchor]> {
971 self.changes.last().map(|change| change.locations())
972 }
973
974 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
975 self.changes.last().map(|change| change.original.as_slice())
976 }
977
978 pub fn invert_last_group(&mut self) {
979 if let Some(last) = self.changes.last_mut()
980 && let Some(current) = last.current.as_mut()
981 {
982 mem::swap(&mut last.original, current);
983 }
984 }
985}
986
987#[derive(Clone)]
988struct InlineBlamePopoverState {
989 scroll_handle: ScrollHandle,
990 commit_message: Option<ParsedCommitMessage>,
991 markdown: Entity<Markdown>,
992}
993
994struct InlineBlamePopover {
995 position: gpui::Point<Pixels>,
996 hide_task: Option<Task<()>>,
997 popover_bounds: Option<Bounds<Pixels>>,
998 popover_state: InlineBlamePopoverState,
999 keyboard_grace: bool,
1000}
1001
1002enum SelectionDragState {
1003 /// State when no drag related activity is detected.
1004 None,
1005 /// State when the mouse is down on a selection that is about to be dragged.
1006 ReadyToDrag {
1007 selection: Selection<Anchor>,
1008 click_position: gpui::Point<Pixels>,
1009 mouse_down_time: Instant,
1010 },
1011 /// State when the mouse is dragging the selection in the editor.
1012 Dragging {
1013 selection: Selection<Anchor>,
1014 drop_cursor: Selection<Anchor>,
1015 hide_drop_cursor: bool,
1016 },
1017}
1018
1019enum ColumnarSelectionState {
1020 FromMouse {
1021 selection_tail: Anchor,
1022 display_point: Option<DisplayPoint>,
1023 },
1024 FromSelection {
1025 selection_tail: Anchor,
1026 },
1027}
1028
1029/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1030/// a breakpoint on them.
1031#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1032struct PhantomBreakpointIndicator {
1033 display_row: DisplayRow,
1034 /// There's a small debounce between hovering over the line and showing the indicator.
1035 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1036 is_active: bool,
1037 collides_with_existing_breakpoint: bool,
1038}
1039
1040/// Represents a diff review button indicator that shows up when hovering over lines in the gutter
1041/// in diff view mode.
1042#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1043pub(crate) struct PhantomDiffReviewIndicator {
1044 /// The starting anchor of the selection (or the only row if not dragging).
1045 pub start: Anchor,
1046 /// The ending anchor of the selection. Equal to start_anchor for single-line selection.
1047 pub end: Anchor,
1048 /// There's a small debounce between hovering over the line and showing the indicator.
1049 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1050 pub is_active: bool,
1051}
1052
1053#[derive(Clone, Debug)]
1054pub(crate) struct DiffReviewDragState {
1055 pub start_anchor: Anchor,
1056 pub current_anchor: Anchor,
1057}
1058
1059impl DiffReviewDragState {
1060 pub fn row_range(&self, snapshot: &DisplaySnapshot) -> std::ops::RangeInclusive<DisplayRow> {
1061 let start = self.start_anchor.to_display_point(snapshot).row();
1062 let current = self.current_anchor.to_display_point(snapshot).row();
1063
1064 (start..=current).sorted()
1065 }
1066}
1067
1068/// Identifies a specific hunk in the diff buffer.
1069/// Used as a key to group comments by their location.
1070#[derive(Clone, Debug)]
1071pub struct DiffHunkKey {
1072 /// The file path (relative to worktree) this hunk belongs to.
1073 pub file_path: Arc<util::rel_path::RelPath>,
1074 /// An anchor at the start of the hunk. This tracks position as the buffer changes.
1075 pub hunk_start_anchor: Anchor,
1076}
1077
1078/// A review comment stored locally before being sent to the Agent panel.
1079#[derive(Clone)]
1080pub struct StoredReviewComment {
1081 /// Unique identifier for this comment (for edit/delete operations).
1082 pub id: usize,
1083 /// The comment text entered by the user.
1084 pub comment: String,
1085 /// Anchors for the code range being reviewed.
1086 pub range: Range<Anchor>,
1087 /// Timestamp when the comment was created (for chronological ordering).
1088 pub created_at: Instant,
1089 /// Whether this comment is currently being edited inline.
1090 pub is_editing: bool,
1091}
1092
1093impl StoredReviewComment {
1094 pub fn new(id: usize, comment: String, anchor_range: Range<Anchor>) -> Self {
1095 Self {
1096 id,
1097 comment,
1098 range: anchor_range,
1099 created_at: Instant::now(),
1100 is_editing: false,
1101 }
1102 }
1103}
1104
1105/// Represents an active diff review overlay that appears when clicking the "Add Review" button.
1106pub(crate) struct DiffReviewOverlay {
1107 pub anchor_range: Range<Anchor>,
1108 /// The block ID for the overlay.
1109 pub block_id: CustomBlockId,
1110 /// The editor entity for the review input.
1111 pub prompt_editor: Entity<Editor>,
1112 /// The hunk key this overlay belongs to.
1113 pub hunk_key: DiffHunkKey,
1114 /// Whether the comments section is expanded.
1115 pub comments_expanded: bool,
1116 /// Editors for comments currently being edited inline.
1117 /// Key: comment ID, Value: Editor entity for inline editing.
1118 pub inline_edit_editors: HashMap<usize, Entity<Editor>>,
1119 /// Subscriptions for inline edit editors' action handlers.
1120 /// Key: comment ID, Value: Subscription keeping the Newline action handler alive.
1121 pub inline_edit_subscriptions: HashMap<usize, Subscription>,
1122 /// The current user's avatar URI for display in comment rows.
1123 pub user_avatar_uri: Option<SharedUri>,
1124 /// Subscription to keep the action handler alive.
1125 _subscription: Subscription,
1126}
1127
1128/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1129///
1130/// See the [module level documentation](self) for more information.
1131pub struct Editor {
1132 focus_handle: FocusHandle,
1133 last_focused_descendant: Option<WeakFocusHandle>,
1134 /// The text buffer being edited
1135 buffer: Entity<MultiBuffer>,
1136 /// Map of how text in the buffer should be displayed.
1137 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1138 pub display_map: Entity<DisplayMap>,
1139 placeholder_display_map: Option<Entity<DisplayMap>>,
1140 pub selections: SelectionsCollection,
1141 pub scroll_manager: ScrollManager,
1142 /// When inline assist editors are linked, they all render cursors because
1143 /// typing enters text into each of them, even the ones that aren't focused.
1144 pub(crate) show_cursor_when_unfocused: bool,
1145 columnar_selection_state: Option<ColumnarSelectionState>,
1146 add_selections_state: Option<AddSelectionsState>,
1147 select_next_state: Option<SelectNextState>,
1148 select_prev_state: Option<SelectNextState>,
1149 selection_history: SelectionHistory,
1150 defer_selection_effects: bool,
1151 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1152 autoclose_regions: Vec<AutocloseRegion>,
1153 snippet_stack: InvalidationStack<SnippetState>,
1154 select_syntax_node_history: SelectSyntaxNodeHistory,
1155 ime_transaction: Option<TransactionId>,
1156 pub diagnostics_max_severity: DiagnosticSeverity,
1157 active_diagnostics: ActiveDiagnostic,
1158 show_inline_diagnostics: bool,
1159 inline_diagnostics_update: Task<()>,
1160 inline_diagnostics_enabled: bool,
1161 diagnostics_enabled: bool,
1162 word_completions_enabled: bool,
1163 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1164 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1165 hard_wrap: Option<usize>,
1166 project: Option<Entity<Project>>,
1167 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1168 completion_provider: Option<Rc<dyn CompletionProvider>>,
1169 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1170 blink_manager: Entity<BlinkManager>,
1171 show_cursor_names: bool,
1172 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1173 pub show_local_selections: bool,
1174 mode: EditorMode,
1175 show_breadcrumbs: bool,
1176 show_gutter: bool,
1177 show_scrollbars: ScrollbarAxes,
1178 minimap_visibility: MinimapVisibility,
1179 offset_content: bool,
1180 disable_expand_excerpt_buttons: bool,
1181 delegate_expand_excerpts: bool,
1182 delegate_stage_and_restore: bool,
1183 delegate_open_excerpts: bool,
1184 enable_lsp_data: bool,
1185 enable_runnables: bool,
1186 show_line_numbers: Option<bool>,
1187 use_relative_line_numbers: Option<bool>,
1188 show_git_diff_gutter: Option<bool>,
1189 show_code_actions: Option<bool>,
1190 show_runnables: Option<bool>,
1191 show_breakpoints: Option<bool>,
1192 show_diff_review_button: bool,
1193 show_wrap_guides: Option<bool>,
1194 show_indent_guides: Option<bool>,
1195 buffers_with_disabled_indent_guides: HashSet<BufferId>,
1196 highlight_order: usize,
1197 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1198 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1199 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1200 scrollbar_marker_state: ScrollbarMarkerState,
1201 active_indent_guides_state: ActiveIndentGuidesState,
1202 nav_history: Option<ItemNavHistory>,
1203 context_menu: RefCell<Option<CodeContextMenu>>,
1204 context_menu_options: Option<ContextMenuOptions>,
1205 mouse_context_menu: Option<MouseContextMenu>,
1206 completion_tasks: Vec<(CompletionId, Task<()>)>,
1207 inline_blame_popover: Option<InlineBlamePopover>,
1208 inline_blame_popover_show_task: Option<Task<()>>,
1209 signature_help_state: SignatureHelpState,
1210 auto_signature_help: Option<bool>,
1211 find_all_references_task_sources: Vec<Anchor>,
1212 next_completion_id: CompletionId,
1213 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1214 code_actions_task: Option<Task<Result<()>>>,
1215 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1216 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1217 debounced_selection_highlight_complete: bool,
1218 document_highlights_task: Option<Task<()>>,
1219 linked_editing_range_task: Option<Task<Option<()>>>,
1220 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1221 pending_rename: Option<RenameState>,
1222 searchable: bool,
1223 cursor_shape: CursorShape,
1224 /// Whether the cursor is offset one character to the left when something is
1225 /// selected (needed for vim visual mode)
1226 cursor_offset_on_selection: bool,
1227 current_line_highlight: Option<CurrentLineHighlight>,
1228 /// Whether to collapse search match ranges to just their start position.
1229 /// When true, navigating to a match positions the cursor at the match
1230 /// without selecting the matched text.
1231 collapse_matches: bool,
1232 autoindent_mode: Option<AutoindentMode>,
1233 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1234 input_enabled: bool,
1235 expects_character_input: bool,
1236 use_modal_editing: bool,
1237 read_only: bool,
1238 leader_id: Option<CollaboratorId>,
1239 remote_id: Option<ViewId>,
1240 pub hover_state: HoverState,
1241 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1242 prev_pressure_stage: Option<PressureStage>,
1243 gutter_hovered: bool,
1244 hovered_link_state: Option<HoveredLinkState>,
1245 edit_prediction_provider: Option<RegisteredEditPredictionDelegate>,
1246 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1247 active_edit_prediction: Option<EditPredictionState>,
1248 /// Used to prevent flickering as the user types while the menu is open
1249 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1250 edit_prediction_settings: EditPredictionSettings,
1251 edit_predictions_hidden_for_vim_mode: bool,
1252 show_edit_predictions_override: Option<bool>,
1253 show_completions_on_input_override: Option<bool>,
1254 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1255 edit_prediction_preview: EditPredictionPreview,
1256 in_leading_whitespace: bool,
1257 next_inlay_id: usize,
1258 next_color_inlay_id: usize,
1259 _subscriptions: Vec<Subscription>,
1260 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1261 gutter_dimensions: GutterDimensions,
1262 style: Option<EditorStyle>,
1263 text_style_refinement: Option<TextStyleRefinement>,
1264 next_editor_action_id: EditorActionId,
1265 editor_actions: Rc<
1266 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1267 >,
1268 use_autoclose: bool,
1269 use_auto_surround: bool,
1270 use_selection_highlight: bool,
1271 auto_replace_emoji_shortcode: bool,
1272 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1273 show_git_blame_gutter: bool,
1274 show_git_blame_inline: bool,
1275 show_git_blame_inline_delay_task: Option<Task<()>>,
1276 git_blame_inline_enabled: bool,
1277 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1278 buffer_serialization: Option<BufferSerialization>,
1279 show_selection_menu: Option<bool>,
1280 blame: Option<Entity<GitBlame>>,
1281 blame_subscription: Option<Subscription>,
1282 custom_context_menu: Option<
1283 Box<
1284 dyn 'static
1285 + Fn(
1286 &mut Self,
1287 DisplayPoint,
1288 &mut Window,
1289 &mut Context<Self>,
1290 ) -> Option<Entity<ui::ContextMenu>>,
1291 >,
1292 >,
1293 last_bounds: Option<Bounds<Pixels>>,
1294 last_position_map: Option<Rc<PositionMap>>,
1295 expect_bounds_change: Option<Bounds<Pixels>>,
1296 runnables: RunnableData,
1297 breakpoint_store: Option<Entity<BreakpointStore>>,
1298 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1299 pub(crate) gutter_diff_review_indicator: (Option<PhantomDiffReviewIndicator>, Option<Task<()>>),
1300 pub(crate) diff_review_drag_state: Option<DiffReviewDragState>,
1301 /// Active diff review overlays. Multiple overlays can be open simultaneously
1302 /// when hunks have comments stored.
1303 pub(crate) diff_review_overlays: Vec<DiffReviewOverlay>,
1304 /// Stored review comments grouped by hunk.
1305 /// Uses a Vec instead of HashMap because DiffHunkKey contains an Anchor
1306 /// which doesn't implement Hash/Eq in a way suitable for HashMap keys.
1307 stored_review_comments: Vec<(DiffHunkKey, Vec<StoredReviewComment>)>,
1308 /// Counter for generating unique comment IDs.
1309 next_review_comment_id: usize,
1310 hovered_diff_hunk_row: Option<DisplayRow>,
1311 pull_diagnostics_task: Task<()>,
1312 in_project_search: bool,
1313 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1314 breadcrumb_header: Option<String>,
1315 focused_block: Option<FocusedBlock>,
1316 next_scroll_position: NextScrollCursorCenterTopBottom,
1317 addons: HashMap<TypeId, Box<dyn Addon>>,
1318 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1319 load_diff_task: Option<Shared<Task<()>>>,
1320 /// Whether we are temporarily displaying a diff other than git's
1321 temporary_diff_override: bool,
1322 selection_mark_mode: bool,
1323 toggle_fold_multiple_buffers: Task<()>,
1324 _scroll_cursor_center_top_bottom_task: Task<()>,
1325 serialize_selections: Task<()>,
1326 serialize_folds: Task<()>,
1327 mouse_cursor_hidden: bool,
1328 minimap: Option<Entity<Self>>,
1329 hide_mouse_mode: HideMouseMode,
1330 pub change_list: ChangeList,
1331 inline_value_cache: InlineValueCache,
1332 number_deleted_lines: bool,
1333
1334 selection_drag_state: SelectionDragState,
1335 colors: Option<LspColorData>,
1336 post_scroll_update: Task<()>,
1337 refresh_colors_task: Task<()>,
1338 use_document_folding_ranges: bool,
1339 refresh_folding_ranges_task: Task<()>,
1340 inlay_hints: Option<LspInlayHintData>,
1341 folding_newlines: Task<()>,
1342 select_next_is_case_sensitive: Option<bool>,
1343 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1344 on_local_selections_changed:
1345 Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
1346 suppress_selection_callback: bool,
1347 applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
1348 accent_data: Option<AccentData>,
1349 bracket_fetched_tree_sitter_chunks: HashMap<Range<text::Anchor>, HashSet<Range<BufferRow>>>,
1350 semantic_token_state: SemanticTokenState,
1351 pub(crate) refresh_matching_bracket_highlights_task: Task<()>,
1352 refresh_document_symbols_task: Shared<Task<()>>,
1353 lsp_document_symbols: HashMap<BufferId, Vec<OutlineItem<text::Anchor>>>,
1354 refresh_outline_symbols_at_cursor_at_cursor_task: Task<()>,
1355 outline_symbols_at_cursor: Option<(BufferId, Vec<OutlineItem<Anchor>>)>,
1356 sticky_headers_task: Task<()>,
1357 sticky_headers: Option<Vec<OutlineItem<Anchor>>>,
1358 pub(crate) colorize_brackets_task: Task<()>,
1359}
1360
1361#[derive(Debug, PartialEq)]
1362struct AccentData {
1363 colors: AccentColors,
1364 overrides: Vec<SharedString>,
1365}
1366
1367fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1368 if debounce_ms > 0 {
1369 Some(Duration::from_millis(debounce_ms))
1370 } else {
1371 None
1372 }
1373}
1374
1375#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1376enum NextScrollCursorCenterTopBottom {
1377 #[default]
1378 Center,
1379 Top,
1380 Bottom,
1381}
1382
1383impl NextScrollCursorCenterTopBottom {
1384 fn next(&self) -> Self {
1385 match self {
1386 Self::Center => Self::Top,
1387 Self::Top => Self::Bottom,
1388 Self::Bottom => Self::Center,
1389 }
1390 }
1391}
1392
1393#[derive(Clone)]
1394pub struct EditorSnapshot {
1395 pub mode: EditorMode,
1396 show_gutter: bool,
1397 offset_content: bool,
1398 show_line_numbers: Option<bool>,
1399 number_deleted_lines: bool,
1400 show_git_diff_gutter: Option<bool>,
1401 show_code_actions: Option<bool>,
1402 show_runnables: Option<bool>,
1403 show_breakpoints: Option<bool>,
1404 git_blame_gutter_max_author_length: Option<usize>,
1405 pub display_snapshot: DisplaySnapshot,
1406 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1407 is_focused: bool,
1408 scroll_anchor: SharedScrollAnchor,
1409 ongoing_scroll: OngoingScroll,
1410 current_line_highlight: CurrentLineHighlight,
1411 gutter_hovered: bool,
1412 semantic_tokens_enabled: bool,
1413}
1414
1415#[derive(Default, Debug, Clone, Copy)]
1416pub struct GutterDimensions {
1417 pub left_padding: Pixels,
1418 pub right_padding: Pixels,
1419 pub width: Pixels,
1420 pub margin: Pixels,
1421 pub git_blame_entries_width: Option<Pixels>,
1422}
1423
1424impl GutterDimensions {
1425 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1426 Self {
1427 margin: Self::default_gutter_margin(font_id, font_size, cx),
1428 ..Default::default()
1429 }
1430 }
1431
1432 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1433 -cx.text_system().descent(font_id, font_size)
1434 }
1435 /// The full width of the space taken up by the gutter.
1436 pub fn full_width(&self) -> Pixels {
1437 self.margin + self.width
1438 }
1439
1440 /// The width of the space reserved for the fold indicators,
1441 /// use alongside 'justify_end' and `gutter_width` to
1442 /// right align content with the line numbers
1443 pub fn fold_area_width(&self) -> Pixels {
1444 self.margin + self.right_padding
1445 }
1446}
1447
1448struct CharacterDimensions {
1449 em_width: Pixels,
1450 em_advance: Pixels,
1451 line_height: Pixels,
1452}
1453
1454#[derive(Debug)]
1455pub struct RemoteSelection {
1456 pub replica_id: ReplicaId,
1457 pub selection: Selection<Anchor>,
1458 pub cursor_shape: CursorShape,
1459 pub collaborator_id: CollaboratorId,
1460 pub line_mode: bool,
1461 pub user_name: Option<SharedString>,
1462 pub color: PlayerColor,
1463}
1464
1465#[derive(Clone, Debug)]
1466struct SelectionHistoryEntry {
1467 selections: Arc<[Selection<Anchor>]>,
1468 select_next_state: Option<SelectNextState>,
1469 select_prev_state: Option<SelectNextState>,
1470 add_selections_state: Option<AddSelectionsState>,
1471}
1472
1473#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1474enum SelectionHistoryMode {
1475 #[default]
1476 Normal,
1477 Undoing,
1478 Redoing,
1479 Skipping,
1480}
1481
1482#[derive(Clone, PartialEq, Eq, Hash)]
1483struct HoveredCursor {
1484 replica_id: ReplicaId,
1485 selection_id: usize,
1486}
1487
1488#[derive(Debug)]
1489/// SelectionEffects controls the side-effects of updating the selection.
1490///
1491/// The default behaviour does "what you mostly want":
1492/// - it pushes to the nav history if the cursor moved by >10 lines
1493/// - it re-triggers completion requests
1494/// - it scrolls to fit
1495///
1496/// You might want to modify these behaviours. For example when doing a "jump"
1497/// like go to definition, we always want to add to nav history; but when scrolling
1498/// in vim mode we never do.
1499///
1500/// Similarly, you might want to disable scrolling if you don't want the viewport to
1501/// move.
1502#[derive(Clone)]
1503pub struct SelectionEffects {
1504 nav_history: Option<bool>,
1505 completions: bool,
1506 scroll: Option<Autoscroll>,
1507}
1508
1509impl Default for SelectionEffects {
1510 fn default() -> Self {
1511 Self {
1512 nav_history: None,
1513 completions: true,
1514 scroll: Some(Autoscroll::fit()),
1515 }
1516 }
1517}
1518impl SelectionEffects {
1519 pub fn scroll(scroll: Autoscroll) -> Self {
1520 Self {
1521 scroll: Some(scroll),
1522 ..Default::default()
1523 }
1524 }
1525
1526 pub fn no_scroll() -> Self {
1527 Self {
1528 scroll: None,
1529 ..Default::default()
1530 }
1531 }
1532
1533 pub fn completions(self, completions: bool) -> Self {
1534 Self {
1535 completions,
1536 ..self
1537 }
1538 }
1539
1540 pub fn nav_history(self, nav_history: bool) -> Self {
1541 Self {
1542 nav_history: Some(nav_history),
1543 ..self
1544 }
1545 }
1546}
1547
1548struct DeferredSelectionEffectsState {
1549 changed: bool,
1550 effects: SelectionEffects,
1551 old_cursor_position: Anchor,
1552 history_entry: SelectionHistoryEntry,
1553}
1554
1555#[derive(Default)]
1556struct SelectionHistory {
1557 #[allow(clippy::type_complexity)]
1558 selections_by_transaction:
1559 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1560 mode: SelectionHistoryMode,
1561 undo_stack: VecDeque<SelectionHistoryEntry>,
1562 redo_stack: VecDeque<SelectionHistoryEntry>,
1563}
1564
1565impl SelectionHistory {
1566 #[track_caller]
1567 fn insert_transaction(
1568 &mut self,
1569 transaction_id: TransactionId,
1570 selections: Arc<[Selection<Anchor>]>,
1571 ) {
1572 if selections.is_empty() {
1573 log::error!(
1574 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1575 std::panic::Location::caller()
1576 );
1577 return;
1578 }
1579 self.selections_by_transaction
1580 .insert(transaction_id, (selections, None));
1581 }
1582
1583 #[allow(clippy::type_complexity)]
1584 fn transaction(
1585 &self,
1586 transaction_id: TransactionId,
1587 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1588 self.selections_by_transaction.get(&transaction_id)
1589 }
1590
1591 #[allow(clippy::type_complexity)]
1592 fn transaction_mut(
1593 &mut self,
1594 transaction_id: TransactionId,
1595 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1596 self.selections_by_transaction.get_mut(&transaction_id)
1597 }
1598
1599 fn push(&mut self, entry: SelectionHistoryEntry) {
1600 if !entry.selections.is_empty() {
1601 match self.mode {
1602 SelectionHistoryMode::Normal => {
1603 self.push_undo(entry);
1604 self.redo_stack.clear();
1605 }
1606 SelectionHistoryMode::Undoing => self.push_redo(entry),
1607 SelectionHistoryMode::Redoing => self.push_undo(entry),
1608 SelectionHistoryMode::Skipping => {}
1609 }
1610 }
1611 }
1612
1613 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1614 if self
1615 .undo_stack
1616 .back()
1617 .is_none_or(|e| e.selections != entry.selections)
1618 {
1619 self.undo_stack.push_back(entry);
1620 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1621 self.undo_stack.pop_front();
1622 }
1623 }
1624 }
1625
1626 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1627 if self
1628 .redo_stack
1629 .back()
1630 .is_none_or(|e| e.selections != entry.selections)
1631 {
1632 self.redo_stack.push_back(entry);
1633 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1634 self.redo_stack.pop_front();
1635 }
1636 }
1637 }
1638}
1639
1640#[derive(Clone, Copy)]
1641pub struct RowHighlightOptions {
1642 pub autoscroll: bool,
1643 pub include_gutter: bool,
1644}
1645
1646impl Default for RowHighlightOptions {
1647 fn default() -> Self {
1648 Self {
1649 autoscroll: Default::default(),
1650 include_gutter: true,
1651 }
1652 }
1653}
1654
1655struct RowHighlight {
1656 index: usize,
1657 range: Range<Anchor>,
1658 color: Hsla,
1659 options: RowHighlightOptions,
1660 type_id: TypeId,
1661}
1662
1663#[derive(Clone, Debug)]
1664struct AddSelectionsState {
1665 groups: Vec<AddSelectionsGroup>,
1666}
1667
1668#[derive(Clone, Debug)]
1669struct AddSelectionsGroup {
1670 above: bool,
1671 stack: Vec<usize>,
1672}
1673
1674#[derive(Clone)]
1675struct SelectNextState {
1676 query: AhoCorasick,
1677 wordwise: bool,
1678 done: bool,
1679}
1680
1681impl std::fmt::Debug for SelectNextState {
1682 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1683 f.debug_struct(std::any::type_name::<Self>())
1684 .field("wordwise", &self.wordwise)
1685 .field("done", &self.done)
1686 .finish()
1687 }
1688}
1689
1690#[derive(Debug)]
1691struct AutocloseRegion {
1692 selection_id: usize,
1693 range: Range<Anchor>,
1694 pair: BracketPair,
1695}
1696
1697#[derive(Debug)]
1698struct SnippetState {
1699 ranges: Vec<Vec<Range<Anchor>>>,
1700 active_index: usize,
1701 choices: Vec<Option<Vec<String>>>,
1702}
1703
1704#[doc(hidden)]
1705pub struct RenameState {
1706 pub range: Range<Anchor>,
1707 pub old_name: Arc<str>,
1708 pub editor: Entity<Editor>,
1709 block_id: CustomBlockId,
1710}
1711
1712struct InvalidationStack<T>(Vec<T>);
1713
1714struct RegisteredEditPredictionDelegate {
1715 provider: Arc<dyn EditPredictionDelegateHandle>,
1716 _subscription: Subscription,
1717}
1718
1719#[derive(Debug, PartialEq, Eq)]
1720pub struct ActiveDiagnosticGroup {
1721 pub active_range: Range<Anchor>,
1722 pub active_message: String,
1723 pub group_id: usize,
1724 pub blocks: HashSet<CustomBlockId>,
1725}
1726
1727#[derive(Debug, PartialEq, Eq)]
1728
1729pub(crate) enum ActiveDiagnostic {
1730 None,
1731 All,
1732 Group(ActiveDiagnosticGroup),
1733}
1734
1735#[derive(Serialize, Deserialize, Clone, Debug)]
1736pub struct ClipboardSelection {
1737 /// The number of bytes in this selection.
1738 pub len: usize,
1739 /// Whether this was a full-line selection.
1740 pub is_entire_line: bool,
1741 /// The indentation of the first line when this content was originally copied.
1742 pub first_line_indent: u32,
1743 #[serde(default)]
1744 pub file_path: Option<PathBuf>,
1745 #[serde(default)]
1746 pub line_range: Option<RangeInclusive<u32>>,
1747}
1748
1749impl ClipboardSelection {
1750 pub fn for_buffer(
1751 len: usize,
1752 is_entire_line: bool,
1753 range: Range<Point>,
1754 buffer: &MultiBufferSnapshot,
1755 project: Option<&Entity<Project>>,
1756 cx: &App,
1757 ) -> Self {
1758 let first_line_indent = buffer
1759 .indent_size_for_line(MultiBufferRow(range.start.row))
1760 .len;
1761
1762 let file_path = util::maybe!({
1763 let project = project?.read(cx);
1764 let file = buffer.file_at(range.start)?;
1765 let project_path = ProjectPath {
1766 worktree_id: file.worktree_id(cx),
1767 path: file.path().clone(),
1768 };
1769 project.absolute_path(&project_path, cx)
1770 });
1771
1772 let line_range = if file_path.is_some() {
1773 buffer
1774 .range_to_buffer_range(range)
1775 .map(|(_, buffer_range)| buffer_range.start.row..=buffer_range.end.row)
1776 } else {
1777 None
1778 };
1779
1780 Self {
1781 len,
1782 is_entire_line,
1783 first_line_indent,
1784 file_path,
1785 line_range,
1786 }
1787 }
1788}
1789
1790// selections, scroll behavior, was newest selection reversed
1791type SelectSyntaxNodeHistoryState = (
1792 Box<[Selection<Anchor>]>,
1793 SelectSyntaxNodeScrollBehavior,
1794 bool,
1795);
1796
1797#[derive(Default)]
1798struct SelectSyntaxNodeHistory {
1799 stack: Vec<SelectSyntaxNodeHistoryState>,
1800 // disable temporarily to allow changing selections without losing the stack
1801 pub disable_clearing: bool,
1802}
1803
1804impl SelectSyntaxNodeHistory {
1805 pub fn try_clear(&mut self) {
1806 if !self.disable_clearing {
1807 self.stack.clear();
1808 }
1809 }
1810
1811 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1812 self.stack.push(selection);
1813 }
1814
1815 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1816 self.stack.pop()
1817 }
1818}
1819
1820enum SelectSyntaxNodeScrollBehavior {
1821 CursorTop,
1822 FitSelection,
1823 CursorBottom,
1824}
1825
1826#[derive(Debug, Clone, Copy)]
1827pub(crate) struct NavigationData {
1828 cursor_anchor: Anchor,
1829 cursor_position: Point,
1830 scroll_anchor: ScrollAnchor,
1831 scroll_top_row: u32,
1832}
1833
1834#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1835pub enum GotoDefinitionKind {
1836 Symbol,
1837 Declaration,
1838 Type,
1839 Implementation,
1840}
1841
1842pub enum FormatTarget {
1843 Buffers(HashSet<Entity<Buffer>>),
1844 Ranges(Vec<Range<MultiBufferPoint>>),
1845}
1846
1847pub(crate) struct FocusedBlock {
1848 id: BlockId,
1849 focus_handle: WeakFocusHandle,
1850}
1851
1852#[derive(Clone, Debug)]
1853pub enum JumpData {
1854 MultiBufferRow {
1855 row: MultiBufferRow,
1856 line_offset_from_top: u32,
1857 },
1858 MultiBufferPoint {
1859 anchor: language::Anchor,
1860 position: Point,
1861 line_offset_from_top: u32,
1862 },
1863}
1864
1865pub enum MultibufferSelectionMode {
1866 First,
1867 All,
1868}
1869
1870#[derive(Clone, Copy, Debug, Default)]
1871pub struct RewrapOptions {
1872 pub override_language_settings: bool,
1873 pub preserve_existing_whitespace: bool,
1874 pub line_length: Option<usize>,
1875}
1876
1877impl Editor {
1878 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1879 let buffer = cx.new(|cx| Buffer::local("", cx));
1880 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1881 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1882 }
1883
1884 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1885 let buffer = cx.new(|cx| Buffer::local("", cx));
1886 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1887 Self::new(EditorMode::full(), buffer, None, window, cx)
1888 }
1889
1890 pub fn auto_height(
1891 min_lines: usize,
1892 max_lines: usize,
1893 window: &mut Window,
1894 cx: &mut Context<Self>,
1895 ) -> Self {
1896 let buffer = cx.new(|cx| Buffer::local("", cx));
1897 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1898 Self::new(
1899 EditorMode::AutoHeight {
1900 min_lines,
1901 max_lines: Some(max_lines),
1902 },
1903 buffer,
1904 None,
1905 window,
1906 cx,
1907 )
1908 }
1909
1910 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1911 /// The editor grows as tall as needed to fit its content.
1912 pub fn auto_height_unbounded(
1913 min_lines: usize,
1914 window: &mut Window,
1915 cx: &mut Context<Self>,
1916 ) -> Self {
1917 let buffer = cx.new(|cx| Buffer::local("", cx));
1918 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1919 Self::new(
1920 EditorMode::AutoHeight {
1921 min_lines,
1922 max_lines: None,
1923 },
1924 buffer,
1925 None,
1926 window,
1927 cx,
1928 )
1929 }
1930
1931 pub fn for_buffer(
1932 buffer: Entity<Buffer>,
1933 project: Option<Entity<Project>>,
1934 window: &mut Window,
1935 cx: &mut Context<Self>,
1936 ) -> Self {
1937 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1938 Self::new(EditorMode::full(), buffer, project, window, cx)
1939 }
1940
1941 pub fn for_multibuffer(
1942 buffer: Entity<MultiBuffer>,
1943 project: Option<Entity<Project>>,
1944 window: &mut Window,
1945 cx: &mut Context<Self>,
1946 ) -> Self {
1947 Self::new(EditorMode::full(), buffer, project, window, cx)
1948 }
1949
1950 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1951 let mut clone = Self::new(
1952 self.mode.clone(),
1953 self.buffer.clone(),
1954 self.project.clone(),
1955 window,
1956 cx,
1957 );
1958 let my_snapshot = self.display_map.update(cx, |display_map, cx| {
1959 let snapshot = display_map.snapshot(cx);
1960 clone.display_map.update(cx, |display_map, cx| {
1961 display_map.set_state(&snapshot, cx);
1962 });
1963 snapshot
1964 });
1965 let clone_snapshot = clone.display_map.update(cx, |map, cx| map.snapshot(cx));
1966 clone.folds_did_change(cx);
1967 clone.selections.clone_state(&self.selections);
1968 clone
1969 .scroll_manager
1970 .clone_state(&self.scroll_manager, &my_snapshot, &clone_snapshot, cx);
1971 clone.searchable = self.searchable;
1972 clone.read_only = self.read_only;
1973 clone.buffers_with_disabled_indent_guides =
1974 self.buffers_with_disabled_indent_guides.clone();
1975 clone
1976 }
1977
1978 pub fn new(
1979 mode: EditorMode,
1980 buffer: Entity<MultiBuffer>,
1981 project: Option<Entity<Project>>,
1982 window: &mut Window,
1983 cx: &mut Context<Self>,
1984 ) -> Self {
1985 Editor::new_internal(mode, buffer, project, None, window, cx)
1986 }
1987
1988 pub fn refresh_sticky_headers(
1989 &mut self,
1990 display_snapshot: &DisplaySnapshot,
1991 cx: &mut Context<Editor>,
1992 ) {
1993 if !self.mode.is_full() {
1994 return;
1995 }
1996 let multi_buffer = display_snapshot.buffer_snapshot().clone();
1997 let scroll_anchor = self
1998 .scroll_manager
1999 .native_anchor(display_snapshot, cx)
2000 .anchor;
2001 let Some(buffer_snapshot) = multi_buffer.as_singleton() else {
2002 return;
2003 };
2004
2005 let buffer = buffer_snapshot.clone();
2006 let Some((buffer_visible_start, _)) = multi_buffer.anchor_to_buffer_anchor(scroll_anchor)
2007 else {
2008 return;
2009 };
2010 let buffer_visible_start = buffer_visible_start.to_point(&buffer);
2011 let max_row = buffer.max_point().row;
2012 let start_row = buffer_visible_start.row.min(max_row);
2013 let end_row = (buffer_visible_start.row + 10).min(max_row);
2014
2015 let syntax = self.style(cx).syntax.clone();
2016 let background_task = cx.background_spawn(async move {
2017 buffer
2018 .outline_items_containing(
2019 Point::new(start_row, 0)..Point::new(end_row, 0),
2020 true,
2021 Some(syntax.as_ref()),
2022 )
2023 .into_iter()
2024 .filter_map(|outline_item| {
2025 Some(OutlineItem {
2026 depth: outline_item.depth,
2027 range: multi_buffer
2028 .buffer_anchor_range_to_anchor_range(outline_item.range)?,
2029 source_range_for_text: multi_buffer.buffer_anchor_range_to_anchor_range(
2030 outline_item.source_range_for_text,
2031 )?,
2032 text: outline_item.text,
2033 highlight_ranges: outline_item.highlight_ranges,
2034 name_ranges: outline_item.name_ranges,
2035 body_range: outline_item.body_range.and_then(|range| {
2036 multi_buffer.buffer_anchor_range_to_anchor_range(range)
2037 }),
2038 annotation_range: outline_item.annotation_range.and_then(|range| {
2039 multi_buffer.buffer_anchor_range_to_anchor_range(range)
2040 }),
2041 })
2042 })
2043 .collect()
2044 });
2045 self.sticky_headers_task = cx.spawn(async move |this, cx| {
2046 let sticky_headers = background_task.await;
2047 this.update(cx, |this, cx| {
2048 this.sticky_headers = Some(sticky_headers);
2049 cx.notify();
2050 })
2051 .ok();
2052 });
2053 }
2054
2055 fn new_internal(
2056 mode: EditorMode,
2057 multi_buffer: Entity<MultiBuffer>,
2058 project: Option<Entity<Project>>,
2059 display_map: Option<Entity<DisplayMap>>,
2060 window: &mut Window,
2061 cx: &mut Context<Self>,
2062 ) -> Self {
2063 debug_assert!(
2064 display_map.is_none() || mode.is_minimap(),
2065 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
2066 );
2067
2068 let full_mode = mode.is_full();
2069 let is_minimap = mode.is_minimap();
2070 let diagnostics_max_severity = if full_mode {
2071 EditorSettings::get_global(cx)
2072 .diagnostics_max_severity
2073 .unwrap_or(DiagnosticSeverity::Hint)
2074 } else {
2075 DiagnosticSeverity::Off
2076 };
2077 let style = window.text_style();
2078 let font_size = style.font_size.to_pixels(window.rem_size());
2079 let editor = cx.entity().downgrade();
2080 let fold_placeholder = FoldPlaceholder {
2081 constrain_width: false,
2082 render: Arc::new(move |fold_id, fold_range, cx| {
2083 let editor = editor.clone();
2084 FoldPlaceholder::fold_element(fold_id, cx)
2085 .cursor_pointer()
2086 .child("⋯")
2087 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
2088 .on_click(move |_, _window, cx| {
2089 editor
2090 .update(cx, |editor, cx| {
2091 editor.unfold_ranges(
2092 &[fold_range.start..fold_range.end],
2093 true,
2094 false,
2095 cx,
2096 );
2097 cx.stop_propagation();
2098 })
2099 .ok();
2100 })
2101 .into_any()
2102 }),
2103 merge_adjacent: true,
2104 ..FoldPlaceholder::default()
2105 };
2106 let display_map = display_map.unwrap_or_else(|| {
2107 cx.new(|cx| {
2108 DisplayMap::new(
2109 multi_buffer.clone(),
2110 style.font(),
2111 font_size,
2112 None,
2113 FILE_HEADER_HEIGHT,
2114 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2115 fold_placeholder,
2116 diagnostics_max_severity,
2117 cx,
2118 )
2119 })
2120 });
2121
2122 let selections = SelectionsCollection::new();
2123
2124 let blink_manager = cx.new(|cx| {
2125 let mut blink_manager = BlinkManager::new(
2126 CURSOR_BLINK_INTERVAL,
2127 |cx| EditorSettings::get_global(cx).cursor_blink,
2128 cx,
2129 );
2130 if is_minimap {
2131 blink_manager.disable(cx);
2132 }
2133 blink_manager
2134 });
2135
2136 let soft_wrap_mode_override =
2137 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
2138
2139 let mut project_subscriptions = Vec::new();
2140 if full_mode && let Some(project) = project.as_ref() {
2141 project_subscriptions.push(cx.subscribe_in(
2142 project,
2143 window,
2144 |editor, _, event, window, cx| match event {
2145 project::Event::RefreshCodeLens => {
2146 // we always query lens with actions, without storing them, always refreshing them
2147 }
2148 project::Event::RefreshInlayHints {
2149 server_id,
2150 request_id,
2151 } => {
2152 editor.refresh_inlay_hints(
2153 InlayHintRefreshReason::RefreshRequested {
2154 server_id: *server_id,
2155 request_id: *request_id,
2156 },
2157 cx,
2158 );
2159 }
2160 project::Event::RefreshSemanticTokens {
2161 server_id,
2162 request_id,
2163 } => {
2164 editor.refresh_semantic_tokens(
2165 None,
2166 Some(RefreshForServer {
2167 server_id: *server_id,
2168 request_id: *request_id,
2169 }),
2170 cx,
2171 );
2172 }
2173 project::Event::LanguageServerRemoved(_) => {
2174 editor.registered_buffers.clear();
2175 editor.register_visible_buffers(cx);
2176 editor.invalidate_semantic_tokens(None);
2177 editor.refresh_runnables(None, window, cx);
2178 editor.update_lsp_data(None, window, cx);
2179 editor.refresh_inlay_hints(InlayHintRefreshReason::ServerRemoved, cx);
2180 }
2181 project::Event::SnippetEdit(id, snippet_edits) => {
2182 // todo(lw): Non singletons
2183 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
2184 let snapshot = buffer.read(cx).snapshot();
2185 let focus_handle = editor.focus_handle(cx);
2186 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
2187 for (range, snippet) in snippet_edits {
2188 let buffer_range =
2189 language::range_from_lsp(*range).to_offset(&snapshot);
2190 editor
2191 .insert_snippet(
2192 &[MultiBufferOffset(buffer_range.start)
2193 ..MultiBufferOffset(buffer_range.end)],
2194 snippet.clone(),
2195 window,
2196 cx,
2197 )
2198 .ok();
2199 }
2200 }
2201 }
2202 }
2203 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
2204 let buffer_id = *buffer_id;
2205 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
2206 editor.register_buffer(buffer_id, cx);
2207 editor.refresh_runnables(Some(buffer_id), window, cx);
2208 editor.update_lsp_data(Some(buffer_id), window, cx);
2209 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
2210 refresh_linked_ranges(editor, window, cx);
2211 editor.refresh_code_actions(window, cx);
2212 editor.refresh_document_highlights(cx);
2213 }
2214 }
2215
2216 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
2217 let Some(workspace) = editor.workspace() else {
2218 return;
2219 };
2220 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2221 else {
2222 return;
2223 };
2224
2225 if active_editor.entity_id() == cx.entity_id() {
2226 let entity_id = cx.entity_id();
2227 workspace.update(cx, |this, cx| {
2228 this.panes_mut()
2229 .iter_mut()
2230 .filter(|pane| pane.entity_id() != entity_id)
2231 .for_each(|p| {
2232 p.update(cx, |pane, _| {
2233 pane.nav_history_mut().rename_item(
2234 entity_id,
2235 project_path.clone(),
2236 abs_path.clone().into(),
2237 );
2238 })
2239 });
2240 });
2241
2242 Self::open_transaction_for_hidden_buffers(
2243 workspace,
2244 transaction.clone(),
2245 "Rename".to_string(),
2246 window,
2247 cx,
2248 );
2249 }
2250 }
2251
2252 project::Event::WorkspaceEditApplied(transaction) => {
2253 let Some(workspace) = editor.workspace() else {
2254 return;
2255 };
2256 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2257 else {
2258 return;
2259 };
2260
2261 if active_editor.entity_id() == cx.entity_id() {
2262 Self::open_transaction_for_hidden_buffers(
2263 workspace,
2264 transaction.clone(),
2265 "LSP Edit".to_string(),
2266 window,
2267 cx,
2268 );
2269 }
2270 }
2271
2272 _ => {}
2273 },
2274 ));
2275 if let Some(task_inventory) = project
2276 .read(cx)
2277 .task_store()
2278 .read(cx)
2279 .task_inventory()
2280 .cloned()
2281 {
2282 project_subscriptions.push(cx.observe_in(
2283 &task_inventory,
2284 window,
2285 |editor, _, window, cx| {
2286 editor.refresh_runnables(None, window, cx);
2287 },
2288 ));
2289 };
2290
2291 project_subscriptions.push(cx.subscribe_in(
2292 &project.read(cx).breakpoint_store(),
2293 window,
2294 |editor, _, event, window, cx| match event {
2295 BreakpointStoreEvent::ClearDebugLines => {
2296 editor.clear_row_highlights::<ActiveDebugLine>();
2297 editor.refresh_inline_values(cx);
2298 }
2299 BreakpointStoreEvent::SetDebugLine => {
2300 if editor.go_to_active_debug_line(window, cx) {
2301 cx.stop_propagation();
2302 }
2303
2304 editor.refresh_inline_values(cx);
2305 }
2306 _ => {}
2307 },
2308 ));
2309 let git_store = project.read(cx).git_store().clone();
2310 let project = project.clone();
2311 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2312 if let GitStoreEvent::RepositoryAdded = event {
2313 this.load_diff_task = Some(
2314 update_uncommitted_diff_for_buffer(
2315 cx.entity(),
2316 &project,
2317 this.buffer.read(cx).all_buffers(),
2318 this.buffer.clone(),
2319 cx,
2320 )
2321 .shared(),
2322 );
2323 }
2324 }));
2325 }
2326
2327 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2328
2329 let inlay_hint_settings =
2330 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2331 let focus_handle = cx.focus_handle();
2332 if !is_minimap {
2333 cx.on_focus(&focus_handle, window, Self::handle_focus)
2334 .detach();
2335 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2336 .detach();
2337 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2338 .detach();
2339 cx.on_blur(&focus_handle, window, Self::handle_blur)
2340 .detach();
2341 cx.observe_pending_input(window, Self::observe_pending_input)
2342 .detach();
2343 }
2344
2345 let show_indent_guides =
2346 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2347 Some(false)
2348 } else {
2349 None
2350 };
2351
2352 let breakpoint_store = match (&mode, project.as_ref()) {
2353 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2354 _ => None,
2355 };
2356
2357 let mut code_action_providers = Vec::new();
2358 let mut load_uncommitted_diff = None;
2359 if let Some(project) = project.clone() {
2360 load_uncommitted_diff = Some(
2361 update_uncommitted_diff_for_buffer(
2362 cx.entity(),
2363 &project,
2364 multi_buffer.read(cx).all_buffers(),
2365 multi_buffer.clone(),
2366 cx,
2367 )
2368 .shared(),
2369 );
2370 code_action_providers.push(Rc::new(project) as Rc<_>);
2371 }
2372
2373 let mut editor = Self {
2374 focus_handle,
2375 show_cursor_when_unfocused: false,
2376 last_focused_descendant: None,
2377 buffer: multi_buffer.clone(),
2378 display_map: display_map.clone(),
2379 placeholder_display_map: None,
2380 selections,
2381 scroll_manager: ScrollManager::new(cx),
2382 columnar_selection_state: None,
2383 add_selections_state: None,
2384 select_next_state: None,
2385 select_prev_state: None,
2386 selection_history: SelectionHistory::default(),
2387 defer_selection_effects: false,
2388 deferred_selection_effects_state: None,
2389 autoclose_regions: Vec::new(),
2390 snippet_stack: InvalidationStack::default(),
2391 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2392 ime_transaction: None,
2393 active_diagnostics: ActiveDiagnostic::None,
2394 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2395 inline_diagnostics_update: Task::ready(()),
2396 inline_diagnostics: Vec::new(),
2397 soft_wrap_mode_override,
2398 diagnostics_max_severity,
2399 hard_wrap: None,
2400 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2401 semantics_provider: project
2402 .as_ref()
2403 .map(|project| Rc::new(project.downgrade()) as _),
2404 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2405 project,
2406 blink_manager: blink_manager.clone(),
2407 show_local_selections: true,
2408 show_scrollbars: ScrollbarAxes {
2409 horizontal: full_mode,
2410 vertical: full_mode,
2411 },
2412 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2413 offset_content: !matches!(mode, EditorMode::SingleLine),
2414 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2415 show_gutter: full_mode,
2416 show_line_numbers: (!full_mode).then_some(false),
2417 use_relative_line_numbers: None,
2418 disable_expand_excerpt_buttons: !full_mode,
2419 delegate_expand_excerpts: false,
2420 delegate_stage_and_restore: false,
2421 delegate_open_excerpts: false,
2422 enable_lsp_data: true,
2423 enable_runnables: true,
2424 show_git_diff_gutter: None,
2425 show_code_actions: None,
2426 show_runnables: None,
2427 show_breakpoints: None,
2428 show_diff_review_button: false,
2429 show_wrap_guides: None,
2430 show_indent_guides,
2431 buffers_with_disabled_indent_guides: HashSet::default(),
2432 highlight_order: 0,
2433 highlighted_rows: HashMap::default(),
2434 background_highlights: HashMap::default(),
2435 gutter_highlights: HashMap::default(),
2436 scrollbar_marker_state: ScrollbarMarkerState::default(),
2437 active_indent_guides_state: ActiveIndentGuidesState::default(),
2438 nav_history: None,
2439 context_menu: RefCell::new(None),
2440 context_menu_options: None,
2441 mouse_context_menu: None,
2442 completion_tasks: Vec::new(),
2443 inline_blame_popover: None,
2444 inline_blame_popover_show_task: None,
2445 signature_help_state: SignatureHelpState::default(),
2446 auto_signature_help: None,
2447 find_all_references_task_sources: Vec::new(),
2448 next_completion_id: 0,
2449 next_inlay_id: 0,
2450 code_action_providers,
2451 available_code_actions: None,
2452 code_actions_task: None,
2453 quick_selection_highlight_task: None,
2454 debounced_selection_highlight_task: None,
2455 debounced_selection_highlight_complete: false,
2456 document_highlights_task: None,
2457 linked_editing_range_task: None,
2458 pending_rename: None,
2459 searchable: !is_minimap,
2460 cursor_shape: EditorSettings::get_global(cx)
2461 .cursor_shape
2462 .unwrap_or_default(),
2463 cursor_offset_on_selection: false,
2464 current_line_highlight: None,
2465 autoindent_mode: Some(AutoindentMode::EachLine),
2466 collapse_matches: false,
2467 workspace: None,
2468 input_enabled: !is_minimap,
2469 expects_character_input: !is_minimap,
2470 use_modal_editing: full_mode,
2471 read_only: is_minimap,
2472 use_autoclose: true,
2473 use_auto_surround: true,
2474 use_selection_highlight: true,
2475 auto_replace_emoji_shortcode: false,
2476 jsx_tag_auto_close_enabled_in_any_buffer: false,
2477 leader_id: None,
2478 remote_id: None,
2479 hover_state: HoverState::default(),
2480 pending_mouse_down: None,
2481 prev_pressure_stage: None,
2482 hovered_link_state: None,
2483 edit_prediction_provider: None,
2484 active_edit_prediction: None,
2485 stale_edit_prediction_in_menu: None,
2486 edit_prediction_preview: EditPredictionPreview::Inactive {
2487 released_too_fast: false,
2488 },
2489 inline_diagnostics_enabled: full_mode,
2490 diagnostics_enabled: full_mode,
2491 word_completions_enabled: full_mode,
2492 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2493 gutter_hovered: false,
2494 pixel_position_of_newest_cursor: None,
2495 last_bounds: None,
2496 last_position_map: None,
2497 expect_bounds_change: None,
2498 gutter_dimensions: GutterDimensions::default(),
2499 style: None,
2500 show_cursor_names: false,
2501 hovered_cursors: HashMap::default(),
2502 next_editor_action_id: EditorActionId::default(),
2503 editor_actions: Rc::default(),
2504 edit_predictions_hidden_for_vim_mode: false,
2505 show_edit_predictions_override: None,
2506 show_completions_on_input_override: None,
2507 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2508 edit_prediction_settings: EditPredictionSettings::Disabled,
2509 in_leading_whitespace: false,
2510 custom_context_menu: None,
2511 show_git_blame_gutter: false,
2512 show_git_blame_inline: false,
2513 show_selection_menu: None,
2514 show_git_blame_inline_delay_task: None,
2515 git_blame_inline_enabled: full_mode
2516 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2517 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2518 buffer_serialization: is_minimap.not().then(|| {
2519 BufferSerialization::new(
2520 ProjectSettings::get_global(cx)
2521 .session
2522 .restore_unsaved_buffers,
2523 )
2524 }),
2525 blame: None,
2526 blame_subscription: None,
2527
2528 breakpoint_store,
2529 gutter_breakpoint_indicator: (None, None),
2530 gutter_diff_review_indicator: (None, None),
2531 diff_review_drag_state: None,
2532 diff_review_overlays: Vec::new(),
2533 stored_review_comments: Vec::new(),
2534 next_review_comment_id: 0,
2535 hovered_diff_hunk_row: None,
2536 _subscriptions: (!is_minimap)
2537 .then(|| {
2538 vec![
2539 cx.observe(&multi_buffer, Self::on_buffer_changed),
2540 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2541 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2542 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2543 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2544 cx.observe_global_in::<GlobalTheme>(window, Self::theme_changed),
2545 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2546 cx.observe_window_activation(window, |editor, window, cx| {
2547 let active = window.is_window_active();
2548 editor.blink_manager.update(cx, |blink_manager, cx| {
2549 if active {
2550 blink_manager.enable(cx);
2551 } else {
2552 blink_manager.disable(cx);
2553 }
2554 });
2555 if active {
2556 editor.show_mouse_cursor(cx);
2557 }
2558 }),
2559 ]
2560 })
2561 .unwrap_or_default(),
2562 runnables: RunnableData::new(),
2563 pull_diagnostics_task: Task::ready(()),
2564 colors: None,
2565 refresh_colors_task: Task::ready(()),
2566 use_document_folding_ranges: false,
2567 refresh_folding_ranges_task: Task::ready(()),
2568 inlay_hints: None,
2569 next_color_inlay_id: 0,
2570 post_scroll_update: Task::ready(()),
2571 linked_edit_ranges: Default::default(),
2572 in_project_search: false,
2573 previous_search_ranges: None,
2574 breadcrumb_header: None,
2575 focused_block: None,
2576 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2577 addons: HashMap::default(),
2578 registered_buffers: HashMap::default(),
2579 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2580 selection_mark_mode: false,
2581 toggle_fold_multiple_buffers: Task::ready(()),
2582 serialize_selections: Task::ready(()),
2583 serialize_folds: Task::ready(()),
2584 text_style_refinement: None,
2585 load_diff_task: load_uncommitted_diff,
2586 temporary_diff_override: false,
2587 mouse_cursor_hidden: false,
2588 minimap: None,
2589 hide_mouse_mode: EditorSettings::get_global(cx)
2590 .hide_mouse
2591 .unwrap_or_default(),
2592 change_list: ChangeList::new(),
2593 mode,
2594 selection_drag_state: SelectionDragState::None,
2595 folding_newlines: Task::ready(()),
2596 lookup_key: None,
2597 select_next_is_case_sensitive: None,
2598 on_local_selections_changed: None,
2599 suppress_selection_callback: false,
2600 applicable_language_settings: HashMap::default(),
2601 semantic_token_state: SemanticTokenState::new(cx, full_mode),
2602 accent_data: None,
2603 bracket_fetched_tree_sitter_chunks: HashMap::default(),
2604 number_deleted_lines: false,
2605 refresh_matching_bracket_highlights_task: Task::ready(()),
2606 refresh_document_symbols_task: Task::ready(()).shared(),
2607 lsp_document_symbols: HashMap::default(),
2608 refresh_outline_symbols_at_cursor_at_cursor_task: Task::ready(()),
2609 outline_symbols_at_cursor: None,
2610 sticky_headers_task: Task::ready(()),
2611 sticky_headers: None,
2612 colorize_brackets_task: Task::ready(()),
2613 };
2614
2615 if is_minimap {
2616 return editor;
2617 }
2618
2619 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2620 editor.accent_data = editor.fetch_accent_data(cx);
2621
2622 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2623 editor
2624 ._subscriptions
2625 .push(cx.observe(breakpoints, |_, _, cx| {
2626 cx.notify();
2627 }));
2628 }
2629 editor._subscriptions.extend(project_subscriptions);
2630
2631 editor._subscriptions.push(cx.subscribe_in(
2632 &cx.entity(),
2633 window,
2634 |editor, _, e: &EditorEvent, window, cx| match e {
2635 EditorEvent::ScrollPositionChanged { local, .. } => {
2636 if *local {
2637 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2638 editor.inline_blame_popover.take();
2639 let snapshot = editor.snapshot(window, cx);
2640 let new_anchor = editor
2641 .scroll_manager
2642 .native_anchor(&snapshot.display_snapshot, cx);
2643 editor.update_restoration_data(cx, move |data| {
2644 data.scroll_position = (
2645 new_anchor.top_row(snapshot.buffer_snapshot()),
2646 new_anchor.offset,
2647 );
2648 });
2649
2650 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2651 cx.background_executor()
2652 .timer(Duration::from_millis(50))
2653 .await;
2654 editor
2655 .update_in(cx, |editor, window, cx| {
2656 editor.update_data_on_scroll(window, cx)
2657 })
2658 .ok();
2659 });
2660 }
2661 editor.refresh_sticky_headers(&editor.snapshot(window, cx), cx);
2662 }
2663 EditorEvent::Edited { .. } => {
2664 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2665 .map(|vim_mode| vim_mode.0)
2666 .unwrap_or(false);
2667 if !vim_mode {
2668 let display_map = editor.display_snapshot(cx);
2669 let selections = editor.selections.all_adjusted_display(&display_map);
2670 let pop_state = editor
2671 .change_list
2672 .last()
2673 .map(|previous| {
2674 previous.len() == selections.len()
2675 && previous.iter().enumerate().all(|(ix, p)| {
2676 p.to_display_point(&display_map).row()
2677 == selections[ix].head().row()
2678 })
2679 })
2680 .unwrap_or(false);
2681 let new_positions = selections
2682 .into_iter()
2683 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2684 .collect();
2685 editor
2686 .change_list
2687 .push_to_change_list(pop_state, new_positions);
2688 }
2689 }
2690 _ => (),
2691 },
2692 ));
2693
2694 if let Some(dap_store) = editor
2695 .project
2696 .as_ref()
2697 .map(|project| project.read(cx).dap_store())
2698 {
2699 let weak_editor = cx.weak_entity();
2700
2701 editor
2702 ._subscriptions
2703 .push(
2704 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2705 let session_entity = cx.entity();
2706 weak_editor
2707 .update(cx, |editor, cx| {
2708 editor._subscriptions.push(
2709 cx.subscribe(&session_entity, Self::on_debug_session_event),
2710 );
2711 })
2712 .ok();
2713 }),
2714 );
2715
2716 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2717 editor
2718 ._subscriptions
2719 .push(cx.subscribe(&session, Self::on_debug_session_event));
2720 }
2721 }
2722
2723 // skip adding the initial selection to selection history
2724 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2725 editor.end_selection(window, cx);
2726 editor.selection_history.mode = SelectionHistoryMode::Normal;
2727
2728 editor.scroll_manager.show_scrollbars(window, cx);
2729 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2730
2731 if full_mode {
2732 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2733 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2734
2735 if editor.git_blame_inline_enabled {
2736 editor.start_git_blame_inline(false, window, cx);
2737 }
2738
2739 editor.go_to_active_debug_line(window, cx);
2740
2741 editor.minimap =
2742 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2743 editor.colors = Some(LspColorData::new(cx));
2744 editor.use_document_folding_ranges = true;
2745 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2746
2747 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2748 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2749 }
2750 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2751 }
2752
2753 editor
2754 }
2755
2756 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2757 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2758 }
2759
2760 pub fn deploy_mouse_context_menu(
2761 &mut self,
2762 position: gpui::Point<Pixels>,
2763 context_menu: Entity<ContextMenu>,
2764 window: &mut Window,
2765 cx: &mut Context<Self>,
2766 ) {
2767 self.mouse_context_menu = Some(MouseContextMenu::new(
2768 self,
2769 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2770 context_menu,
2771 window,
2772 cx,
2773 ));
2774 }
2775
2776 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2777 self.mouse_context_menu
2778 .as_ref()
2779 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2780 }
2781
2782 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2783 if self
2784 .selections
2785 .pending_anchor()
2786 .is_some_and(|pending_selection| {
2787 let snapshot = self.buffer().read(cx).snapshot(cx);
2788 pending_selection.range().includes(range, &snapshot)
2789 })
2790 {
2791 return true;
2792 }
2793
2794 self.selections
2795 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2796 .into_iter()
2797 .any(|selection| {
2798 // This is needed to cover a corner case, if we just check for an existing
2799 // selection in the fold range, having a cursor at the start of the fold
2800 // marks it as selected. Non-empty selections don't cause this.
2801 let length = selection.end - selection.start;
2802 length > 0
2803 })
2804 }
2805
2806 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2807 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2808 }
2809
2810 fn key_context_internal(
2811 &self,
2812 has_active_edit_prediction: bool,
2813 window: &mut Window,
2814 cx: &mut App,
2815 ) -> KeyContext {
2816 let mut key_context = KeyContext::new_with_defaults();
2817 key_context.add("Editor");
2818 let mode = match self.mode {
2819 EditorMode::SingleLine => "single_line",
2820 EditorMode::AutoHeight { .. } => "auto_height",
2821 EditorMode::Minimap { .. } => "minimap",
2822 EditorMode::Full { .. } => "full",
2823 };
2824
2825 if EditorSettings::jupyter_enabled(cx) {
2826 key_context.add("jupyter");
2827 }
2828
2829 key_context.set("mode", mode);
2830 if self.pending_rename.is_some() {
2831 key_context.add("renaming");
2832 }
2833
2834 if let Some(snippet_stack) = self.snippet_stack.last() {
2835 key_context.add("in_snippet");
2836
2837 if snippet_stack.active_index > 0 {
2838 key_context.add("has_previous_tabstop");
2839 }
2840
2841 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2842 key_context.add("has_next_tabstop");
2843 }
2844 }
2845
2846 match self.context_menu.borrow().as_ref() {
2847 Some(CodeContextMenu::Completions(menu)) => {
2848 if menu.visible() {
2849 key_context.add("menu");
2850 key_context.add("showing_completions");
2851 }
2852 }
2853 Some(CodeContextMenu::CodeActions(menu)) => {
2854 if menu.visible() {
2855 key_context.add("menu");
2856 key_context.add("showing_code_actions")
2857 }
2858 }
2859 None => {}
2860 }
2861
2862 if self.signature_help_state.has_multiple_signatures() {
2863 key_context.add("showing_signature_help");
2864 }
2865
2866 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2867 if !self.focus_handle(cx).contains_focused(window, cx)
2868 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2869 {
2870 for addon in self.addons.values() {
2871 addon.extend_key_context(&mut key_context, cx)
2872 }
2873 }
2874
2875 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2876 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2877 Some(
2878 file.full_path(cx)
2879 .extension()?
2880 .to_string_lossy()
2881 .to_lowercase(),
2882 )
2883 }) {
2884 key_context.set("extension", extension);
2885 }
2886 } else {
2887 key_context.add("multibuffer");
2888 }
2889
2890 if has_active_edit_prediction {
2891 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2892 key_context.add("copilot_suggestion");
2893 }
2894
2895 if self.in_leading_whitespace {
2896 key_context.add("in_leading_whitespace");
2897 }
2898 if self.edit_prediction_requires_modifier() {
2899 key_context.set("edit_prediction_mode", "subtle")
2900 } else {
2901 key_context.set("edit_prediction_mode", "eager");
2902 }
2903
2904 if self.selection_mark_mode {
2905 key_context.add("selection_mode");
2906 }
2907
2908 let disjoint = self.selections.disjoint_anchors();
2909 if matches!(
2910 &self.mode,
2911 EditorMode::SingleLine | EditorMode::AutoHeight { .. }
2912 ) && let [selection] = disjoint
2913 && selection.start == selection.end
2914 {
2915 let snapshot = self.snapshot(window, cx);
2916 let snapshot = snapshot.buffer_snapshot();
2917 let caret_offset = selection.end.to_offset(snapshot);
2918
2919 if caret_offset == MultiBufferOffset(0) {
2920 key_context.add("start_of_input");
2921 }
2922
2923 if caret_offset == snapshot.len() {
2924 key_context.add("end_of_input");
2925 }
2926 }
2927
2928 if self.has_any_expanded_diff_hunks(cx) {
2929 key_context.add("diffs_expanded");
2930 }
2931
2932 key_context
2933 }
2934
2935 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2936 self.last_bounds.as_ref()
2937 }
2938
2939 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2940 if self.mouse_cursor_hidden {
2941 self.mouse_cursor_hidden = false;
2942 cx.notify();
2943 }
2944 }
2945
2946 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2947 let hide_mouse_cursor = match origin {
2948 HideMouseCursorOrigin::TypingAction => {
2949 matches!(
2950 self.hide_mouse_mode,
2951 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2952 )
2953 }
2954 HideMouseCursorOrigin::MovementAction => {
2955 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2956 }
2957 };
2958 if self.mouse_cursor_hidden != hide_mouse_cursor {
2959 self.mouse_cursor_hidden = hide_mouse_cursor;
2960 cx.notify();
2961 }
2962 }
2963
2964 fn accept_edit_prediction_keystroke(
2965 &self,
2966 granularity: EditPredictionGranularity,
2967 window: &mut Window,
2968 cx: &mut App,
2969 ) -> Option<gpui::KeybindingKeystroke> {
2970 let key_context = self.key_context_internal(true, window, cx);
2971
2972 let bindings =
2973 match granularity {
2974 EditPredictionGranularity::Word => window
2975 .bindings_for_action_in_context(&AcceptNextWordEditPrediction, key_context),
2976 EditPredictionGranularity::Line => window
2977 .bindings_for_action_in_context(&AcceptNextLineEditPrediction, key_context),
2978 EditPredictionGranularity::Full => {
2979 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2980 }
2981 };
2982
2983 bindings
2984 .into_iter()
2985 .rev()
2986 .find_map(|binding| match binding.keystrokes() {
2987 [keystroke, ..] => Some(keystroke.clone()),
2988 _ => None,
2989 })
2990 }
2991
2992 fn preview_edit_prediction_keystroke(
2993 &self,
2994 window: &mut Window,
2995 cx: &mut App,
2996 ) -> Option<gpui::KeybindingKeystroke> {
2997 let key_context = self.key_context_internal(true, window, cx);
2998 let bindings = window.bindings_for_action_in_context(&AcceptEditPrediction, key_context);
2999 bindings
3000 .into_iter()
3001 .rev()
3002 .find_map(|binding| match binding.keystrokes() {
3003 [keystroke, ..] if keystroke.modifiers().modified() => Some(keystroke.clone()),
3004 _ => None,
3005 })
3006 }
3007
3008 fn edit_prediction_preview_modifiers_held(
3009 &self,
3010 modifiers: &Modifiers,
3011 window: &mut Window,
3012 cx: &mut App,
3013 ) -> bool {
3014 let key_context = self.key_context_internal(true, window, cx);
3015 let actions: [&dyn Action; 3] = [
3016 &AcceptEditPrediction,
3017 &AcceptNextWordEditPrediction,
3018 &AcceptNextLineEditPrediction,
3019 ];
3020
3021 actions.into_iter().any(|action| {
3022 window
3023 .bindings_for_action_in_context(action, key_context.clone())
3024 .into_iter()
3025 .rev()
3026 .any(|binding| {
3027 binding.keystrokes().first().is_some_and(|keystroke| {
3028 keystroke.modifiers().modified() && keystroke.modifiers() == modifiers
3029 })
3030 })
3031 })
3032 }
3033
3034 fn edit_prediction_cursor_popover_prefers_preview(
3035 &self,
3036 completion: &EditPredictionState,
3037 cx: &App,
3038 ) -> bool {
3039 let multibuffer_snapshot = self.buffer.read(cx).snapshot(cx);
3040
3041 match &completion.completion {
3042 EditPrediction::Edit {
3043 edits, snapshot, ..
3044 } => {
3045 let mut start_row: Option<u32> = None;
3046 let mut end_row: Option<u32> = None;
3047
3048 for (range, text) in edits {
3049 let Some((_, range)) =
3050 multibuffer_snapshot.anchor_range_to_buffer_anchor_range(range.clone())
3051 else {
3052 continue;
3053 };
3054 let edit_start_row = range.start.to_point(snapshot).row;
3055 let old_end_row = range.end.to_point(snapshot).row;
3056 let inserted_newline_count = text
3057 .as_ref()
3058 .chars()
3059 .filter(|character| *character == '\n')
3060 .count() as u32;
3061 let deleted_newline_count = old_end_row - edit_start_row;
3062 let preview_end_row = edit_start_row + inserted_newline_count;
3063
3064 start_row =
3065 Some(start_row.map_or(edit_start_row, |row| row.min(edit_start_row)));
3066 end_row = Some(end_row.map_or(preview_end_row, |row| row.max(preview_end_row)));
3067
3068 if deleted_newline_count > 1 {
3069 end_row = Some(end_row.map_or(old_end_row, |row| row.max(old_end_row)));
3070 }
3071 }
3072
3073 start_row
3074 .zip(end_row)
3075 .is_some_and(|(start_row, end_row)| end_row > start_row)
3076 }
3077 EditPrediction::MoveWithin { .. } | EditPrediction::MoveOutside { .. } => false,
3078 }
3079 }
3080
3081 fn edit_prediction_keybind_display(
3082 &self,
3083 surface: EditPredictionKeybindSurface,
3084 window: &mut Window,
3085 cx: &mut App,
3086 ) -> EditPredictionKeybindDisplay {
3087 let accept_keystroke =
3088 self.accept_edit_prediction_keystroke(EditPredictionGranularity::Full, window, cx);
3089 let preview_keystroke = self.preview_edit_prediction_keystroke(window, cx);
3090
3091 let action = match surface {
3092 EditPredictionKeybindSurface::Inline
3093 | EditPredictionKeybindSurface::CursorPopoverCompact => {
3094 if self.edit_prediction_requires_modifier() {
3095 EditPredictionKeybindAction::Preview
3096 } else {
3097 EditPredictionKeybindAction::Accept
3098 }
3099 }
3100 EditPredictionKeybindSurface::CursorPopoverExpanded => self
3101 .active_edit_prediction
3102 .as_ref()
3103 .filter(|completion| {
3104 self.edit_prediction_cursor_popover_prefers_preview(completion, cx)
3105 })
3106 .map_or(EditPredictionKeybindAction::Accept, |_| {
3107 EditPredictionKeybindAction::Preview
3108 }),
3109 };
3110 #[cfg(test)]
3111 let preview_copy = preview_keystroke.clone();
3112 #[cfg(test)]
3113 let accept_copy = accept_keystroke.clone();
3114
3115 let displayed_keystroke = match surface {
3116 EditPredictionKeybindSurface::Inline => match action {
3117 EditPredictionKeybindAction::Accept => accept_keystroke,
3118 EditPredictionKeybindAction::Preview => preview_keystroke,
3119 },
3120 EditPredictionKeybindSurface::CursorPopoverCompact
3121 | EditPredictionKeybindSurface::CursorPopoverExpanded => match action {
3122 EditPredictionKeybindAction::Accept => accept_keystroke,
3123 EditPredictionKeybindAction::Preview => {
3124 preview_keystroke.or_else(|| accept_keystroke.clone())
3125 }
3126 },
3127 };
3128
3129 let missing_accept_keystroke = displayed_keystroke.is_none();
3130
3131 EditPredictionKeybindDisplay {
3132 #[cfg(test)]
3133 accept_keystroke: accept_copy,
3134 #[cfg(test)]
3135 preview_keystroke: preview_copy,
3136 displayed_keystroke,
3137 action,
3138 missing_accept_keystroke,
3139 show_hold_label: matches!(surface, EditPredictionKeybindSurface::CursorPopoverCompact)
3140 && self.edit_prediction_preview.released_too_fast(),
3141 }
3142 }
3143
3144 pub fn new_file(
3145 workspace: &mut Workspace,
3146 _: &workspace::NewFile,
3147 window: &mut Window,
3148 cx: &mut Context<Workspace>,
3149 ) {
3150 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
3151 "Failed to create buffer",
3152 window,
3153 cx,
3154 |e, _, _| match e.error_code() {
3155 ErrorCode::RemoteUpgradeRequired => Some(format!(
3156 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
3157 e.error_tag("required").unwrap_or("the latest version")
3158 )),
3159 _ => None,
3160 },
3161 );
3162 }
3163
3164 pub fn new_in_workspace(
3165 workspace: &mut Workspace,
3166 window: &mut Window,
3167 cx: &mut Context<Workspace>,
3168 ) -> Task<Result<Entity<Editor>>> {
3169 let project = workspace.project().clone();
3170 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
3171
3172 cx.spawn_in(window, async move |workspace, cx| {
3173 let buffer = create.await?;
3174 workspace.update_in(cx, |workspace, window, cx| {
3175 let editor =
3176 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
3177 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
3178 editor
3179 })
3180 })
3181 }
3182
3183 fn new_file_vertical(
3184 workspace: &mut Workspace,
3185 _: &workspace::NewFileSplitVertical,
3186 window: &mut Window,
3187 cx: &mut Context<Workspace>,
3188 ) {
3189 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
3190 }
3191
3192 fn new_file_horizontal(
3193 workspace: &mut Workspace,
3194 _: &workspace::NewFileSplitHorizontal,
3195 window: &mut Window,
3196 cx: &mut Context<Workspace>,
3197 ) {
3198 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
3199 }
3200
3201 fn new_file_split(
3202 workspace: &mut Workspace,
3203 action: &workspace::NewFileSplit,
3204 window: &mut Window,
3205 cx: &mut Context<Workspace>,
3206 ) {
3207 Self::new_file_in_direction(workspace, action.0, window, cx)
3208 }
3209
3210 fn new_file_in_direction(
3211 workspace: &mut Workspace,
3212 direction: SplitDirection,
3213 window: &mut Window,
3214 cx: &mut Context<Workspace>,
3215 ) {
3216 let project = workspace.project().clone();
3217 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
3218
3219 cx.spawn_in(window, async move |workspace, cx| {
3220 let buffer = create.await?;
3221 workspace.update_in(cx, move |workspace, window, cx| {
3222 workspace.split_item(
3223 direction,
3224 Box::new(
3225 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
3226 ),
3227 window,
3228 cx,
3229 )
3230 })?;
3231 anyhow::Ok(())
3232 })
3233 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
3234 match e.error_code() {
3235 ErrorCode::RemoteUpgradeRequired => Some(format!(
3236 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
3237 e.error_tag("required").unwrap_or("the latest version")
3238 )),
3239 _ => None,
3240 }
3241 });
3242 }
3243
3244 pub fn leader_id(&self) -> Option<CollaboratorId> {
3245 self.leader_id
3246 }
3247
3248 pub fn buffer(&self) -> &Entity<MultiBuffer> {
3249 &self.buffer
3250 }
3251
3252 pub fn project(&self) -> Option<&Entity<Project>> {
3253 self.project.as_ref()
3254 }
3255
3256 pub fn workspace(&self) -> Option<Entity<Workspace>> {
3257 self.workspace.as_ref()?.0.upgrade()
3258 }
3259
3260 /// Detaches a task and shows an error notification in the workspace if available,
3261 /// otherwise just logs the error.
3262 pub fn detach_and_notify_err<R, E>(
3263 &self,
3264 task: Task<Result<R, E>>,
3265 window: &mut Window,
3266 cx: &mut App,
3267 ) where
3268 E: std::fmt::Debug + std::fmt::Display + 'static,
3269 R: 'static,
3270 {
3271 if let Some(workspace) = self.workspace() {
3272 task.detach_and_notify_err(workspace.downgrade(), window, cx);
3273 } else {
3274 task.detach_and_log_err(cx);
3275 }
3276 }
3277
3278 /// Returns the workspace serialization ID if this editor should be serialized.
3279 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
3280 self.workspace
3281 .as_ref()
3282 .filter(|_| self.should_serialize_buffer())
3283 .and_then(|workspace| workspace.1)
3284 }
3285
3286 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
3287 self.buffer().read(cx).title(cx)
3288 }
3289
3290 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
3291 let git_blame_gutter_max_author_length = self
3292 .render_git_blame_gutter(cx)
3293 .then(|| {
3294 if let Some(blame) = self.blame.as_ref() {
3295 let max_author_length =
3296 blame.update(cx, |blame, cx| blame.max_author_length(cx));
3297 Some(max_author_length)
3298 } else {
3299 None
3300 }
3301 })
3302 .flatten();
3303
3304 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3305
3306 EditorSnapshot {
3307 mode: self.mode.clone(),
3308 show_gutter: self.show_gutter,
3309 offset_content: self.offset_content,
3310 show_line_numbers: self.show_line_numbers,
3311 number_deleted_lines: self.number_deleted_lines,
3312 show_git_diff_gutter: self.show_git_diff_gutter,
3313 semantic_tokens_enabled: self.semantic_token_state.enabled(),
3314 show_code_actions: self.show_code_actions,
3315 show_runnables: self.show_runnables,
3316 show_breakpoints: self.show_breakpoints,
3317 git_blame_gutter_max_author_length,
3318 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
3319 display_snapshot,
3320 placeholder_display_snapshot: self
3321 .placeholder_display_map
3322 .as_ref()
3323 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
3324 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
3325 is_focused: self.focus_handle.is_focused(window),
3326 current_line_highlight: self
3327 .current_line_highlight
3328 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
3329 gutter_hovered: self.gutter_hovered,
3330 }
3331 }
3332
3333 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
3334 self.buffer.read(cx).language_at(point, cx)
3335 }
3336
3337 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
3338 self.buffer.read(cx).read(cx).file_at(point).cloned()
3339 }
3340
3341 pub fn active_buffer(&self, cx: &App) -> Option<Entity<Buffer>> {
3342 let multibuffer = self.buffer.read(cx);
3343 let snapshot = multibuffer.snapshot(cx);
3344 let (anchor, _) =
3345 snapshot.anchor_to_buffer_anchor(self.selections.newest_anchor().head())?;
3346 multibuffer.buffer(anchor.buffer_id)
3347 }
3348
3349 pub fn mode(&self) -> &EditorMode {
3350 &self.mode
3351 }
3352
3353 pub fn set_mode(&mut self, mode: EditorMode) {
3354 self.mode = mode;
3355 }
3356
3357 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
3358 self.collaboration_hub.as_deref()
3359 }
3360
3361 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
3362 self.collaboration_hub = Some(hub);
3363 }
3364
3365 pub fn set_in_project_search(&mut self, in_project_search: bool) {
3366 self.in_project_search = in_project_search;
3367 }
3368
3369 pub fn set_custom_context_menu(
3370 &mut self,
3371 f: impl 'static
3372 + Fn(
3373 &mut Self,
3374 DisplayPoint,
3375 &mut Window,
3376 &mut Context<Self>,
3377 ) -> Option<Entity<ui::ContextMenu>>,
3378 ) {
3379 self.custom_context_menu = Some(Box::new(f))
3380 }
3381
3382 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
3383 self.completion_provider = provider;
3384 }
3385
3386 #[cfg(any(test, feature = "test-support"))]
3387 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
3388 self.completion_provider.clone()
3389 }
3390
3391 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
3392 self.semantics_provider.clone()
3393 }
3394
3395 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
3396 self.semantics_provider = provider;
3397 }
3398
3399 pub fn set_edit_prediction_provider<T>(
3400 &mut self,
3401 provider: Option<Entity<T>>,
3402 window: &mut Window,
3403 cx: &mut Context<Self>,
3404 ) where
3405 T: EditPredictionDelegate,
3406 {
3407 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionDelegate {
3408 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
3409 if this.focus_handle.is_focused(window) {
3410 this.update_visible_edit_prediction(window, cx);
3411 }
3412 }),
3413 provider: Arc::new(provider),
3414 });
3415 self.update_edit_prediction_settings(cx);
3416 self.refresh_edit_prediction(false, false, window, cx);
3417 }
3418
3419 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
3420 self.placeholder_display_map
3421 .as_ref()
3422 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
3423 }
3424
3425 pub fn set_placeholder_text(
3426 &mut self,
3427 placeholder_text: &str,
3428 window: &mut Window,
3429 cx: &mut Context<Self>,
3430 ) {
3431 let multibuffer = cx
3432 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3433
3434 let style = window.text_style();
3435
3436 self.placeholder_display_map = Some(cx.new(|cx| {
3437 DisplayMap::new(
3438 multibuffer,
3439 style.font(),
3440 style.font_size.to_pixels(window.rem_size()),
3441 None,
3442 FILE_HEADER_HEIGHT,
3443 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3444 Default::default(),
3445 DiagnosticSeverity::Off,
3446 cx,
3447 )
3448 }));
3449 cx.notify();
3450 }
3451
3452 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3453 self.cursor_shape = cursor_shape;
3454
3455 // Disrupt blink for immediate user feedback that the cursor shape has changed
3456 self.blink_manager.update(cx, BlinkManager::show_cursor);
3457
3458 cx.notify();
3459 }
3460
3461 pub fn cursor_shape(&self) -> CursorShape {
3462 self.cursor_shape
3463 }
3464
3465 pub fn set_cursor_offset_on_selection(&mut self, set_cursor_offset_on_selection: bool) {
3466 self.cursor_offset_on_selection = set_cursor_offset_on_selection;
3467 }
3468
3469 pub fn set_current_line_highlight(
3470 &mut self,
3471 current_line_highlight: Option<CurrentLineHighlight>,
3472 ) {
3473 self.current_line_highlight = current_line_highlight;
3474 }
3475
3476 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3477 self.collapse_matches = collapse_matches;
3478 }
3479
3480 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3481 if self.collapse_matches {
3482 return range.start..range.start;
3483 }
3484 range.clone()
3485 }
3486
3487 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3488 self.display_map.read(cx).clip_at_line_ends
3489 }
3490
3491 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3492 if self.display_map.read(cx).clip_at_line_ends != clip {
3493 self.display_map
3494 .update(cx, |map, _| map.clip_at_line_ends = clip);
3495 }
3496 }
3497
3498 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3499 self.input_enabled = input_enabled;
3500 }
3501
3502 pub fn set_expects_character_input(&mut self, expects_character_input: bool) {
3503 self.expects_character_input = expects_character_input;
3504 }
3505
3506 pub fn set_edit_predictions_hidden_for_vim_mode(
3507 &mut self,
3508 hidden: bool,
3509 window: &mut Window,
3510 cx: &mut Context<Self>,
3511 ) {
3512 if hidden != self.edit_predictions_hidden_for_vim_mode {
3513 self.edit_predictions_hidden_for_vim_mode = hidden;
3514 if hidden {
3515 self.update_visible_edit_prediction(window, cx);
3516 } else {
3517 self.refresh_edit_prediction(true, false, window, cx);
3518 }
3519 }
3520 }
3521
3522 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3523 self.menu_edit_predictions_policy = value;
3524 }
3525
3526 pub fn set_autoindent(&mut self, autoindent: bool) {
3527 if autoindent {
3528 self.autoindent_mode = Some(AutoindentMode::EachLine);
3529 } else {
3530 self.autoindent_mode = None;
3531 }
3532 }
3533
3534 pub fn capability(&self, cx: &App) -> Capability {
3535 if self.read_only {
3536 Capability::ReadOnly
3537 } else {
3538 self.buffer.read(cx).capability()
3539 }
3540 }
3541
3542 pub fn read_only(&self, cx: &App) -> bool {
3543 self.read_only || self.buffer.read(cx).read_only()
3544 }
3545
3546 pub fn set_read_only(&mut self, read_only: bool) {
3547 self.read_only = read_only;
3548 }
3549
3550 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3551 self.use_autoclose = autoclose;
3552 }
3553
3554 pub fn set_use_selection_highlight(&mut self, highlight: bool) {
3555 self.use_selection_highlight = highlight;
3556 }
3557
3558 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3559 self.use_auto_surround = auto_surround;
3560 }
3561
3562 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3563 self.auto_replace_emoji_shortcode = auto_replace;
3564 }
3565
3566 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3567 self.buffer_serialization = should_serialize.then(|| {
3568 BufferSerialization::new(
3569 ProjectSettings::get_global(cx)
3570 .session
3571 .restore_unsaved_buffers,
3572 )
3573 })
3574 }
3575
3576 fn should_serialize_buffer(&self) -> bool {
3577 self.buffer_serialization.is_some()
3578 }
3579
3580 pub fn toggle_edit_predictions(
3581 &mut self,
3582 _: &ToggleEditPrediction,
3583 window: &mut Window,
3584 cx: &mut Context<Self>,
3585 ) {
3586 if self.show_edit_predictions_override.is_some() {
3587 self.set_show_edit_predictions(None, window, cx);
3588 } else {
3589 let show_edit_predictions = !self.edit_predictions_enabled();
3590 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3591 }
3592 }
3593
3594 pub fn set_show_completions_on_input(&mut self, show_completions_on_input: Option<bool>) {
3595 self.show_completions_on_input_override = show_completions_on_input;
3596 }
3597
3598 pub fn set_show_edit_predictions(
3599 &mut self,
3600 show_edit_predictions: Option<bool>,
3601 window: &mut Window,
3602 cx: &mut Context<Self>,
3603 ) {
3604 self.show_edit_predictions_override = show_edit_predictions;
3605 self.update_edit_prediction_settings(cx);
3606
3607 if let Some(false) = show_edit_predictions {
3608 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
3609 } else {
3610 self.refresh_edit_prediction(false, true, window, cx);
3611 }
3612 }
3613
3614 fn edit_predictions_disabled_in_scope(
3615 &self,
3616 buffer: &Entity<Buffer>,
3617 buffer_position: language::Anchor,
3618 cx: &App,
3619 ) -> bool {
3620 let snapshot = buffer.read(cx).snapshot();
3621 let settings = snapshot.settings_at(buffer_position, cx);
3622
3623 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3624 return false;
3625 };
3626
3627 scope.override_name().is_some_and(|scope_name| {
3628 settings
3629 .edit_predictions_disabled_in
3630 .iter()
3631 .any(|s| s == scope_name)
3632 })
3633 }
3634
3635 pub fn set_use_modal_editing(&mut self, to: bool) {
3636 self.use_modal_editing = to;
3637 }
3638
3639 pub fn use_modal_editing(&self) -> bool {
3640 self.use_modal_editing
3641 }
3642
3643 fn selections_did_change(
3644 &mut self,
3645 local: bool,
3646 old_cursor_position: &Anchor,
3647 effects: SelectionEffects,
3648 window: &mut Window,
3649 cx: &mut Context<Self>,
3650 ) {
3651 window.invalidate_character_coordinates();
3652
3653 // Copy selections to primary selection buffer
3654 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3655 if local {
3656 let selections = self
3657 .selections
3658 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3659 let buffer_handle = self.buffer.read(cx).read(cx);
3660
3661 let mut text = String::new();
3662 for (index, selection) in selections.iter().enumerate() {
3663 let text_for_selection = buffer_handle
3664 .text_for_range(selection.start..selection.end)
3665 .collect::<String>();
3666
3667 text.push_str(&text_for_selection);
3668 if index != selections.len() - 1 {
3669 text.push('\n');
3670 }
3671 }
3672
3673 if !text.is_empty() {
3674 cx.write_to_primary(ClipboardItem::new_string(text));
3675 }
3676 }
3677
3678 let selection_anchors = self.selections.disjoint_anchors_arc();
3679
3680 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3681 self.buffer.update(cx, |buffer, cx| {
3682 buffer.set_active_selections(
3683 &selection_anchors,
3684 self.selections.line_mode(),
3685 self.cursor_shape,
3686 cx,
3687 )
3688 });
3689 }
3690 let display_map = self
3691 .display_map
3692 .update(cx, |display_map, cx| display_map.snapshot(cx));
3693 let buffer = display_map.buffer_snapshot();
3694 if self.selections.count() == 1 {
3695 self.add_selections_state = None;
3696 }
3697 self.select_next_state = None;
3698 self.select_prev_state = None;
3699 self.select_syntax_node_history.try_clear();
3700 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3701 self.snippet_stack.invalidate(&selection_anchors, buffer);
3702 self.take_rename(false, window, cx);
3703
3704 let newest_selection = self.selections.newest_anchor();
3705 let new_cursor_position = newest_selection.head();
3706 let selection_start = newest_selection.start;
3707
3708 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3709 self.push_to_nav_history(
3710 *old_cursor_position,
3711 Some(new_cursor_position.to_point(buffer)),
3712 false,
3713 effects.nav_history == Some(true),
3714 cx,
3715 );
3716 }
3717
3718 if local {
3719 if let Some((anchor, _)) = buffer.anchor_to_buffer_anchor(new_cursor_position) {
3720 self.register_buffer(anchor.buffer_id, cx);
3721 }
3722
3723 let mut context_menu = self.context_menu.borrow_mut();
3724 let completion_menu = match context_menu.as_ref() {
3725 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3726 Some(CodeContextMenu::CodeActions(_)) => {
3727 *context_menu = None;
3728 None
3729 }
3730 None => None,
3731 };
3732 let completion_position = completion_menu.map(|menu| menu.initial_position);
3733 drop(context_menu);
3734
3735 if effects.completions
3736 && let Some(completion_position) = completion_position
3737 {
3738 let start_offset = selection_start.to_offset(buffer);
3739 let position_matches = start_offset == completion_position.to_offset(buffer);
3740 let continue_showing = if let Some((snap, ..)) =
3741 buffer.point_to_buffer_offset(completion_position)
3742 && !snap.capability.editable()
3743 {
3744 false
3745 } else if position_matches {
3746 if self.snippet_stack.is_empty() {
3747 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3748 == Some(CharKind::Word)
3749 } else {
3750 // Snippet choices can be shown even when the cursor is in whitespace.
3751 // Dismissing the menu with actions like backspace is handled by
3752 // invalidation regions.
3753 true
3754 }
3755 } else {
3756 false
3757 };
3758
3759 if continue_showing {
3760 self.open_or_update_completions_menu(None, None, false, window, cx);
3761 } else {
3762 self.hide_context_menu(window, cx);
3763 }
3764 }
3765
3766 hide_hover(self, cx);
3767
3768 if old_cursor_position.to_display_point(&display_map).row()
3769 != new_cursor_position.to_display_point(&display_map).row()
3770 {
3771 self.available_code_actions.take();
3772 }
3773 self.refresh_code_actions(window, cx);
3774 self.refresh_document_highlights(cx);
3775 refresh_linked_ranges(self, window, cx);
3776
3777 self.refresh_selected_text_highlights(&display_map, false, window, cx);
3778 self.refresh_matching_bracket_highlights(&display_map, cx);
3779 self.refresh_outline_symbols_at_cursor(cx);
3780 self.update_visible_edit_prediction(window, cx);
3781 self.inline_blame_popover.take();
3782 if self.git_blame_inline_enabled {
3783 self.start_inline_blame_timer(window, cx);
3784 }
3785 }
3786
3787 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3788
3789 if local && !self.suppress_selection_callback {
3790 if let Some(callback) = self.on_local_selections_changed.as_ref() {
3791 let cursor_position = self.selections.newest::<Point>(&display_map).head();
3792 callback(cursor_position, window, cx);
3793 }
3794 }
3795
3796 cx.emit(EditorEvent::SelectionsChanged { local });
3797
3798 let selections = &self.selections.disjoint_anchors_arc();
3799 if selections.len() == 1 {
3800 cx.emit(SearchEvent::ActiveMatchChanged)
3801 }
3802 if local && let Some(buffer_snapshot) = buffer.as_singleton() {
3803 let inmemory_selections = selections
3804 .iter()
3805 .map(|s| {
3806 let start = s.range().start.text_anchor_in(buffer_snapshot);
3807 let end = s.range().end.text_anchor_in(buffer_snapshot);
3808 (start..end).to_point(buffer_snapshot)
3809 })
3810 .collect();
3811 self.update_restoration_data(cx, |data| {
3812 data.selections = inmemory_selections;
3813 });
3814
3815 if WorkspaceSettings::get(None, cx).restore_on_startup
3816 != RestoreOnStartupBehavior::EmptyTab
3817 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3818 {
3819 let snapshot = self.buffer().read(cx).snapshot(cx);
3820 let selections = selections.clone();
3821 let background_executor = cx.background_executor().clone();
3822 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3823 let db = EditorDb::global(cx);
3824 self.serialize_selections = cx.background_spawn(async move {
3825 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3826 let db_selections = selections
3827 .iter()
3828 .map(|selection| {
3829 (
3830 selection.start.to_offset(&snapshot).0,
3831 selection.end.to_offset(&snapshot).0,
3832 )
3833 })
3834 .collect();
3835
3836 db.save_editor_selections(editor_id, workspace_id, db_selections)
3837 .await
3838 .with_context(|| {
3839 format!(
3840 "persisting editor selections for editor {editor_id}, \
3841 workspace {workspace_id:?}"
3842 )
3843 })
3844 .log_err();
3845 });
3846 }
3847 }
3848
3849 cx.notify();
3850 }
3851
3852 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3853 use text::ToOffset as _;
3854
3855 if self.mode.is_minimap()
3856 || WorkspaceSettings::get(None, cx).restore_on_startup
3857 == RestoreOnStartupBehavior::EmptyTab
3858 {
3859 return;
3860 }
3861
3862 let display_snapshot = self
3863 .display_map
3864 .update(cx, |display_map, cx| display_map.snapshot(cx));
3865 let Some(buffer_snapshot) = display_snapshot.buffer_snapshot().as_singleton() else {
3866 return;
3867 };
3868 let inmemory_folds = display_snapshot
3869 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3870 .map(|fold| {
3871 let start = fold.range.start.text_anchor_in(buffer_snapshot);
3872 let end = fold.range.end.text_anchor_in(buffer_snapshot);
3873 (start..end).to_point(buffer_snapshot)
3874 })
3875 .collect();
3876 self.update_restoration_data(cx, |data| {
3877 data.folds = inmemory_folds;
3878 });
3879
3880 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3881 return;
3882 };
3883
3884 // Get file path for path-based fold storage (survives tab close)
3885 let Some(file_path) = self.buffer().read(cx).as_singleton().and_then(|buffer| {
3886 project::File::from_dyn(buffer.read(cx).file())
3887 .map(|file| Arc::<Path>::from(file.abs_path(cx)))
3888 }) else {
3889 return;
3890 };
3891
3892 let background_executor = cx.background_executor().clone();
3893 const FINGERPRINT_LEN: usize = 32;
3894 let db_folds = display_snapshot
3895 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3896 .map(|fold| {
3897 let start = fold
3898 .range
3899 .start
3900 .text_anchor_in(buffer_snapshot)
3901 .to_offset(buffer_snapshot);
3902 let end = fold
3903 .range
3904 .end
3905 .text_anchor_in(buffer_snapshot)
3906 .to_offset(buffer_snapshot);
3907
3908 // Extract fingerprints - content at fold boundaries for validation on restore
3909 // Both fingerprints must be INSIDE the fold to avoid capturing surrounding
3910 // content that might change independently.
3911 // start_fp: first min(32, fold_len) bytes of fold content
3912 // end_fp: last min(32, fold_len) bytes of fold content
3913 // Clip to character boundaries to handle multibyte UTF-8 characters.
3914 let fold_len = end - start;
3915 let start_fp_end = buffer_snapshot
3916 .clip_offset(start + std::cmp::min(FINGERPRINT_LEN, fold_len), Bias::Left);
3917 let start_fp: String = buffer_snapshot
3918 .text_for_range(start..start_fp_end)
3919 .collect();
3920 let end_fp_start = buffer_snapshot
3921 .clip_offset(end.saturating_sub(FINGERPRINT_LEN).max(start), Bias::Right);
3922 let end_fp: String = buffer_snapshot.text_for_range(end_fp_start..end).collect();
3923
3924 (start, end, start_fp, end_fp)
3925 })
3926 .collect::<Vec<_>>();
3927 let db = EditorDb::global(cx);
3928 self.serialize_folds = cx.background_spawn(async move {
3929 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3930 if db_folds.is_empty() {
3931 // No folds - delete any persisted folds for this file
3932 db.delete_file_folds(workspace_id, file_path)
3933 .await
3934 .with_context(|| format!("deleting file folds for workspace {workspace_id:?}"))
3935 .log_err();
3936 } else {
3937 db.save_file_folds(workspace_id, file_path, db_folds)
3938 .await
3939 .with_context(|| {
3940 format!("persisting file folds for workspace {workspace_id:?}")
3941 })
3942 .log_err();
3943 }
3944 });
3945 }
3946
3947 pub fn sync_selections(
3948 &mut self,
3949 other: Entity<Editor>,
3950 cx: &mut Context<Self>,
3951 ) -> gpui::Subscription {
3952 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3953 if !other_selections.is_empty() {
3954 self.selections
3955 .change_with(&self.display_snapshot(cx), |selections| {
3956 selections.select_anchors(other_selections);
3957 });
3958 }
3959
3960 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3961 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3962 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3963 if other_selections.is_empty() {
3964 return;
3965 }
3966 let snapshot = this.display_snapshot(cx);
3967 this.selections.change_with(&snapshot, |selections| {
3968 selections.select_anchors(other_selections);
3969 });
3970 }
3971 });
3972
3973 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3974 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3975 let these_selections = this.selections.disjoint_anchors().to_vec();
3976 if these_selections.is_empty() {
3977 return;
3978 }
3979 other.update(cx, |other_editor, cx| {
3980 let snapshot = other_editor.display_snapshot(cx);
3981 other_editor
3982 .selections
3983 .change_with(&snapshot, |selections| {
3984 selections.select_anchors(these_selections);
3985 })
3986 });
3987 }
3988 });
3989
3990 Subscription::join(other_subscription, this_subscription)
3991 }
3992
3993 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3994 if self.buffer().read(cx).is_singleton() {
3995 return;
3996 }
3997 let snapshot = self.buffer.read(cx).snapshot(cx);
3998 let buffer_ids: HashSet<BufferId> = self
3999 .selections
4000 .disjoint_anchor_ranges()
4001 .flat_map(|range| snapshot.buffer_ids_for_range(range))
4002 .collect();
4003 for buffer_id in buffer_ids {
4004 self.unfold_buffer(buffer_id, cx);
4005 }
4006 }
4007
4008 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
4009 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
4010 /// effects of selection change occur at the end of the transaction.
4011 pub fn change_selections<R>(
4012 &mut self,
4013 effects: SelectionEffects,
4014 window: &mut Window,
4015 cx: &mut Context<Self>,
4016 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
4017 ) -> R {
4018 let snapshot = self.display_snapshot(cx);
4019 if let Some(state) = &mut self.deferred_selection_effects_state {
4020 state.effects.scroll = effects.scroll.or(state.effects.scroll);
4021 state.effects.completions = effects.completions;
4022 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
4023 let (changed, result) = self.selections.change_with(&snapshot, change);
4024 state.changed |= changed;
4025 return result;
4026 }
4027 let mut state = DeferredSelectionEffectsState {
4028 changed: false,
4029 effects,
4030 old_cursor_position: self.selections.newest_anchor().head(),
4031 history_entry: SelectionHistoryEntry {
4032 selections: self.selections.disjoint_anchors_arc(),
4033 select_next_state: self.select_next_state.clone(),
4034 select_prev_state: self.select_prev_state.clone(),
4035 add_selections_state: self.add_selections_state.clone(),
4036 },
4037 };
4038 let (changed, result) = self.selections.change_with(&snapshot, change);
4039 state.changed = state.changed || changed;
4040 if self.defer_selection_effects {
4041 self.deferred_selection_effects_state = Some(state);
4042 } else {
4043 self.apply_selection_effects(state, window, cx);
4044 }
4045 result
4046 }
4047
4048 /// Defers the effects of selection change, so that the effects of multiple calls to
4049 /// `change_selections` are applied at the end. This way these intermediate states aren't added
4050 /// to selection history and the state of popovers based on selection position aren't
4051 /// erroneously updated.
4052 pub fn with_selection_effects_deferred<R>(
4053 &mut self,
4054 window: &mut Window,
4055 cx: &mut Context<Self>,
4056 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
4057 ) -> R {
4058 let already_deferred = self.defer_selection_effects;
4059 self.defer_selection_effects = true;
4060 let result = update(self, window, cx);
4061 if !already_deferred {
4062 self.defer_selection_effects = false;
4063 if let Some(state) = self.deferred_selection_effects_state.take() {
4064 self.apply_selection_effects(state, window, cx);
4065 }
4066 }
4067 result
4068 }
4069
4070 fn apply_selection_effects(
4071 &mut self,
4072 state: DeferredSelectionEffectsState,
4073 window: &mut Window,
4074 cx: &mut Context<Self>,
4075 ) {
4076 if state.changed {
4077 self.selection_history.push(state.history_entry);
4078
4079 if let Some(autoscroll) = state.effects.scroll {
4080 self.request_autoscroll(autoscroll, cx);
4081 }
4082
4083 let old_cursor_position = &state.old_cursor_position;
4084
4085 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
4086
4087 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
4088 self.show_signature_help_auto(window, cx);
4089 }
4090 }
4091 }
4092
4093 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
4094 where
4095 I: IntoIterator<Item = (Range<S>, T)>,
4096 S: ToOffset,
4097 T: Into<Arc<str>>,
4098 {
4099 if self.read_only(cx) {
4100 return;
4101 }
4102
4103 self.buffer
4104 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
4105 }
4106
4107 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
4108 where
4109 I: IntoIterator<Item = (Range<S>, T)>,
4110 S: ToOffset,
4111 T: Into<Arc<str>>,
4112 {
4113 if self.read_only(cx) {
4114 return;
4115 }
4116
4117 self.buffer.update(cx, |buffer, cx| {
4118 buffer.edit(edits, self.autoindent_mode.clone(), cx)
4119 });
4120 }
4121
4122 pub fn edit_with_block_indent<I, S, T>(
4123 &mut self,
4124 edits: I,
4125 original_indent_columns: Vec<Option<u32>>,
4126 cx: &mut Context<Self>,
4127 ) where
4128 I: IntoIterator<Item = (Range<S>, T)>,
4129 S: ToOffset,
4130 T: Into<Arc<str>>,
4131 {
4132 if self.read_only(cx) {
4133 return;
4134 }
4135
4136 self.buffer.update(cx, |buffer, cx| {
4137 buffer.edit(
4138 edits,
4139 Some(AutoindentMode::Block {
4140 original_indent_columns,
4141 }),
4142 cx,
4143 )
4144 });
4145 }
4146
4147 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
4148 self.hide_context_menu(window, cx);
4149
4150 match phase {
4151 SelectPhase::Begin {
4152 position,
4153 add,
4154 click_count,
4155 } => self.begin_selection(position, add, click_count, window, cx),
4156 SelectPhase::BeginColumnar {
4157 position,
4158 goal_column,
4159 reset,
4160 mode,
4161 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
4162 SelectPhase::Extend {
4163 position,
4164 click_count,
4165 } => self.extend_selection(position, click_count, window, cx),
4166 SelectPhase::Update {
4167 position,
4168 goal_column,
4169 scroll_delta,
4170 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
4171 SelectPhase::End => self.end_selection(window, cx),
4172 }
4173 }
4174
4175 fn extend_selection(
4176 &mut self,
4177 position: DisplayPoint,
4178 click_count: usize,
4179 window: &mut Window,
4180 cx: &mut Context<Self>,
4181 ) {
4182 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4183 let tail = self
4184 .selections
4185 .newest::<MultiBufferOffset>(&display_map)
4186 .tail();
4187 let click_count = click_count.max(match self.selections.select_mode() {
4188 SelectMode::Character => 1,
4189 SelectMode::Word(_) => 2,
4190 SelectMode::Line(_) => 3,
4191 SelectMode::All => 4,
4192 });
4193 self.begin_selection(position, false, click_count, window, cx);
4194
4195 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
4196
4197 let current_selection = match self.selections.select_mode() {
4198 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
4199 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
4200 };
4201
4202 let mut pending_selection = self
4203 .selections
4204 .pending_anchor()
4205 .cloned()
4206 .expect("extend_selection not called with pending selection");
4207
4208 if pending_selection
4209 .start
4210 .cmp(¤t_selection.start, display_map.buffer_snapshot())
4211 == Ordering::Greater
4212 {
4213 pending_selection.start = current_selection.start;
4214 }
4215 if pending_selection
4216 .end
4217 .cmp(¤t_selection.end, display_map.buffer_snapshot())
4218 == Ordering::Less
4219 {
4220 pending_selection.end = current_selection.end;
4221 pending_selection.reversed = true;
4222 }
4223
4224 let mut pending_mode = self.selections.pending_mode().unwrap();
4225 match &mut pending_mode {
4226 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
4227 _ => {}
4228 }
4229
4230 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
4231 SelectionEffects::scroll(Autoscroll::fit())
4232 } else {
4233 SelectionEffects::no_scroll()
4234 };
4235
4236 self.change_selections(effects, window, cx, |s| {
4237 s.set_pending(pending_selection.clone(), pending_mode);
4238 s.set_is_extending(true);
4239 });
4240 }
4241
4242 fn begin_selection(
4243 &mut self,
4244 position: DisplayPoint,
4245 add: bool,
4246 click_count: usize,
4247 window: &mut Window,
4248 cx: &mut Context<Self>,
4249 ) {
4250 if !self.focus_handle.is_focused(window) {
4251 self.last_focused_descendant = None;
4252 window.focus(&self.focus_handle, cx);
4253 }
4254
4255 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4256 let buffer = display_map.buffer_snapshot();
4257 let position = display_map.clip_point(position, Bias::Left);
4258
4259 let start;
4260 let end;
4261 let mode;
4262 let mut auto_scroll;
4263 match click_count {
4264 1 => {
4265 start = buffer.anchor_before(position.to_point(&display_map));
4266 end = start;
4267 mode = SelectMode::Character;
4268 auto_scroll = true;
4269 }
4270 2 => {
4271 let position = display_map
4272 .clip_point(position, Bias::Left)
4273 .to_offset(&display_map, Bias::Left);
4274 let (range, _) = buffer.surrounding_word(position, None);
4275 start = buffer.anchor_before(range.start);
4276 end = buffer.anchor_before(range.end);
4277 mode = SelectMode::Word(start..end);
4278 auto_scroll = true;
4279 }
4280 3 => {
4281 let position = display_map
4282 .clip_point(position, Bias::Left)
4283 .to_point(&display_map);
4284 let line_start = display_map.prev_line_boundary(position).0;
4285 let next_line_start = buffer.clip_point(
4286 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4287 Bias::Left,
4288 );
4289 start = buffer.anchor_before(line_start);
4290 end = buffer.anchor_before(next_line_start);
4291 mode = SelectMode::Line(start..end);
4292 auto_scroll = true;
4293 }
4294 _ => {
4295 start = buffer.anchor_before(MultiBufferOffset(0));
4296 end = buffer.anchor_before(buffer.len());
4297 mode = SelectMode::All;
4298 auto_scroll = false;
4299 }
4300 }
4301 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
4302
4303 let point_to_delete: Option<usize> = {
4304 let selected_points: Vec<Selection<Point>> =
4305 self.selections.disjoint_in_range(start..end, &display_map);
4306
4307 if !add || click_count > 1 {
4308 None
4309 } else if !selected_points.is_empty() {
4310 Some(selected_points[0].id)
4311 } else {
4312 let clicked_point_already_selected =
4313 self.selections.disjoint_anchors().iter().find(|selection| {
4314 selection.start.to_point(buffer) == start.to_point(buffer)
4315 || selection.end.to_point(buffer) == end.to_point(buffer)
4316 });
4317
4318 clicked_point_already_selected.map(|selection| selection.id)
4319 }
4320 };
4321
4322 let selections_count = self.selections.count();
4323 let effects = if auto_scroll {
4324 SelectionEffects::default()
4325 } else {
4326 SelectionEffects::no_scroll()
4327 };
4328
4329 self.change_selections(effects, window, cx, |s| {
4330 if let Some(point_to_delete) = point_to_delete {
4331 s.delete(point_to_delete);
4332
4333 if selections_count == 1 {
4334 s.set_pending_anchor_range(start..end, mode);
4335 }
4336 } else {
4337 if !add {
4338 s.clear_disjoint();
4339 }
4340
4341 s.set_pending_anchor_range(start..end, mode);
4342 }
4343 });
4344 }
4345
4346 fn begin_columnar_selection(
4347 &mut self,
4348 position: DisplayPoint,
4349 goal_column: u32,
4350 reset: bool,
4351 mode: ColumnarMode,
4352 window: &mut Window,
4353 cx: &mut Context<Self>,
4354 ) {
4355 if !self.focus_handle.is_focused(window) {
4356 self.last_focused_descendant = None;
4357 window.focus(&self.focus_handle, cx);
4358 }
4359
4360 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4361
4362 if reset {
4363 let pointer_position = display_map
4364 .buffer_snapshot()
4365 .anchor_before(position.to_point(&display_map));
4366
4367 self.change_selections(
4368 SelectionEffects::scroll(Autoscroll::newest()),
4369 window,
4370 cx,
4371 |s| {
4372 s.clear_disjoint();
4373 s.set_pending_anchor_range(
4374 pointer_position..pointer_position,
4375 SelectMode::Character,
4376 );
4377 },
4378 );
4379 };
4380
4381 let tail = self.selections.newest::<Point>(&display_map).tail();
4382 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
4383 self.columnar_selection_state = match mode {
4384 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
4385 selection_tail: selection_anchor,
4386 display_point: if reset {
4387 if position.column() != goal_column {
4388 Some(DisplayPoint::new(position.row(), goal_column))
4389 } else {
4390 None
4391 }
4392 } else {
4393 None
4394 },
4395 }),
4396 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
4397 selection_tail: selection_anchor,
4398 }),
4399 };
4400
4401 if !reset {
4402 self.select_columns(position, goal_column, &display_map, window, cx);
4403 }
4404 }
4405
4406 fn update_selection(
4407 &mut self,
4408 position: DisplayPoint,
4409 goal_column: u32,
4410 scroll_delta: gpui::Point<f32>,
4411 window: &mut Window,
4412 cx: &mut Context<Self>,
4413 ) {
4414 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4415
4416 if self.columnar_selection_state.is_some() {
4417 self.select_columns(position, goal_column, &display_map, window, cx);
4418 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
4419 let buffer = display_map.buffer_snapshot();
4420 let head;
4421 let tail;
4422 let mode = self.selections.pending_mode().unwrap();
4423 match &mode {
4424 SelectMode::Character => {
4425 head = position.to_point(&display_map);
4426 tail = pending.tail().to_point(buffer);
4427 }
4428 SelectMode::Word(original_range) => {
4429 let offset = display_map
4430 .clip_point(position, Bias::Left)
4431 .to_offset(&display_map, Bias::Left);
4432 let original_range = original_range.to_offset(buffer);
4433
4434 let head_offset = if buffer.is_inside_word(offset, None)
4435 || original_range.contains(&offset)
4436 {
4437 let (word_range, _) = buffer.surrounding_word(offset, None);
4438 if word_range.start < original_range.start {
4439 word_range.start
4440 } else {
4441 word_range.end
4442 }
4443 } else {
4444 offset
4445 };
4446
4447 head = head_offset.to_point(buffer);
4448 if head_offset <= original_range.start {
4449 tail = original_range.end.to_point(buffer);
4450 } else {
4451 tail = original_range.start.to_point(buffer);
4452 }
4453 }
4454 SelectMode::Line(original_range) => {
4455 let original_range = original_range.to_point(display_map.buffer_snapshot());
4456
4457 let position = display_map
4458 .clip_point(position, Bias::Left)
4459 .to_point(&display_map);
4460 let line_start = display_map.prev_line_boundary(position).0;
4461 let next_line_start = buffer.clip_point(
4462 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4463 Bias::Left,
4464 );
4465
4466 if line_start < original_range.start {
4467 head = line_start
4468 } else {
4469 head = next_line_start
4470 }
4471
4472 if head <= original_range.start {
4473 tail = original_range.end;
4474 } else {
4475 tail = original_range.start;
4476 }
4477 }
4478 SelectMode::All => {
4479 return;
4480 }
4481 };
4482
4483 if head < tail {
4484 pending.start = buffer.anchor_before(head);
4485 pending.end = buffer.anchor_before(tail);
4486 pending.reversed = true;
4487 } else {
4488 pending.start = buffer.anchor_before(tail);
4489 pending.end = buffer.anchor_before(head);
4490 pending.reversed = false;
4491 }
4492
4493 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4494 s.set_pending(pending.clone(), mode);
4495 });
4496 } else {
4497 log::error!("update_selection dispatched with no pending selection");
4498 return;
4499 }
4500
4501 self.apply_scroll_delta(scroll_delta, window, cx);
4502 cx.notify();
4503 }
4504
4505 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4506 self.columnar_selection_state.take();
4507 if let Some(pending_mode) = self.selections.pending_mode() {
4508 let selections = self
4509 .selections
4510 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4511 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4512 s.select(selections);
4513 s.clear_pending();
4514 if s.is_extending() {
4515 s.set_is_extending(false);
4516 } else {
4517 s.set_select_mode(pending_mode);
4518 }
4519 });
4520 }
4521 }
4522
4523 fn select_columns(
4524 &mut self,
4525 head: DisplayPoint,
4526 goal_column: u32,
4527 display_map: &DisplaySnapshot,
4528 window: &mut Window,
4529 cx: &mut Context<Self>,
4530 ) {
4531 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4532 return;
4533 };
4534
4535 let tail = match columnar_state {
4536 ColumnarSelectionState::FromMouse {
4537 selection_tail,
4538 display_point,
4539 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4540 ColumnarSelectionState::FromSelection { selection_tail } => {
4541 selection_tail.to_display_point(display_map)
4542 }
4543 };
4544
4545 let start_row = cmp::min(tail.row(), head.row());
4546 let end_row = cmp::max(tail.row(), head.row());
4547 let start_column = cmp::min(tail.column(), goal_column);
4548 let end_column = cmp::max(tail.column(), goal_column);
4549 let reversed = start_column < tail.column();
4550
4551 let selection_ranges = (start_row.0..=end_row.0)
4552 .map(DisplayRow)
4553 .filter_map(|row| {
4554 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4555 || start_column <= display_map.line_len(row))
4556 && !display_map.is_block_line(row)
4557 {
4558 let start = display_map
4559 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4560 .to_point(display_map);
4561 let end = display_map
4562 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4563 .to_point(display_map);
4564 if reversed {
4565 Some(end..start)
4566 } else {
4567 Some(start..end)
4568 }
4569 } else {
4570 None
4571 }
4572 })
4573 .collect::<Vec<_>>();
4574 if selection_ranges.is_empty() {
4575 return;
4576 }
4577
4578 let ranges = match columnar_state {
4579 ColumnarSelectionState::FromMouse { .. } => {
4580 let mut non_empty_ranges = selection_ranges
4581 .iter()
4582 .filter(|selection_range| selection_range.start != selection_range.end)
4583 .peekable();
4584 if non_empty_ranges.peek().is_some() {
4585 non_empty_ranges.cloned().collect()
4586 } else {
4587 selection_ranges
4588 }
4589 }
4590 _ => selection_ranges,
4591 };
4592
4593 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4594 s.select_ranges(ranges);
4595 });
4596 cx.notify();
4597 }
4598
4599 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4600 self.selections
4601 .all_adjusted(snapshot)
4602 .iter()
4603 .any(|selection| !selection.is_empty())
4604 }
4605
4606 pub fn has_pending_nonempty_selection(&self) -> bool {
4607 let pending_nonempty_selection = match self.selections.pending_anchor() {
4608 Some(Selection { start, end, .. }) => start != end,
4609 None => false,
4610 };
4611
4612 pending_nonempty_selection
4613 || (self.columnar_selection_state.is_some()
4614 && self.selections.disjoint_anchors().len() > 1)
4615 }
4616
4617 pub fn has_pending_selection(&self) -> bool {
4618 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4619 }
4620
4621 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4622 self.selection_mark_mode = false;
4623 self.selection_drag_state = SelectionDragState::None;
4624
4625 if self.dismiss_menus_and_popups(true, window, cx) {
4626 cx.notify();
4627 return;
4628 }
4629 if self.clear_expanded_diff_hunks(cx) {
4630 cx.notify();
4631 return;
4632 }
4633 if self.show_git_blame_gutter {
4634 self.show_git_blame_gutter = false;
4635 cx.notify();
4636 return;
4637 }
4638
4639 if self.mode.is_full()
4640 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4641 {
4642 cx.notify();
4643 return;
4644 }
4645
4646 cx.propagate();
4647 }
4648
4649 pub fn dismiss_menus_and_popups(
4650 &mut self,
4651 is_user_requested: bool,
4652 window: &mut Window,
4653 cx: &mut Context<Self>,
4654 ) -> bool {
4655 let mut dismissed = false;
4656
4657 dismissed |= self.take_rename(false, window, cx).is_some();
4658 dismissed |= self.hide_blame_popover(true, cx);
4659 dismissed |= hide_hover(self, cx);
4660 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4661 dismissed |= self.hide_context_menu(window, cx).is_some();
4662 dismissed |= self.mouse_context_menu.take().is_some();
4663 dismissed |= is_user_requested
4664 && self.discard_edit_prediction(EditPredictionDiscardReason::Rejected, cx);
4665 dismissed |= self.snippet_stack.pop().is_some();
4666 if self.diff_review_drag_state.is_some() {
4667 self.cancel_diff_review_drag(cx);
4668 dismissed = true;
4669 }
4670 if !self.diff_review_overlays.is_empty() {
4671 self.dismiss_all_diff_review_overlays(cx);
4672 dismissed = true;
4673 }
4674
4675 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4676 self.dismiss_diagnostics(cx);
4677 dismissed = true;
4678 }
4679
4680 dismissed
4681 }
4682
4683 fn linked_editing_ranges_for(
4684 &self,
4685 query_range: Range<text::Anchor>,
4686 cx: &App,
4687 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4688 use text::ToOffset as TO;
4689
4690 if self.linked_edit_ranges.is_empty() {
4691 return None;
4692 }
4693 if query_range.start.buffer_id != query_range.end.buffer_id {
4694 return None;
4695 };
4696 let multibuffer_snapshot = self.buffer.read(cx).snapshot(cx);
4697 let buffer = self.buffer.read(cx).buffer(query_range.end.buffer_id)?;
4698 let buffer_snapshot = buffer.read(cx).snapshot();
4699 let (base_range, linked_ranges) = self.linked_edit_ranges.get(
4700 buffer_snapshot.remote_id(),
4701 query_range.clone(),
4702 &buffer_snapshot,
4703 )?;
4704 // find offset from the start of current range to current cursor position
4705 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4706
4707 let start_offset = TO::to_offset(&query_range.start, &buffer_snapshot);
4708 let start_difference = start_offset - start_byte_offset;
4709 let end_offset = TO::to_offset(&query_range.end, &buffer_snapshot);
4710 let end_difference = end_offset - start_byte_offset;
4711
4712 // Current range has associated linked ranges.
4713 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4714 for range in linked_ranges.iter() {
4715 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4716 let end_offset = start_offset + end_difference;
4717 let start_offset = start_offset + start_difference;
4718 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4719 continue;
4720 }
4721 if self.selections.disjoint_anchor_ranges().any(|s| {
4722 let Some((selection_start, _)) =
4723 multibuffer_snapshot.anchor_to_buffer_anchor(s.start)
4724 else {
4725 return false;
4726 };
4727 let Some((selection_end, _)) = multibuffer_snapshot.anchor_to_buffer_anchor(s.end)
4728 else {
4729 return false;
4730 };
4731 if selection_start.buffer_id != query_range.start.buffer_id
4732 || selection_end.buffer_id != query_range.end.buffer_id
4733 {
4734 return false;
4735 }
4736 TO::to_offset(&selection_start, &buffer_snapshot) <= end_offset
4737 && TO::to_offset(&selection_end, &buffer_snapshot) >= start_offset
4738 }) {
4739 continue;
4740 }
4741 let start = buffer_snapshot.anchor_after(start_offset);
4742 let end = buffer_snapshot.anchor_after(end_offset);
4743 linked_edits
4744 .entry(buffer.clone())
4745 .or_default()
4746 .push(start..end);
4747 }
4748 Some(linked_edits)
4749 }
4750
4751 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4752 let text: Arc<str> = text.into();
4753
4754 if self.read_only(cx) {
4755 return;
4756 }
4757
4758 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4759
4760 self.unfold_buffers_with_selections(cx);
4761
4762 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4763 let mut bracket_inserted = false;
4764 let mut edits = Vec::new();
4765 let mut linked_edits = LinkedEdits::new();
4766 let mut new_selections = Vec::with_capacity(selections.len());
4767 let mut new_autoclose_regions = Vec::new();
4768 let snapshot = self.buffer.read(cx).read(cx);
4769 let mut clear_linked_edit_ranges = false;
4770 let mut all_selections_read_only = true;
4771 let mut has_adjacent_edits = false;
4772 let mut in_adjacent_group = false;
4773
4774 let mut regions = self
4775 .selections_with_autoclose_regions(selections, &snapshot)
4776 .peekable();
4777
4778 while let Some((selection, autoclose_region)) = regions.next() {
4779 if snapshot
4780 .point_to_buffer_point(selection.head())
4781 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4782 {
4783 continue;
4784 }
4785 if snapshot
4786 .point_to_buffer_point(selection.tail())
4787 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4788 {
4789 // note, ideally we'd clip the tail to the closest writeable region towards the head
4790 continue;
4791 }
4792 all_selections_read_only = false;
4793
4794 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4795 // Determine if the inserted text matches the opening or closing
4796 // bracket of any of this language's bracket pairs.
4797 let mut bracket_pair = None;
4798 let mut is_bracket_pair_start = false;
4799 let mut is_bracket_pair_end = false;
4800 if !text.is_empty() {
4801 let mut bracket_pair_matching_end = None;
4802 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4803 // and they are removing the character that triggered IME popup.
4804 for (pair, enabled) in scope.brackets() {
4805 if !pair.close && !pair.surround {
4806 continue;
4807 }
4808
4809 if enabled && pair.start.ends_with(text.as_ref()) {
4810 let prefix_len = pair.start.len() - text.len();
4811 let preceding_text_matches_prefix = prefix_len == 0
4812 || (selection.start.column >= (prefix_len as u32)
4813 && snapshot.contains_str_at(
4814 Point::new(
4815 selection.start.row,
4816 selection.start.column - (prefix_len as u32),
4817 ),
4818 &pair.start[..prefix_len],
4819 ));
4820 if preceding_text_matches_prefix {
4821 bracket_pair = Some(pair.clone());
4822 is_bracket_pair_start = true;
4823 break;
4824 }
4825 }
4826 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4827 {
4828 // take first bracket pair matching end, but don't break in case a later bracket
4829 // pair matches start
4830 bracket_pair_matching_end = Some(pair.clone());
4831 }
4832 }
4833 if let Some(end) = bracket_pair_matching_end
4834 && bracket_pair.is_none()
4835 {
4836 bracket_pair = Some(end);
4837 is_bracket_pair_end = true;
4838 }
4839 }
4840
4841 if let Some(bracket_pair) = bracket_pair {
4842 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4843 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4844 let auto_surround =
4845 self.use_auto_surround && snapshot_settings.use_auto_surround;
4846 if selection.is_empty() {
4847 if is_bracket_pair_start {
4848 // If the inserted text is a suffix of an opening bracket and the
4849 // selection is preceded by the rest of the opening bracket, then
4850 // insert the closing bracket.
4851 let following_text_allows_autoclose = snapshot
4852 .chars_at(selection.start)
4853 .next()
4854 .is_none_or(|c| scope.should_autoclose_before(c));
4855
4856 let preceding_text_allows_autoclose = selection.start.column == 0
4857 || snapshot
4858 .reversed_chars_at(selection.start)
4859 .next()
4860 .is_none_or(|c| {
4861 bracket_pair.start != bracket_pair.end
4862 || !snapshot
4863 .char_classifier_at(selection.start)
4864 .is_word(c)
4865 });
4866
4867 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4868 && bracket_pair.start.len() == 1
4869 {
4870 let target = bracket_pair.start.chars().next().unwrap();
4871 let mut byte_offset = 0u32;
4872 let current_line_count = snapshot
4873 .reversed_chars_at(selection.start)
4874 .take_while(|&c| c != '\n')
4875 .filter(|c| {
4876 byte_offset += c.len_utf8() as u32;
4877 if *c != target {
4878 return false;
4879 }
4880
4881 let point = Point::new(
4882 selection.start.row,
4883 selection.start.column.saturating_sub(byte_offset),
4884 );
4885
4886 let is_enabled = snapshot
4887 .language_scope_at(point)
4888 .and_then(|scope| {
4889 scope
4890 .brackets()
4891 .find(|(pair, _)| {
4892 pair.start == bracket_pair.start
4893 })
4894 .map(|(_, enabled)| enabled)
4895 })
4896 .unwrap_or(true);
4897
4898 let is_delimiter = snapshot
4899 .language_scope_at(Point::new(
4900 point.row,
4901 point.column + 1,
4902 ))
4903 .and_then(|scope| {
4904 scope
4905 .brackets()
4906 .find(|(pair, _)| {
4907 pair.start == bracket_pair.start
4908 })
4909 .map(|(_, enabled)| !enabled)
4910 })
4911 .unwrap_or(false);
4912
4913 is_enabled && !is_delimiter
4914 })
4915 .count();
4916 current_line_count % 2 == 1
4917 } else {
4918 false
4919 };
4920
4921 if autoclose
4922 && bracket_pair.close
4923 && following_text_allows_autoclose
4924 && preceding_text_allows_autoclose
4925 && !is_closing_quote
4926 {
4927 let anchor = snapshot.anchor_before(selection.end);
4928 new_selections.push((selection.map(|_| anchor), text.len()));
4929 new_autoclose_regions.push((
4930 anchor,
4931 text.len(),
4932 selection.id,
4933 bracket_pair.clone(),
4934 ));
4935 edits.push((
4936 selection.range(),
4937 format!("{}{}", text, bracket_pair.end).into(),
4938 ));
4939 bracket_inserted = true;
4940 continue;
4941 }
4942 }
4943
4944 if let Some(region) = autoclose_region {
4945 // If the selection is followed by an auto-inserted closing bracket,
4946 // then don't insert that closing bracket again; just move the selection
4947 // past the closing bracket.
4948 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4949 && text.as_ref() == region.pair.end.as_str()
4950 && snapshot.contains_str_at(region.range.end, text.as_ref());
4951 if should_skip {
4952 let anchor = snapshot.anchor_after(selection.end);
4953 new_selections
4954 .push((selection.map(|_| anchor), region.pair.end.len()));
4955 continue;
4956 }
4957 }
4958
4959 let always_treat_brackets_as_autoclosed = snapshot
4960 .language_settings_at(selection.start, cx)
4961 .always_treat_brackets_as_autoclosed;
4962 if always_treat_brackets_as_autoclosed
4963 && is_bracket_pair_end
4964 && snapshot.contains_str_at(selection.end, text.as_ref())
4965 {
4966 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4967 // and the inserted text is a closing bracket and the selection is followed
4968 // by the closing bracket then move the selection past the closing bracket.
4969 let anchor = snapshot.anchor_after(selection.end);
4970 new_selections.push((selection.map(|_| anchor), text.len()));
4971 continue;
4972 }
4973 }
4974 // If an opening bracket is 1 character long and is typed while
4975 // text is selected, then surround that text with the bracket pair.
4976 else if auto_surround
4977 && bracket_pair.surround
4978 && is_bracket_pair_start
4979 && bracket_pair.start.chars().count() == 1
4980 {
4981 edits.push((selection.start..selection.start, text.clone()));
4982 edits.push((
4983 selection.end..selection.end,
4984 bracket_pair.end.as_str().into(),
4985 ));
4986 bracket_inserted = true;
4987 new_selections.push((
4988 Selection {
4989 id: selection.id,
4990 start: snapshot.anchor_after(selection.start),
4991 end: snapshot.anchor_before(selection.end),
4992 reversed: selection.reversed,
4993 goal: selection.goal,
4994 },
4995 0,
4996 ));
4997 continue;
4998 }
4999 }
5000 }
5001
5002 if self.auto_replace_emoji_shortcode
5003 && selection.is_empty()
5004 && text.as_ref().ends_with(':')
5005 && let Some(possible_emoji_short_code) =
5006 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
5007 && !possible_emoji_short_code.is_empty()
5008 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
5009 {
5010 let emoji_shortcode_start = Point::new(
5011 selection.start.row,
5012 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
5013 );
5014
5015 // Remove shortcode from buffer
5016 edits.push((
5017 emoji_shortcode_start..selection.start,
5018 "".to_string().into(),
5019 ));
5020 new_selections.push((
5021 Selection {
5022 id: selection.id,
5023 start: snapshot.anchor_after(emoji_shortcode_start),
5024 end: snapshot.anchor_before(selection.start),
5025 reversed: selection.reversed,
5026 goal: selection.goal,
5027 },
5028 0,
5029 ));
5030
5031 // Insert emoji
5032 let selection_start_anchor = snapshot.anchor_after(selection.start);
5033 new_selections.push((selection.map(|_| selection_start_anchor), 0));
5034 edits.push((selection.start..selection.end, emoji.to_string().into()));
5035
5036 continue;
5037 }
5038
5039 let next_is_adjacent = regions
5040 .peek()
5041 .is_some_and(|(next, _)| selection.end == next.start);
5042
5043 // If not handling any auto-close operation, then just replace the selected
5044 // text with the given input and move the selection to the end of the
5045 // newly inserted text.
5046 let anchor = if in_adjacent_group || next_is_adjacent {
5047 // After edits the right bias would shift those anchor to the next visible fragment
5048 // but we want to resolve to the previous one
5049 snapshot.anchor_before(selection.end)
5050 } else {
5051 snapshot.anchor_after(selection.end)
5052 };
5053
5054 if !self.linked_edit_ranges.is_empty() {
5055 let start_anchor = snapshot.anchor_before(selection.start);
5056 let classifier = snapshot
5057 .char_classifier_at(start_anchor)
5058 .scope_context(Some(CharScopeContext::LinkedEdit));
5059
5060 if let Some((_, anchor_range)) =
5061 snapshot.anchor_range_to_buffer_anchor_range(start_anchor..anchor)
5062 {
5063 let is_word_char = text
5064 .chars()
5065 .next()
5066 .is_none_or(|char| classifier.is_word(char));
5067
5068 let is_dot = text.as_ref() == ".";
5069 let should_apply_linked_edit = is_word_char || is_dot;
5070
5071 if should_apply_linked_edit {
5072 linked_edits.push(&self, anchor_range, text.clone(), cx);
5073 } else {
5074 clear_linked_edit_ranges = true;
5075 }
5076 }
5077 }
5078
5079 new_selections.push((selection.map(|_| anchor), 0));
5080 edits.push((selection.start..selection.end, text.clone()));
5081
5082 has_adjacent_edits |= next_is_adjacent;
5083 in_adjacent_group = next_is_adjacent;
5084 }
5085
5086 if all_selections_read_only {
5087 return;
5088 }
5089
5090 drop(regions);
5091 drop(snapshot);
5092
5093 self.transact(window, cx, |this, window, cx| {
5094 if clear_linked_edit_ranges {
5095 this.linked_edit_ranges.clear();
5096 }
5097 let initial_buffer_versions =
5098 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
5099
5100 this.buffer.update(cx, |buffer, cx| {
5101 if has_adjacent_edits {
5102 buffer.edit_non_coalesce(edits, this.autoindent_mode.clone(), cx);
5103 } else {
5104 buffer.edit(edits, this.autoindent_mode.clone(), cx);
5105 }
5106 });
5107 linked_edits.apply(cx);
5108 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
5109 let new_selection_deltas = new_selections.iter().map(|e| e.1);
5110 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
5111 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
5112 new_anchor_selections,
5113 &map,
5114 )
5115 .zip(new_selection_deltas)
5116 .map(|(selection, delta)| Selection {
5117 id: selection.id,
5118 start: selection.start + delta,
5119 end: selection.end + delta,
5120 reversed: selection.reversed,
5121 goal: SelectionGoal::None,
5122 })
5123 .collect::<Vec<_>>();
5124
5125 let mut i = 0;
5126 for (position, delta, selection_id, pair) in new_autoclose_regions {
5127 let position = position.to_offset(map.buffer_snapshot()) + delta;
5128 let start = map.buffer_snapshot().anchor_before(position);
5129 let end = map.buffer_snapshot().anchor_after(position);
5130 while let Some(existing_state) = this.autoclose_regions.get(i) {
5131 match existing_state
5132 .range
5133 .start
5134 .cmp(&start, map.buffer_snapshot())
5135 {
5136 Ordering::Less => i += 1,
5137 Ordering::Greater => break,
5138 Ordering::Equal => {
5139 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
5140 Ordering::Less => i += 1,
5141 Ordering::Equal => break,
5142 Ordering::Greater => break,
5143 }
5144 }
5145 }
5146 }
5147 this.autoclose_regions.insert(
5148 i,
5149 AutocloseRegion {
5150 selection_id,
5151 range: start..end,
5152 pair,
5153 },
5154 );
5155 }
5156
5157 let had_active_edit_prediction = this.has_active_edit_prediction();
5158 this.change_selections(
5159 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
5160 window,
5161 cx,
5162 |s| s.select(new_selections),
5163 );
5164
5165 if !bracket_inserted
5166 && let Some(on_type_format_task) =
5167 this.trigger_on_type_formatting(text.to_string(), window, cx)
5168 {
5169 on_type_format_task.detach_and_log_err(cx);
5170 }
5171
5172 let editor_settings = EditorSettings::get_global(cx);
5173 if bracket_inserted
5174 && (editor_settings.auto_signature_help
5175 || editor_settings.show_signature_help_after_edits)
5176 {
5177 this.show_signature_help(&ShowSignatureHelp, window, cx);
5178 }
5179
5180 let trigger_in_words =
5181 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
5182 if this.hard_wrap.is_some() {
5183 let latest: Range<Point> = this.selections.newest(&map).range();
5184 if latest.is_empty()
5185 && this
5186 .buffer()
5187 .read(cx)
5188 .snapshot(cx)
5189 .line_len(MultiBufferRow(latest.start.row))
5190 == latest.start.column
5191 {
5192 this.rewrap_impl(
5193 RewrapOptions {
5194 override_language_settings: true,
5195 preserve_existing_whitespace: true,
5196 line_length: None,
5197 },
5198 cx,
5199 )
5200 }
5201 }
5202 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
5203 refresh_linked_ranges(this, window, cx);
5204 this.refresh_edit_prediction(true, false, window, cx);
5205 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
5206 });
5207 }
5208
5209 fn find_possible_emoji_shortcode_at_position(
5210 snapshot: &MultiBufferSnapshot,
5211 position: Point,
5212 ) -> Option<String> {
5213 let mut chars = Vec::new();
5214 let mut found_colon = false;
5215 for char in snapshot.reversed_chars_at(position).take(100) {
5216 // Found a possible emoji shortcode in the middle of the buffer
5217 if found_colon {
5218 if char.is_whitespace() {
5219 chars.reverse();
5220 return Some(chars.iter().collect());
5221 }
5222 // If the previous character is not a whitespace, we are in the middle of a word
5223 // and we only want to complete the shortcode if the word is made up of other emojis
5224 let mut containing_word = String::new();
5225 for ch in snapshot
5226 .reversed_chars_at(position)
5227 .skip(chars.len() + 1)
5228 .take(100)
5229 {
5230 if ch.is_whitespace() {
5231 break;
5232 }
5233 containing_word.push(ch);
5234 }
5235 let containing_word = containing_word.chars().rev().collect::<String>();
5236 if util::word_consists_of_emojis(containing_word.as_str()) {
5237 chars.reverse();
5238 return Some(chars.iter().collect());
5239 }
5240 }
5241
5242 if char.is_whitespace() || !char.is_ascii() {
5243 return None;
5244 }
5245 if char == ':' {
5246 found_colon = true;
5247 } else {
5248 chars.push(char);
5249 }
5250 }
5251 // Found a possible emoji shortcode at the beginning of the buffer
5252 chars.reverse();
5253 Some(chars.iter().collect())
5254 }
5255
5256 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
5257 if self.read_only(cx) {
5258 return;
5259 }
5260
5261 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5262 self.transact(window, cx, |this, window, cx| {
5263 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
5264 let selections = this
5265 .selections
5266 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
5267 let multi_buffer = this.buffer.read(cx);
5268 let buffer = multi_buffer.snapshot(cx);
5269 selections
5270 .iter()
5271 .map(|selection| {
5272 let start_point = selection.start.to_point(&buffer);
5273 let mut existing_indent =
5274 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
5275 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
5276 let start = selection.start;
5277 let end = selection.end;
5278 let selection_is_empty = start == end;
5279 let language_scope = buffer.language_scope_at(start);
5280 let (delimiter, newline_config) = if let Some(language) = &language_scope {
5281 let needs_extra_newline = NewlineConfig::insert_extra_newline_brackets(
5282 &buffer,
5283 start..end,
5284 language,
5285 )
5286 || NewlineConfig::insert_extra_newline_tree_sitter(
5287 &buffer,
5288 start..end,
5289 );
5290
5291 let mut newline_config = NewlineConfig::Newline {
5292 additional_indent: IndentSize::spaces(0),
5293 extra_line_additional_indent: if needs_extra_newline {
5294 Some(IndentSize::spaces(0))
5295 } else {
5296 None
5297 },
5298 prevent_auto_indent: false,
5299 };
5300
5301 let comment_delimiter = maybe!({
5302 if !selection_is_empty {
5303 return None;
5304 }
5305
5306 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5307 return None;
5308 }
5309
5310 return comment_delimiter_for_newline(
5311 &start_point,
5312 &buffer,
5313 language,
5314 );
5315 });
5316
5317 let doc_delimiter = maybe!({
5318 if !selection_is_empty {
5319 return None;
5320 }
5321
5322 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5323 return None;
5324 }
5325
5326 return documentation_delimiter_for_newline(
5327 &start_point,
5328 &buffer,
5329 language,
5330 &mut newline_config,
5331 );
5332 });
5333
5334 let list_delimiter = maybe!({
5335 if !selection_is_empty {
5336 return None;
5337 }
5338
5339 if !multi_buffer.language_settings(cx).extend_list_on_newline {
5340 return None;
5341 }
5342
5343 return list_delimiter_for_newline(
5344 &start_point,
5345 &buffer,
5346 language,
5347 &mut newline_config,
5348 );
5349 });
5350
5351 (
5352 comment_delimiter.or(doc_delimiter).or(list_delimiter),
5353 newline_config,
5354 )
5355 } else {
5356 (
5357 None,
5358 NewlineConfig::Newline {
5359 additional_indent: IndentSize::spaces(0),
5360 extra_line_additional_indent: None,
5361 prevent_auto_indent: false,
5362 },
5363 )
5364 };
5365
5366 let (edit_start, new_text, prevent_auto_indent) = match &newline_config {
5367 NewlineConfig::ClearCurrentLine => {
5368 let row_start =
5369 buffer.point_to_offset(Point::new(start_point.row, 0));
5370 (row_start, String::new(), false)
5371 }
5372 NewlineConfig::UnindentCurrentLine { continuation } => {
5373 let row_start =
5374 buffer.point_to_offset(Point::new(start_point.row, 0));
5375 let tab_size = buffer.language_settings_at(start, cx).tab_size;
5376 let tab_size_indent = IndentSize::spaces(tab_size.get());
5377 let reduced_indent =
5378 existing_indent.with_delta(Ordering::Less, tab_size_indent);
5379 let mut new_text = String::new();
5380 new_text.extend(reduced_indent.chars());
5381 new_text.push_str(continuation);
5382 (row_start, new_text, true)
5383 }
5384 NewlineConfig::Newline {
5385 additional_indent,
5386 extra_line_additional_indent,
5387 prevent_auto_indent,
5388 } => {
5389 let auto_indent_mode =
5390 buffer.language_settings_at(start, cx).auto_indent;
5391 let preserve_indent =
5392 auto_indent_mode != language::AutoIndentMode::None;
5393 let apply_syntax_indent =
5394 auto_indent_mode == language::AutoIndentMode::SyntaxAware;
5395 let capacity_for_delimiter =
5396 delimiter.as_deref().map(str::len).unwrap_or_default();
5397 let existing_indent_len = if preserve_indent {
5398 existing_indent.len as usize
5399 } else {
5400 0
5401 };
5402 let extra_line_len = extra_line_additional_indent
5403 .map(|i| 1 + existing_indent_len + i.len as usize)
5404 .unwrap_or(0);
5405 let mut new_text = String::with_capacity(
5406 1 + capacity_for_delimiter
5407 + existing_indent_len
5408 + additional_indent.len as usize
5409 + extra_line_len,
5410 );
5411 new_text.push('\n');
5412 if preserve_indent {
5413 new_text.extend(existing_indent.chars());
5414 }
5415 new_text.extend(additional_indent.chars());
5416 if let Some(delimiter) = &delimiter {
5417 new_text.push_str(delimiter);
5418 }
5419 if let Some(extra_indent) = extra_line_additional_indent {
5420 new_text.push('\n');
5421 if preserve_indent {
5422 new_text.extend(existing_indent.chars());
5423 }
5424 new_text.extend(extra_indent.chars());
5425 }
5426 (
5427 start,
5428 new_text,
5429 *prevent_auto_indent || !apply_syntax_indent,
5430 )
5431 }
5432 };
5433
5434 let anchor = buffer.anchor_after(end);
5435 let new_selection = selection.map(|_| anchor);
5436 (
5437 ((edit_start..end, new_text), prevent_auto_indent),
5438 (newline_config.has_extra_line(), new_selection),
5439 )
5440 })
5441 .unzip()
5442 };
5443
5444 let mut auto_indent_edits = Vec::new();
5445 let mut edits = Vec::new();
5446 for (edit, prevent_auto_indent) in edits_with_flags {
5447 if prevent_auto_indent {
5448 edits.push(edit);
5449 } else {
5450 auto_indent_edits.push(edit);
5451 }
5452 }
5453 if !edits.is_empty() {
5454 this.edit(edits, cx);
5455 }
5456 if !auto_indent_edits.is_empty() {
5457 this.edit_with_autoindent(auto_indent_edits, cx);
5458 }
5459
5460 let buffer = this.buffer.read(cx).snapshot(cx);
5461 let new_selections = selection_info
5462 .into_iter()
5463 .map(|(extra_newline_inserted, new_selection)| {
5464 let mut cursor = new_selection.end.to_point(&buffer);
5465 if extra_newline_inserted {
5466 cursor.row -= 1;
5467 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
5468 }
5469 new_selection.map(|_| cursor)
5470 })
5471 .collect();
5472
5473 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
5474 this.refresh_edit_prediction(true, false, window, cx);
5475 if let Some(task) = this.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5476 task.detach_and_log_err(cx);
5477 }
5478 });
5479 }
5480
5481 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
5482 if self.read_only(cx) {
5483 return;
5484 }
5485
5486 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5487
5488 let buffer = self.buffer.read(cx);
5489 let snapshot = buffer.snapshot(cx);
5490
5491 let mut edits = Vec::new();
5492 let mut rows = Vec::new();
5493
5494 for (rows_inserted, selection) in self
5495 .selections
5496 .all_adjusted(&self.display_snapshot(cx))
5497 .into_iter()
5498 .enumerate()
5499 {
5500 let cursor = selection.head();
5501 let row = cursor.row;
5502
5503 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
5504
5505 let newline = "\n".to_string();
5506 edits.push((start_of_line..start_of_line, newline));
5507
5508 rows.push(row + rows_inserted as u32);
5509 }
5510
5511 self.transact(window, cx, |editor, window, cx| {
5512 editor.edit(edits, cx);
5513
5514 editor.change_selections(Default::default(), window, cx, |s| {
5515 let mut index = 0;
5516 s.move_cursors_with(&mut |map, _, _| {
5517 let row = rows[index];
5518 index += 1;
5519
5520 let point = Point::new(row, 0);
5521 let boundary = map.next_line_boundary(point).1;
5522 let clipped = map.clip_point(boundary, Bias::Left);
5523
5524 (clipped, SelectionGoal::None)
5525 });
5526 });
5527
5528 let mut indent_edits = Vec::new();
5529 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5530 for row in rows {
5531 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5532 for (row, indent) in indents {
5533 if indent.len == 0 {
5534 continue;
5535 }
5536
5537 let text = match indent.kind {
5538 IndentKind::Space => " ".repeat(indent.len as usize),
5539 IndentKind::Tab => "\t".repeat(indent.len as usize),
5540 };
5541 let point = Point::new(row.0, 0);
5542 indent_edits.push((point..point, text));
5543 }
5544 }
5545 editor.edit(indent_edits, cx);
5546 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5547 format.detach_and_log_err(cx);
5548 }
5549 });
5550 }
5551
5552 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5553 if self.read_only(cx) {
5554 return;
5555 }
5556
5557 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5558
5559 let mut buffer_edits: HashMap<EntityId, (Entity<Buffer>, Vec<Point>)> = HashMap::default();
5560 let mut rows = Vec::new();
5561 let mut rows_inserted = 0;
5562
5563 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5564 let cursor = selection.head();
5565 let row = cursor.row;
5566
5567 let point = Point::new(row, 0);
5568 let Some((buffer_handle, buffer_point)) =
5569 self.buffer.read(cx).point_to_buffer_point(point, cx)
5570 else {
5571 continue;
5572 };
5573
5574 buffer_edits
5575 .entry(buffer_handle.entity_id())
5576 .or_insert_with(|| (buffer_handle, Vec::new()))
5577 .1
5578 .push(buffer_point);
5579
5580 rows_inserted += 1;
5581 rows.push(row + rows_inserted);
5582 }
5583
5584 self.transact(window, cx, |editor, window, cx| {
5585 for (_, (buffer_handle, points)) in &buffer_edits {
5586 buffer_handle.update(cx, |buffer, cx| {
5587 let edits: Vec<_> = points
5588 .iter()
5589 .map(|point| {
5590 let target = Point::new(point.row + 1, 0);
5591 let start_of_line = buffer.point_to_offset(target).min(buffer.len());
5592 (start_of_line..start_of_line, "\n")
5593 })
5594 .collect();
5595 buffer.edit(edits, None, cx);
5596 });
5597 }
5598
5599 editor.change_selections(Default::default(), window, cx, |s| {
5600 let mut index = 0;
5601 s.move_cursors_with(&mut |map, _, _| {
5602 let row = rows[index];
5603 index += 1;
5604
5605 let point = Point::new(row, 0);
5606 let boundary = map.next_line_boundary(point).1;
5607 let clipped = map.clip_point(boundary, Bias::Left);
5608
5609 (clipped, SelectionGoal::None)
5610 });
5611 });
5612
5613 let mut indent_edits = Vec::new();
5614 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5615 for row in rows {
5616 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5617 for (row, indent) in indents {
5618 if indent.len == 0 {
5619 continue;
5620 }
5621
5622 let text = match indent.kind {
5623 IndentKind::Space => " ".repeat(indent.len as usize),
5624 IndentKind::Tab => "\t".repeat(indent.len as usize),
5625 };
5626 let point = Point::new(row.0, 0);
5627 indent_edits.push((point..point, text));
5628 }
5629 }
5630 editor.edit(indent_edits, cx);
5631 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5632 format.detach_and_log_err(cx);
5633 }
5634 });
5635 }
5636
5637 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5638 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5639 original_indent_columns: Vec::new(),
5640 });
5641 self.replace_selections(text, autoindent, window, cx, false);
5642 }
5643
5644 /// Replaces the editor's selections with the provided `text`, applying the
5645 /// given `autoindent_mode` (`None` will skip autoindentation).
5646 ///
5647 /// Early returns if the editor is in read-only mode, without applying any
5648 /// edits.
5649 fn replace_selections(
5650 &mut self,
5651 text: &str,
5652 autoindent_mode: Option<AutoindentMode>,
5653 window: &mut Window,
5654 cx: &mut Context<Self>,
5655 apply_linked_edits: bool,
5656 ) {
5657 if self.read_only(cx) {
5658 return;
5659 }
5660
5661 let text: Arc<str> = text.into();
5662 self.transact(window, cx, |this, window, cx| {
5663 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5664 let linked_edits = if apply_linked_edits {
5665 this.linked_edits_for_selections(text.clone(), cx)
5666 } else {
5667 LinkedEdits::new()
5668 };
5669
5670 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5671 let anchors = {
5672 let snapshot = buffer.read(cx);
5673 old_selections
5674 .iter()
5675 .map(|s| {
5676 let anchor = snapshot.anchor_after(s.head());
5677 s.map(|_| anchor)
5678 })
5679 .collect::<Vec<_>>()
5680 };
5681 buffer.edit(
5682 old_selections
5683 .iter()
5684 .map(|s| (s.start..s.end, text.clone())),
5685 autoindent_mode,
5686 cx,
5687 );
5688 anchors
5689 });
5690
5691 linked_edits.apply(cx);
5692
5693 this.change_selections(Default::default(), window, cx, |s| {
5694 s.select_anchors(selection_anchors);
5695 });
5696
5697 if apply_linked_edits {
5698 refresh_linked_ranges(this, window, cx);
5699 }
5700
5701 cx.notify();
5702 });
5703 }
5704
5705 /// Collects linked edits for the current selections, pairing each linked
5706 /// range with `text`.
5707 pub fn linked_edits_for_selections(&self, text: Arc<str>, cx: &App) -> LinkedEdits {
5708 let multibuffer_snapshot = self.buffer().read(cx).snapshot(cx);
5709 let mut linked_edits = LinkedEdits::new();
5710 if !self.linked_edit_ranges.is_empty() {
5711 for selection in self.selections.disjoint_anchors() {
5712 let Some((_, range)) =
5713 multibuffer_snapshot.anchor_range_to_buffer_anchor_range(selection.range())
5714 else {
5715 continue;
5716 };
5717 linked_edits.push(self, range, text.clone(), cx);
5718 }
5719 }
5720 linked_edits
5721 }
5722
5723 /// Deletes the content covered by the current selections and applies
5724 /// linked edits.
5725 pub fn delete_selections_with_linked_edits(
5726 &mut self,
5727 window: &mut Window,
5728 cx: &mut Context<Self>,
5729 ) {
5730 self.replace_selections("", None, window, cx, true);
5731 }
5732
5733 #[cfg(any(test, feature = "test-support"))]
5734 pub fn set_linked_edit_ranges_for_testing(
5735 &mut self,
5736 ranges: Vec<(Range<Point>, Vec<Range<Point>>)>,
5737 cx: &mut Context<Self>,
5738 ) -> Option<()> {
5739 let Some((buffer, _)) = self
5740 .buffer
5741 .read(cx)
5742 .text_anchor_for_position(self.selections.newest_anchor().start, cx)
5743 else {
5744 return None;
5745 };
5746 let buffer = buffer.read(cx);
5747 let buffer_id = buffer.remote_id();
5748 let mut linked_ranges = Vec::with_capacity(ranges.len());
5749 for (base_range, linked_ranges_points) in ranges {
5750 let base_anchor =
5751 buffer.anchor_before(base_range.start)..buffer.anchor_after(base_range.end);
5752 let linked_anchors = linked_ranges_points
5753 .into_iter()
5754 .map(|range| buffer.anchor_before(range.start)..buffer.anchor_after(range.end))
5755 .collect();
5756 linked_ranges.push((base_anchor, linked_anchors));
5757 }
5758 let mut map = HashMap::default();
5759 map.insert(buffer_id, linked_ranges);
5760 self.linked_edit_ranges = linked_editing_ranges::LinkedEditingRanges(map);
5761 Some(())
5762 }
5763
5764 fn trigger_completion_on_input(
5765 &mut self,
5766 text: &str,
5767 trigger_in_words: bool,
5768 window: &mut Window,
5769 cx: &mut Context<Self>,
5770 ) {
5771 let completions_source = self
5772 .context_menu
5773 .borrow()
5774 .as_ref()
5775 .and_then(|menu| match menu {
5776 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5777 CodeContextMenu::CodeActions(_) => None,
5778 });
5779
5780 match completions_source {
5781 Some(CompletionsMenuSource::Words { .. }) => {
5782 self.open_or_update_completions_menu(
5783 Some(CompletionsMenuSource::Words {
5784 ignore_threshold: false,
5785 }),
5786 None,
5787 trigger_in_words,
5788 window,
5789 cx,
5790 );
5791 }
5792 _ => self.open_or_update_completions_menu(
5793 None,
5794 Some(text.to_owned()).filter(|x| !x.is_empty()),
5795 true,
5796 window,
5797 cx,
5798 ),
5799 }
5800 }
5801
5802 /// If any empty selections is touching the start of its innermost containing autoclose
5803 /// region, expand it to select the brackets.
5804 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5805 let selections = self
5806 .selections
5807 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5808 let buffer = self.buffer.read(cx).read(cx);
5809 let new_selections = self
5810 .selections_with_autoclose_regions(selections, &buffer)
5811 .map(|(mut selection, region)| {
5812 if !selection.is_empty() {
5813 return selection;
5814 }
5815
5816 if let Some(region) = region {
5817 let mut range = region.range.to_offset(&buffer);
5818 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5819 range.start -= region.pair.start.len();
5820 if buffer.contains_str_at(range.start, ®ion.pair.start)
5821 && buffer.contains_str_at(range.end, ®ion.pair.end)
5822 {
5823 range.end += region.pair.end.len();
5824 selection.start = range.start;
5825 selection.end = range.end;
5826
5827 return selection;
5828 }
5829 }
5830 }
5831
5832 let always_treat_brackets_as_autoclosed = buffer
5833 .language_settings_at(selection.start, cx)
5834 .always_treat_brackets_as_autoclosed;
5835
5836 if !always_treat_brackets_as_autoclosed {
5837 return selection;
5838 }
5839
5840 if let Some(scope) = buffer.language_scope_at(selection.start) {
5841 for (pair, enabled) in scope.brackets() {
5842 if !enabled || !pair.close {
5843 continue;
5844 }
5845
5846 if buffer.contains_str_at(selection.start, &pair.end) {
5847 let pair_start_len = pair.start.len();
5848 if buffer.contains_str_at(
5849 selection.start.saturating_sub_usize(pair_start_len),
5850 &pair.start,
5851 ) {
5852 selection.start -= pair_start_len;
5853 selection.end += pair.end.len();
5854
5855 return selection;
5856 }
5857 }
5858 }
5859 }
5860
5861 selection
5862 })
5863 .collect();
5864
5865 drop(buffer);
5866 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5867 selections.select(new_selections)
5868 });
5869 }
5870
5871 /// Iterate the given selections, and for each one, find the smallest surrounding
5872 /// autoclose region. This uses the ordering of the selections and the autoclose
5873 /// regions to avoid repeated comparisons.
5874 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5875 &'a self,
5876 selections: impl IntoIterator<Item = Selection<D>>,
5877 buffer: &'a MultiBufferSnapshot,
5878 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5879 let mut i = 0;
5880 let mut regions = self.autoclose_regions.as_slice();
5881 selections.into_iter().map(move |selection| {
5882 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5883
5884 let mut enclosing = None;
5885 while let Some(pair_state) = regions.get(i) {
5886 if pair_state.range.end.to_offset(buffer) < range.start {
5887 regions = ®ions[i + 1..];
5888 i = 0;
5889 } else if pair_state.range.start.to_offset(buffer) > range.end {
5890 break;
5891 } else {
5892 if pair_state.selection_id == selection.id {
5893 enclosing = Some(pair_state);
5894 }
5895 i += 1;
5896 }
5897 }
5898
5899 (selection, enclosing)
5900 })
5901 }
5902
5903 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5904 fn invalidate_autoclose_regions(
5905 &mut self,
5906 mut selections: &[Selection<Anchor>],
5907 buffer: &MultiBufferSnapshot,
5908 ) {
5909 self.autoclose_regions.retain(|state| {
5910 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5911 return false;
5912 }
5913
5914 let mut i = 0;
5915 while let Some(selection) = selections.get(i) {
5916 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5917 selections = &selections[1..];
5918 continue;
5919 }
5920 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5921 break;
5922 }
5923 if selection.id == state.selection_id {
5924 return true;
5925 } else {
5926 i += 1;
5927 }
5928 }
5929 false
5930 });
5931 }
5932
5933 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5934 let offset = position.to_offset(buffer);
5935 let (word_range, kind) =
5936 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5937 if offset > word_range.start && kind == Some(CharKind::Word) {
5938 Some(
5939 buffer
5940 .text_for_range(word_range.start..offset)
5941 .collect::<String>(),
5942 )
5943 } else {
5944 None
5945 }
5946 }
5947
5948 pub fn is_lsp_relevant(&self, file: Option<&Arc<dyn language::File>>, cx: &App) -> bool {
5949 let Some(project) = self.project() else {
5950 return false;
5951 };
5952 let Some(buffer_file) = project::File::from_dyn(file) else {
5953 return false;
5954 };
5955 let Some(entry_id) = buffer_file.project_entry_id() else {
5956 return false;
5957 };
5958 let project = project.read(cx);
5959 let Some(buffer_worktree) = project.worktree_for_id(buffer_file.worktree_id(cx), cx) else {
5960 return false;
5961 };
5962 let Some(worktree_entry) = buffer_worktree.read(cx).entry_for_id(entry_id) else {
5963 return false;
5964 };
5965 !worktree_entry.is_ignored
5966 }
5967
5968 pub fn visible_buffers(&self, cx: &mut Context<Editor>) -> Vec<Entity<Buffer>> {
5969 let display_snapshot = self.display_snapshot(cx);
5970 let visible_range = self.multi_buffer_visible_range(&display_snapshot, cx);
5971 let multi_buffer = self.buffer().read(cx);
5972 display_snapshot
5973 .buffer_snapshot()
5974 .range_to_buffer_ranges(visible_range)
5975 .into_iter()
5976 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5977 .filter_map(|(buffer_snapshot, _, _)| multi_buffer.buffer(buffer_snapshot.remote_id()))
5978 .collect()
5979 }
5980
5981 pub fn visible_buffer_ranges(
5982 &self,
5983 cx: &mut Context<Editor>,
5984 ) -> Vec<(
5985 BufferSnapshot,
5986 Range<BufferOffset>,
5987 ExcerptRange<text::Anchor>,
5988 )> {
5989 let display_snapshot = self.display_snapshot(cx);
5990 let visible_range = self.multi_buffer_visible_range(&display_snapshot, cx);
5991 display_snapshot
5992 .buffer_snapshot()
5993 .range_to_buffer_ranges(visible_range)
5994 .into_iter()
5995 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5996 .collect()
5997 }
5998
5999 pub fn text_layout_details(&self, window: &mut Window, cx: &mut App) -> TextLayoutDetails {
6000 TextLayoutDetails {
6001 text_system: window.text_system().clone(),
6002 editor_style: self.style.clone().unwrap(),
6003 rem_size: window.rem_size(),
6004 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
6005 visible_rows: self.visible_line_count(),
6006 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
6007 }
6008 }
6009
6010 fn trigger_on_type_formatting(
6011 &self,
6012 input: String,
6013 window: &mut Window,
6014 cx: &mut Context<Self>,
6015 ) -> Option<Task<Result<()>>> {
6016 if input.chars().count() != 1 {
6017 return None;
6018 }
6019
6020 let project = self.project()?;
6021 let position = self.selections.newest_anchor().head();
6022 let (buffer, buffer_position) = self
6023 .buffer
6024 .read(cx)
6025 .text_anchor_for_position(position, cx)?;
6026
6027 let settings = LanguageSettings::for_buffer_at(&buffer.read(cx), buffer_position, cx);
6028 if !settings.use_on_type_format {
6029 return None;
6030 }
6031
6032 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
6033 // hence we do LSP request & edit on host side only — add formats to host's history.
6034 let push_to_lsp_host_history = true;
6035 // If this is not the host, append its history with new edits.
6036 let push_to_client_history = project.read(cx).is_via_collab();
6037
6038 let on_type_formatting = project.update(cx, |project, cx| {
6039 project.on_type_format(
6040 buffer.clone(),
6041 buffer_position,
6042 input,
6043 push_to_lsp_host_history,
6044 cx,
6045 )
6046 });
6047 Some(cx.spawn_in(window, async move |editor, cx| {
6048 if let Some(transaction) = on_type_formatting.await? {
6049 if push_to_client_history {
6050 buffer.update(cx, |buffer, _| {
6051 buffer.push_transaction(transaction, Instant::now());
6052 buffer.finalize_last_transaction();
6053 });
6054 }
6055 editor.update(cx, |editor, cx| {
6056 editor.refresh_document_highlights(cx);
6057 })?;
6058 }
6059 Ok(())
6060 }))
6061 }
6062
6063 pub fn show_word_completions(
6064 &mut self,
6065 _: &ShowWordCompletions,
6066 window: &mut Window,
6067 cx: &mut Context<Self>,
6068 ) {
6069 self.open_or_update_completions_menu(
6070 Some(CompletionsMenuSource::Words {
6071 ignore_threshold: true,
6072 }),
6073 None,
6074 false,
6075 window,
6076 cx,
6077 );
6078 }
6079
6080 pub fn show_completions(
6081 &mut self,
6082 _: &ShowCompletions,
6083 window: &mut Window,
6084 cx: &mut Context<Self>,
6085 ) {
6086 self.open_or_update_completions_menu(None, None, false, window, cx);
6087 }
6088
6089 fn open_or_update_completions_menu(
6090 &mut self,
6091 requested_source: Option<CompletionsMenuSource>,
6092 trigger: Option<String>,
6093 trigger_in_words: bool,
6094 window: &mut Window,
6095 cx: &mut Context<Self>,
6096 ) {
6097 if self.pending_rename.is_some() {
6098 return;
6099 }
6100
6101 let completions_source = self
6102 .context_menu
6103 .borrow()
6104 .as_ref()
6105 .and_then(|menu| match menu {
6106 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
6107 CodeContextMenu::CodeActions(_) => None,
6108 });
6109
6110 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
6111
6112 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
6113 // inserted and selected. To handle that case, the start of the selection is used so that
6114 // the menu starts with all choices.
6115 let position = self
6116 .selections
6117 .newest_anchor()
6118 .start
6119 .bias_right(&multibuffer_snapshot);
6120
6121 if position.diff_base_anchor().is_some() {
6122 return;
6123 }
6124 let multibuffer_position = multibuffer_snapshot.anchor_before(position);
6125 let Some((buffer_position, _)) =
6126 multibuffer_snapshot.anchor_to_buffer_anchor(multibuffer_position)
6127 else {
6128 return;
6129 };
6130 let Some(buffer) = self.buffer.read(cx).buffer(buffer_position.buffer_id) else {
6131 return;
6132 };
6133 let buffer_snapshot = buffer.read(cx).snapshot();
6134
6135 let menu_is_open = matches!(
6136 self.context_menu.borrow().as_ref(),
6137 Some(CodeContextMenu::Completions(_))
6138 );
6139
6140 let language = buffer_snapshot
6141 .language_at(buffer_position)
6142 .map(|language| language.name());
6143 let language_settings = multibuffer_snapshot.language_settings_at(multibuffer_position, cx);
6144 let completion_settings = language_settings.completions.clone();
6145
6146 let show_completions_on_input = self
6147 .show_completions_on_input_override
6148 .unwrap_or(language_settings.show_completions_on_input);
6149 if !menu_is_open && trigger.is_some() && !show_completions_on_input {
6150 return;
6151 }
6152
6153 let query: Option<Arc<String>> =
6154 Self::completion_query(&multibuffer_snapshot, multibuffer_position)
6155 .map(|query| query.into());
6156
6157 drop(multibuffer_snapshot);
6158
6159 // Hide the current completions menu when query is empty. Without this, cached
6160 // completions from before the trigger char may be reused (#32774).
6161 if query.is_none() && menu_is_open {
6162 self.hide_context_menu(window, cx);
6163 }
6164
6165 let mut ignore_word_threshold = false;
6166 let provider = match requested_source {
6167 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
6168 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
6169 ignore_word_threshold = ignore_threshold;
6170 None
6171 }
6172 Some(CompletionsMenuSource::SnippetChoices)
6173 | Some(CompletionsMenuSource::SnippetsOnly) => {
6174 log::error!("bug: SnippetChoices requested_source is not handled");
6175 None
6176 }
6177 };
6178
6179 let sort_completions = provider
6180 .as_ref()
6181 .is_some_and(|provider| provider.sort_completions());
6182
6183 let filter_completions = provider
6184 .as_ref()
6185 .is_none_or(|provider| provider.filter_completions());
6186
6187 let was_snippets_only = matches!(
6188 completions_source,
6189 Some(CompletionsMenuSource::SnippetsOnly)
6190 );
6191
6192 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
6193 if filter_completions {
6194 menu.filter(
6195 query.clone().unwrap_or_default(),
6196 buffer_position,
6197 &buffer,
6198 provider.clone(),
6199 window,
6200 cx,
6201 );
6202 }
6203 // When `is_incomplete` is false, no need to re-query completions when the current query
6204 // is a suffix of the initial query.
6205 let was_complete = !menu.is_incomplete;
6206 if was_complete && !was_snippets_only {
6207 // If the new query is a suffix of the old query (typing more characters) and
6208 // the previous result was complete, the existing completions can be filtered.
6209 //
6210 // Note that snippet completions are always complete.
6211 let query_matches = match (&menu.initial_query, &query) {
6212 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
6213 (None, _) => true,
6214 _ => false,
6215 };
6216 if query_matches {
6217 let position_matches = if menu.initial_position == position {
6218 true
6219 } else {
6220 let snapshot = self.buffer.read(cx).read(cx);
6221 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
6222 };
6223 if position_matches {
6224 return;
6225 }
6226 }
6227 }
6228 };
6229
6230 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
6231 buffer_snapshot.surrounding_word(buffer_position, None)
6232 {
6233 let word_to_exclude = buffer_snapshot
6234 .text_for_range(word_range.clone())
6235 .collect::<String>();
6236 (
6237 buffer_snapshot.anchor_before(word_range.start)
6238 ..buffer_snapshot.anchor_after(buffer_position),
6239 Some(word_to_exclude),
6240 )
6241 } else {
6242 (buffer_position..buffer_position, None)
6243 };
6244
6245 let show_completion_documentation = buffer_snapshot
6246 .settings_at(buffer_position, cx)
6247 .show_completion_documentation;
6248
6249 // The document can be large, so stay in reasonable bounds when searching for words,
6250 // otherwise completion pop-up might be slow to appear.
6251 const WORD_LOOKUP_ROWS: u32 = 5_000;
6252 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
6253 let min_word_search = buffer_snapshot.clip_point(
6254 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
6255 Bias::Left,
6256 );
6257 let max_word_search = buffer_snapshot.clip_point(
6258 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
6259 Bias::Right,
6260 );
6261 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
6262 ..buffer_snapshot.point_to_offset(max_word_search);
6263
6264 let skip_digits = query
6265 .as_ref()
6266 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
6267
6268 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
6269 trigger.as_ref().is_none_or(|trigger| {
6270 provider.is_completion_trigger(
6271 &buffer,
6272 buffer_position,
6273 trigger,
6274 trigger_in_words,
6275 cx,
6276 )
6277 })
6278 });
6279
6280 let provider_responses = if let Some(provider) = &provider
6281 && load_provider_completions
6282 {
6283 let trigger_character =
6284 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
6285 let completion_context = CompletionContext {
6286 trigger_kind: match &trigger_character {
6287 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
6288 None => CompletionTriggerKind::INVOKED,
6289 },
6290 trigger_character,
6291 };
6292
6293 provider.completions(&buffer, buffer_position, completion_context, window, cx)
6294 } else {
6295 Task::ready(Ok(Vec::new()))
6296 };
6297
6298 let load_word_completions = if !self.word_completions_enabled {
6299 false
6300 } else if requested_source
6301 == Some(CompletionsMenuSource::Words {
6302 ignore_threshold: true,
6303 })
6304 {
6305 true
6306 } else {
6307 load_provider_completions
6308 && completion_settings.words != WordsCompletionMode::Disabled
6309 && (ignore_word_threshold || {
6310 let words_min_length = completion_settings.words_min_length;
6311 // check whether word has at least `words_min_length` characters
6312 let query_chars = query.iter().flat_map(|q| q.chars());
6313 query_chars.take(words_min_length).count() == words_min_length
6314 })
6315 };
6316
6317 let mut words = if load_word_completions {
6318 cx.background_spawn({
6319 let buffer_snapshot = buffer_snapshot.clone();
6320 async move {
6321 buffer_snapshot.words_in_range(WordsQuery {
6322 fuzzy_contents: None,
6323 range: word_search_range,
6324 skip_digits,
6325 })
6326 }
6327 })
6328 } else {
6329 Task::ready(BTreeMap::default())
6330 };
6331
6332 let snippets = if let Some(provider) = &provider
6333 && provider.show_snippets()
6334 && let Some(project) = self.project()
6335 {
6336 let char_classifier = buffer_snapshot
6337 .char_classifier_at(buffer_position)
6338 .scope_context(Some(CharScopeContext::Completion));
6339 project.update(cx, |project, cx| {
6340 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
6341 })
6342 } else {
6343 Task::ready(Ok(CompletionResponse {
6344 completions: Vec::new(),
6345 display_options: Default::default(),
6346 is_incomplete: false,
6347 }))
6348 };
6349
6350 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
6351
6352 let id = post_inc(&mut self.next_completion_id);
6353 let task = cx.spawn_in(window, async move |editor, cx| {
6354 let Ok(()) = editor.update(cx, |this, _| {
6355 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
6356 }) else {
6357 return;
6358 };
6359
6360 // TODO: Ideally completions from different sources would be selectively re-queried, so
6361 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
6362 let mut completions = Vec::new();
6363 let mut is_incomplete = false;
6364 let mut display_options: Option<CompletionDisplayOptions> = None;
6365 if let Some(provider_responses) = provider_responses.await.log_err()
6366 && !provider_responses.is_empty()
6367 {
6368 for response in provider_responses {
6369 completions.extend(response.completions);
6370 is_incomplete = is_incomplete || response.is_incomplete;
6371 match display_options.as_mut() {
6372 None => {
6373 display_options = Some(response.display_options);
6374 }
6375 Some(options) => options.merge(&response.display_options),
6376 }
6377 }
6378 if completion_settings.words == WordsCompletionMode::Fallback {
6379 words = Task::ready(BTreeMap::default());
6380 }
6381 }
6382 let display_options = display_options.unwrap_or_default();
6383
6384 let mut words = words.await;
6385 if let Some(word_to_exclude) = &word_to_exclude {
6386 words.remove(word_to_exclude);
6387 }
6388 for lsp_completion in &completions {
6389 words.remove(&lsp_completion.new_text);
6390 }
6391 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
6392 replace_range: word_replace_range.clone(),
6393 new_text: word.clone(),
6394 label: CodeLabel::plain(word, None),
6395 match_start: None,
6396 snippet_deduplication_key: None,
6397 icon_path: None,
6398 documentation: None,
6399 source: CompletionSource::BufferWord {
6400 word_range,
6401 resolved: false,
6402 },
6403 insert_text_mode: Some(InsertTextMode::AS_IS),
6404 confirm: None,
6405 }));
6406
6407 completions.extend(
6408 snippets
6409 .await
6410 .into_iter()
6411 .flat_map(|response| response.completions),
6412 );
6413
6414 let menu = if completions.is_empty() {
6415 None
6416 } else {
6417 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
6418 let languages = editor
6419 .workspace
6420 .as_ref()
6421 .and_then(|(workspace, _)| workspace.upgrade())
6422 .map(|workspace| workspace.read(cx).app_state().languages.clone());
6423 let menu = CompletionsMenu::new(
6424 id,
6425 requested_source.unwrap_or(if load_provider_completions {
6426 CompletionsMenuSource::Normal
6427 } else {
6428 CompletionsMenuSource::SnippetsOnly
6429 }),
6430 sort_completions,
6431 show_completion_documentation,
6432 position,
6433 query.clone(),
6434 is_incomplete,
6435 buffer.clone(),
6436 completions.into(),
6437 editor
6438 .context_menu()
6439 .borrow_mut()
6440 .as_ref()
6441 .map(|menu| menu.primary_scroll_handle()),
6442 display_options,
6443 snippet_sort_order,
6444 languages,
6445 language,
6446 cx,
6447 );
6448
6449 let query = if filter_completions { query } else { None };
6450 let matches_task = menu.do_async_filtering(
6451 query.unwrap_or_default(),
6452 buffer_position,
6453 &buffer,
6454 cx,
6455 );
6456 (menu, matches_task)
6457 }) else {
6458 return;
6459 };
6460
6461 let matches = matches_task.await;
6462
6463 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
6464 // Newer menu already set, so exit.
6465 if let Some(CodeContextMenu::Completions(prev_menu)) =
6466 editor.context_menu.borrow().as_ref()
6467 && prev_menu.id > id
6468 {
6469 return;
6470 };
6471
6472 // Only valid to take prev_menu because either the new menu is immediately set
6473 // below, or the menu is hidden.
6474 if let Some(CodeContextMenu::Completions(prev_menu)) =
6475 editor.context_menu.borrow_mut().take()
6476 {
6477 let position_matches =
6478 if prev_menu.initial_position == menu.initial_position {
6479 true
6480 } else {
6481 let snapshot = editor.buffer.read(cx).read(cx);
6482 prev_menu.initial_position.to_offset(&snapshot)
6483 == menu.initial_position.to_offset(&snapshot)
6484 };
6485 if position_matches {
6486 // Preserve markdown cache before `set_filter_results` because it will
6487 // try to populate the documentation cache.
6488 menu.preserve_markdown_cache(prev_menu);
6489 }
6490 };
6491
6492 menu.set_filter_results(matches, provider, window, cx);
6493 }) else {
6494 return;
6495 };
6496
6497 menu.visible().then_some(menu)
6498 };
6499
6500 editor
6501 .update_in(cx, |editor, window, cx| {
6502 if editor.focus_handle.is_focused(window)
6503 && let Some(menu) = menu
6504 {
6505 *editor.context_menu.borrow_mut() =
6506 Some(CodeContextMenu::Completions(menu));
6507
6508 crate::hover_popover::hide_hover(editor, cx);
6509 if editor.show_edit_predictions_in_menu() {
6510 editor.update_visible_edit_prediction(window, cx);
6511 } else {
6512 editor
6513 .discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
6514 }
6515
6516 cx.notify();
6517 return;
6518 }
6519
6520 if editor.completion_tasks.len() <= 1 {
6521 // If there are no more completion tasks and the last menu was empty, we should hide it.
6522 let was_hidden = editor.hide_context_menu(window, cx).is_none();
6523 // If it was already hidden and we don't show edit predictions in the menu,
6524 // we should also show the edit prediction when available.
6525 if was_hidden && editor.show_edit_predictions_in_menu() {
6526 editor.update_visible_edit_prediction(window, cx);
6527 }
6528 }
6529 })
6530 .ok();
6531 });
6532
6533 self.completion_tasks.push((id, task));
6534 }
6535
6536 #[cfg(any(test, feature = "test-support"))]
6537 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
6538 let menu = self.context_menu.borrow();
6539 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
6540 let completions = menu.completions.borrow();
6541 Some(completions.to_vec())
6542 } else {
6543 None
6544 }
6545 }
6546
6547 pub fn with_completions_menu_matching_id<R>(
6548 &self,
6549 id: CompletionId,
6550 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
6551 ) -> R {
6552 let mut context_menu = self.context_menu.borrow_mut();
6553 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
6554 return f(None);
6555 };
6556 if completions_menu.id != id {
6557 return f(None);
6558 }
6559 f(Some(completions_menu))
6560 }
6561
6562 pub fn confirm_completion(
6563 &mut self,
6564 action: &ConfirmCompletion,
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::Complete, window, cx)
6570 }
6571
6572 pub fn confirm_completion_insert(
6573 &mut self,
6574 _: &ConfirmCompletionInsert,
6575 window: &mut Window,
6576 cx: &mut Context<Self>,
6577 ) -> Option<Task<Result<()>>> {
6578 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6579 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
6580 }
6581
6582 pub fn confirm_completion_replace(
6583 &mut self,
6584 _: &ConfirmCompletionReplace,
6585 window: &mut Window,
6586 cx: &mut Context<Self>,
6587 ) -> Option<Task<Result<()>>> {
6588 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6589 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
6590 }
6591
6592 pub fn compose_completion(
6593 &mut self,
6594 action: &ComposeCompletion,
6595 window: &mut Window,
6596 cx: &mut Context<Self>,
6597 ) -> Option<Task<Result<()>>> {
6598 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6599 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
6600 }
6601
6602 fn do_completion(
6603 &mut self,
6604 item_ix: Option<usize>,
6605 intent: CompletionIntent,
6606 window: &mut Window,
6607 cx: &mut Context<Editor>,
6608 ) -> Option<Task<Result<()>>> {
6609 use language::ToOffset as _;
6610
6611 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
6612 else {
6613 return None;
6614 };
6615
6616 let candidate_id = {
6617 let entries = completions_menu.entries.borrow();
6618 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6619 if self.show_edit_predictions_in_menu() {
6620 self.discard_edit_prediction(EditPredictionDiscardReason::Rejected, cx);
6621 }
6622 mat.candidate_id
6623 };
6624
6625 let completion = completions_menu
6626 .completions
6627 .borrow()
6628 .get(candidate_id)?
6629 .clone();
6630 cx.stop_propagation();
6631
6632 let buffer_handle = completions_menu.buffer.clone();
6633 let multibuffer_snapshot = self.buffer.read(cx).snapshot(cx);
6634 let (initial_position, _) =
6635 multibuffer_snapshot.anchor_to_buffer_anchor(completions_menu.initial_position)?;
6636
6637 let CompletionEdit {
6638 new_text,
6639 snippet,
6640 replace_range,
6641 } = process_completion_for_edit(&completion, intent, &buffer_handle, &initial_position, cx);
6642
6643 let buffer = buffer_handle.read(cx).snapshot();
6644 let newest_selection = self.selections.newest_anchor();
6645
6646 let Some(replace_range_multibuffer) =
6647 multibuffer_snapshot.buffer_anchor_range_to_anchor_range(replace_range.clone())
6648 else {
6649 return None;
6650 };
6651
6652 let Some((buffer_snapshot, newest_range_buffer)) =
6653 multibuffer_snapshot.anchor_range_to_buffer_anchor_range(newest_selection.range())
6654 else {
6655 return None;
6656 };
6657
6658 let old_text = buffer
6659 .text_for_range(replace_range.clone())
6660 .collect::<String>();
6661 let lookbehind = newest_range_buffer
6662 .start
6663 .to_offset(buffer_snapshot)
6664 .saturating_sub(replace_range.start.to_offset(&buffer_snapshot));
6665 let lookahead = replace_range
6666 .end
6667 .to_offset(&buffer_snapshot)
6668 .saturating_sub(newest_range_buffer.end.to_offset(&buffer));
6669 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6670 let suffix = &old_text[lookbehind.min(old_text.len())..];
6671
6672 let selections = self
6673 .selections
6674 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6675 let mut ranges = Vec::new();
6676 let mut all_commit_ranges = Vec::new();
6677 let mut linked_edits = LinkedEdits::new();
6678
6679 let text: Arc<str> = new_text.clone().into();
6680 for selection in &selections {
6681 let range = if selection.id == newest_selection.id {
6682 replace_range_multibuffer.clone()
6683 } else {
6684 let mut range = selection.range();
6685
6686 // if prefix is present, don't duplicate it
6687 if multibuffer_snapshot
6688 .contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix)
6689 {
6690 range.start = range.start.saturating_sub_usize(lookbehind);
6691
6692 // if suffix is also present, mimic the newest cursor and replace it
6693 if selection.id != newest_selection.id
6694 && multibuffer_snapshot.contains_str_at(range.end, suffix)
6695 {
6696 range.end += lookahead;
6697 }
6698 }
6699 range.to_anchors(&multibuffer_snapshot)
6700 };
6701
6702 ranges.push(range.clone());
6703
6704 let start_anchor = multibuffer_snapshot.anchor_before(range.start);
6705 let end_anchor = multibuffer_snapshot.anchor_after(range.end);
6706
6707 if let Some((buffer_snapshot_2, anchor_range)) =
6708 multibuffer_snapshot.anchor_range_to_buffer_anchor_range(start_anchor..end_anchor)
6709 && buffer_snapshot_2.remote_id() == buffer_snapshot.remote_id()
6710 {
6711 all_commit_ranges.push(anchor_range.clone());
6712 if !self.linked_edit_ranges.is_empty() {
6713 linked_edits.push(&self, anchor_range, text.clone(), cx);
6714 }
6715 }
6716 }
6717
6718 let common_prefix_len = old_text
6719 .chars()
6720 .zip(new_text.chars())
6721 .take_while(|(a, b)| a == b)
6722 .map(|(a, _)| a.len_utf8())
6723 .sum::<usize>();
6724
6725 cx.emit(EditorEvent::InputHandled {
6726 utf16_range_to_replace: None,
6727 text: new_text[common_prefix_len..].into(),
6728 });
6729
6730 let tx_id = self.transact(window, cx, |editor, window, cx| {
6731 if let Some(mut snippet) = snippet {
6732 snippet.text = new_text.to_string();
6733 let offset_ranges = ranges
6734 .iter()
6735 .map(|range| range.to_offset(&multibuffer_snapshot))
6736 .collect::<Vec<_>>();
6737 editor
6738 .insert_snippet(&offset_ranges, snippet, window, cx)
6739 .log_err();
6740 } else {
6741 editor.buffer.update(cx, |multi_buffer, cx| {
6742 let auto_indent = match completion.insert_text_mode {
6743 Some(InsertTextMode::AS_IS) => None,
6744 _ => editor.autoindent_mode.clone(),
6745 };
6746 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6747 multi_buffer.edit(edits, auto_indent, cx);
6748 });
6749 }
6750 linked_edits.apply(cx);
6751 editor.refresh_edit_prediction(true, false, window, cx);
6752 });
6753 self.invalidate_autoclose_regions(
6754 &self.selections.disjoint_anchors_arc(),
6755 &multibuffer_snapshot,
6756 );
6757
6758 let show_new_completions_on_confirm = completion
6759 .confirm
6760 .as_ref()
6761 .is_some_and(|confirm| confirm(intent, window, cx));
6762 if show_new_completions_on_confirm {
6763 self.open_or_update_completions_menu(None, None, false, window, cx);
6764 }
6765
6766 let provider = self.completion_provider.as_ref()?;
6767
6768 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6769 let command = lsp_store.as_ref().and_then(|lsp_store| {
6770 let CompletionSource::Lsp {
6771 lsp_completion,
6772 server_id,
6773 ..
6774 } = &completion.source
6775 else {
6776 return None;
6777 };
6778 let lsp_command = lsp_completion.command.as_ref()?;
6779 let available_commands = lsp_store
6780 .read(cx)
6781 .lsp_server_capabilities
6782 .get(server_id)
6783 .and_then(|server_capabilities| {
6784 server_capabilities
6785 .execute_command_provider
6786 .as_ref()
6787 .map(|options| options.commands.as_slice())
6788 })?;
6789 if available_commands.contains(&lsp_command.command) {
6790 Some(CodeAction {
6791 server_id: *server_id,
6792 range: language::Anchor::min_min_range_for_buffer(buffer.remote_id()),
6793 lsp_action: LspAction::Command(lsp_command.clone()),
6794 resolved: false,
6795 })
6796 } else {
6797 None
6798 }
6799 });
6800
6801 drop(completion);
6802 let apply_edits = provider.apply_additional_edits_for_completion(
6803 buffer_handle.clone(),
6804 completions_menu.completions.clone(),
6805 candidate_id,
6806 true,
6807 all_commit_ranges,
6808 cx,
6809 );
6810
6811 let editor_settings = EditorSettings::get_global(cx);
6812 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6813 // After the code completion is finished, users often want to know what signatures are needed.
6814 // so we should automatically call signature_help
6815 self.show_signature_help(&ShowSignatureHelp, window, cx);
6816 }
6817
6818 Some(cx.spawn_in(window, async move |editor, cx| {
6819 let additional_edits_tx = apply_edits.await?;
6820
6821 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6822 let title = command.lsp_action.title().to_owned();
6823 let project_transaction = lsp_store
6824 .update(cx, |lsp_store, cx| {
6825 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6826 })
6827 .await
6828 .context("applying post-completion command")?;
6829 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6830 Self::open_project_transaction(
6831 &editor,
6832 workspace.downgrade(),
6833 project_transaction,
6834 title,
6835 cx,
6836 )
6837 .await?;
6838 }
6839 }
6840
6841 if let Some(tx_id) = tx_id
6842 && let Some(additional_edits_tx) = additional_edits_tx
6843 {
6844 editor
6845 .update(cx, |editor, cx| {
6846 editor.buffer.update(cx, |buffer, cx| {
6847 buffer.merge_transactions(additional_edits_tx.id, tx_id, cx)
6848 });
6849 })
6850 .context("merge transactions")?;
6851 }
6852
6853 Ok(())
6854 }))
6855 }
6856
6857 pub fn toggle_code_actions(
6858 &mut self,
6859 action: &ToggleCodeActions,
6860 window: &mut Window,
6861 cx: &mut Context<Self>,
6862 ) {
6863 let quick_launch = action.quick_launch;
6864 let mut context_menu = self.context_menu.borrow_mut();
6865 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6866 if code_actions.deployed_from == action.deployed_from {
6867 // Toggle if we're selecting the same one
6868 *context_menu = None;
6869 cx.notify();
6870 return;
6871 } else {
6872 // Otherwise, clear it and start a new one
6873 *context_menu = None;
6874 cx.notify();
6875 }
6876 }
6877 drop(context_menu);
6878 let snapshot = self.snapshot(window, cx);
6879 let deployed_from = action.deployed_from.clone();
6880 let action = action.clone();
6881 self.completion_tasks.clear();
6882 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
6883
6884 let multibuffer_point = match &action.deployed_from {
6885 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6886 DisplayPoint::new(*row, 0).to_point(&snapshot)
6887 }
6888 _ => self
6889 .selections
6890 .newest::<Point>(&snapshot.display_snapshot)
6891 .head(),
6892 };
6893 let Some((buffer, buffer_row)) = snapshot
6894 .buffer_snapshot()
6895 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6896 .and_then(|(buffer_snapshot, range)| {
6897 self.buffer()
6898 .read(cx)
6899 .buffer(buffer_snapshot.remote_id())
6900 .map(|buffer| (buffer, range.start.row))
6901 })
6902 else {
6903 return;
6904 };
6905 let buffer_id = buffer.read(cx).remote_id();
6906 let tasks = self
6907 .runnables
6908 .runnables((buffer_id, buffer_row))
6909 .map(|t| Arc::new(t.to_owned()));
6910
6911 if !self.focus_handle.is_focused(window) {
6912 return;
6913 }
6914 let project = self.project.clone();
6915
6916 let code_actions_task = match deployed_from {
6917 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6918 _ => self.code_actions(buffer_row, window, cx),
6919 };
6920
6921 let runnable_task = match deployed_from {
6922 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6923 _ => {
6924 let mut task_context_task = Task::ready(None);
6925 if let Some(tasks) = &tasks
6926 && let Some(project) = project
6927 {
6928 task_context_task =
6929 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6930 }
6931
6932 cx.spawn_in(window, {
6933 let buffer = buffer.clone();
6934 async move |editor, cx| {
6935 let task_context = task_context_task.await;
6936
6937 let resolved_tasks =
6938 tasks
6939 .zip(task_context.clone())
6940 .map(|(tasks, task_context)| ResolvedTasks {
6941 templates: tasks.resolve(&task_context).collect(),
6942 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6943 multibuffer_point.row,
6944 tasks.column,
6945 )),
6946 });
6947 let debug_scenarios = editor
6948 .update(cx, |editor, cx| {
6949 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6950 })?
6951 .await;
6952 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6953 }
6954 })
6955 }
6956 };
6957
6958 cx.spawn_in(window, async move |editor, cx| {
6959 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6960 let code_actions = code_actions_task.await;
6961 let spawn_straight_away = quick_launch
6962 && resolved_tasks
6963 .as_ref()
6964 .is_some_and(|tasks| tasks.templates.len() == 1)
6965 && code_actions
6966 .as_ref()
6967 .is_none_or(|actions| actions.is_empty())
6968 && debug_scenarios.is_empty();
6969
6970 editor.update_in(cx, |editor, window, cx| {
6971 crate::hover_popover::hide_hover(editor, cx);
6972 let actions = CodeActionContents::new(
6973 resolved_tasks,
6974 code_actions,
6975 debug_scenarios,
6976 task_context.unwrap_or_default(),
6977 );
6978
6979 // Don't show the menu if there are no actions available
6980 if actions.is_empty() {
6981 cx.notify();
6982 return Task::ready(Ok(()));
6983 }
6984
6985 *editor.context_menu.borrow_mut() =
6986 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6987 buffer,
6988 actions,
6989 selected_item: Default::default(),
6990 scroll_handle: UniformListScrollHandle::default(),
6991 deployed_from,
6992 }));
6993 cx.notify();
6994 if spawn_straight_away
6995 && let Some(task) = editor.confirm_code_action(
6996 &ConfirmCodeAction { item_ix: Some(0) },
6997 window,
6998 cx,
6999 )
7000 {
7001 return task;
7002 }
7003
7004 Task::ready(Ok(()))
7005 })
7006 })
7007 .detach_and_log_err(cx);
7008 }
7009
7010 fn debug_scenarios(
7011 &mut self,
7012 resolved_tasks: &Option<ResolvedTasks>,
7013 buffer: &Entity<Buffer>,
7014 cx: &mut App,
7015 ) -> Task<Vec<task::DebugScenario>> {
7016 maybe!({
7017 let project = self.project()?;
7018 let dap_store = project.read(cx).dap_store();
7019 let mut scenarios = vec![];
7020 let resolved_tasks = resolved_tasks.as_ref()?;
7021 let buffer = buffer.read(cx);
7022 let language = buffer.language()?;
7023 let debug_adapter = LanguageSettings::for_buffer(&buffer, cx)
7024 .debuggers
7025 .first()
7026 .map(SharedString::from)
7027 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
7028
7029 dap_store.update(cx, |dap_store, cx| {
7030 for (_, task) in &resolved_tasks.templates {
7031 let maybe_scenario = dap_store.debug_scenario_for_build_task(
7032 task.original_task().clone(),
7033 debug_adapter.clone().into(),
7034 task.display_label().to_owned().into(),
7035 cx,
7036 );
7037 scenarios.push(maybe_scenario);
7038 }
7039 });
7040 Some(cx.background_spawn(async move {
7041 futures::future::join_all(scenarios)
7042 .await
7043 .into_iter()
7044 .flatten()
7045 .collect::<Vec<_>>()
7046 }))
7047 })
7048 .unwrap_or_else(|| Task::ready(vec![]))
7049 }
7050
7051 fn code_actions(
7052 &mut self,
7053 buffer_row: u32,
7054 window: &mut Window,
7055 cx: &mut Context<Self>,
7056 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
7057 let mut task = self.code_actions_task.take();
7058 cx.spawn_in(window, async move |editor, cx| {
7059 while let Some(prev_task) = task {
7060 prev_task.await.log_err();
7061 task = editor
7062 .update(cx, |this, _| this.code_actions_task.take())
7063 .ok()?;
7064 }
7065
7066 editor
7067 .update(cx, |editor, cx| {
7068 editor
7069 .available_code_actions
7070 .clone()
7071 .and_then(|(location, code_actions)| {
7072 let snapshot = location.buffer.read(cx).snapshot();
7073 let point_range = location.range.to_point(&snapshot);
7074 let point_range = point_range.start.row..=point_range.end.row;
7075 if point_range.contains(&buffer_row) {
7076 Some(code_actions)
7077 } else {
7078 None
7079 }
7080 })
7081 })
7082 .ok()
7083 .flatten()
7084 })
7085 }
7086
7087 pub fn confirm_code_action(
7088 &mut self,
7089 action: &ConfirmCodeAction,
7090 window: &mut Window,
7091 cx: &mut Context<Self>,
7092 ) -> Option<Task<Result<()>>> {
7093 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
7094
7095 let actions_menu =
7096 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
7097 menu
7098 } else {
7099 return None;
7100 };
7101
7102 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
7103 let action = actions_menu.actions.get(action_ix)?;
7104 let title = action.label();
7105 let buffer = actions_menu.buffer;
7106 let workspace = self.workspace()?;
7107
7108 match action {
7109 CodeActionsItem::Task(task_source_kind, resolved_task) => {
7110 workspace.update(cx, |workspace, cx| {
7111 workspace.schedule_resolved_task(
7112 task_source_kind,
7113 resolved_task,
7114 false,
7115 window,
7116 cx,
7117 );
7118
7119 Some(Task::ready(Ok(())))
7120 })
7121 }
7122 CodeActionsItem::CodeAction { action, provider } => {
7123 let apply_code_action =
7124 provider.apply_code_action(buffer, action, true, window, cx);
7125 let workspace = workspace.downgrade();
7126 Some(cx.spawn_in(window, async move |editor, cx| {
7127 let project_transaction = apply_code_action.await?;
7128 Self::open_project_transaction(
7129 &editor,
7130 workspace,
7131 project_transaction,
7132 title,
7133 cx,
7134 )
7135 .await
7136 }))
7137 }
7138 CodeActionsItem::DebugScenario(scenario) => {
7139 let context = actions_menu.actions.context.into();
7140
7141 workspace.update(cx, |workspace, cx| {
7142 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
7143 workspace.start_debug_session(
7144 scenario,
7145 context,
7146 Some(buffer),
7147 None,
7148 window,
7149 cx,
7150 );
7151 });
7152 Some(Task::ready(Ok(())))
7153 }
7154 }
7155 }
7156
7157 fn open_transaction_for_hidden_buffers(
7158 workspace: Entity<Workspace>,
7159 transaction: ProjectTransaction,
7160 title: String,
7161 window: &mut Window,
7162 cx: &mut Context<Self>,
7163 ) {
7164 if transaction.0.is_empty() {
7165 return;
7166 }
7167
7168 let edited_buffers_already_open = {
7169 let other_editors: Vec<Entity<Editor>> = workspace
7170 .read(cx)
7171 .panes()
7172 .iter()
7173 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
7174 .filter(|editor| editor.entity_id() != cx.entity_id())
7175 .collect();
7176
7177 transaction.0.keys().all(|buffer| {
7178 other_editors.iter().any(|editor| {
7179 let multi_buffer = editor.read(cx).buffer();
7180 multi_buffer.read(cx).is_singleton()
7181 && multi_buffer
7182 .read(cx)
7183 .as_singleton()
7184 .map_or(false, |singleton| {
7185 singleton.entity_id() == buffer.entity_id()
7186 })
7187 })
7188 })
7189 };
7190 if !edited_buffers_already_open {
7191 let workspace = workspace.downgrade();
7192 cx.defer_in(window, move |_, window, cx| {
7193 cx.spawn_in(window, async move |editor, cx| {
7194 Self::open_project_transaction(&editor, workspace, transaction, title, cx)
7195 .await
7196 .ok()
7197 })
7198 .detach();
7199 });
7200 }
7201 }
7202
7203 pub async fn open_project_transaction(
7204 editor: &WeakEntity<Editor>,
7205 workspace: WeakEntity<Workspace>,
7206 transaction: ProjectTransaction,
7207 title: String,
7208 cx: &mut AsyncWindowContext,
7209 ) -> Result<()> {
7210 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
7211 cx.update(|_, cx| {
7212 entries.sort_unstable_by_key(|(buffer, _)| {
7213 buffer.read(cx).file().map(|f| f.path().clone())
7214 });
7215 })?;
7216 if entries.is_empty() {
7217 return Ok(());
7218 }
7219
7220 // If the project transaction's edits are all contained within this editor, then
7221 // avoid opening a new editor to display them.
7222
7223 if let [(buffer, transaction)] = &*entries {
7224 let cursor_excerpt = editor.update(cx, |editor, cx| {
7225 let snapshot = editor.buffer().read(cx).snapshot(cx);
7226 let head = editor.selections.newest_anchor().head();
7227 let (buffer_snapshot, excerpt_range) = snapshot.excerpt_containing(head..head)?;
7228 if buffer_snapshot.remote_id() != buffer.read(cx).remote_id() {
7229 return None;
7230 }
7231 Some(excerpt_range)
7232 })?;
7233
7234 if let Some(excerpt_range) = cursor_excerpt {
7235 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
7236 let excerpt_range = excerpt_range.context.to_offset(buffer);
7237 buffer
7238 .edited_ranges_for_transaction::<usize>(transaction)
7239 .all(|range| {
7240 excerpt_range.start <= range.start && excerpt_range.end >= range.end
7241 })
7242 });
7243
7244 if all_edits_within_excerpt {
7245 return Ok(());
7246 }
7247 }
7248 }
7249
7250 let mut ranges_to_highlight = Vec::new();
7251 let excerpt_buffer = cx.new(|cx| {
7252 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
7253 for (buffer_handle, transaction) in &entries {
7254 let edited_ranges = buffer_handle
7255 .read(cx)
7256 .edited_ranges_for_transaction::<Point>(transaction)
7257 .collect::<Vec<_>>();
7258 multibuffer.set_excerpts_for_path(
7259 PathKey::for_buffer(buffer_handle, cx),
7260 buffer_handle.clone(),
7261 edited_ranges.clone(),
7262 multibuffer_context_lines(cx),
7263 cx,
7264 );
7265 let snapshot = multibuffer.snapshot(cx);
7266 let buffer_snapshot = buffer_handle.read(cx).snapshot();
7267 ranges_to_highlight.extend(edited_ranges.into_iter().filter_map(|range| {
7268 let text_range = buffer_snapshot.anchor_range_inside(range);
7269 let start = snapshot.anchor_in_buffer(text_range.start)?;
7270 let end = snapshot.anchor_in_buffer(text_range.end)?;
7271 Some(start..end)
7272 }));
7273 }
7274 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
7275 multibuffer
7276 });
7277
7278 workspace.update_in(cx, |workspace, window, cx| {
7279 let project = workspace.project().clone();
7280 let editor =
7281 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
7282 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
7283 editor.update(cx, |editor, cx| {
7284 editor.highlight_background(
7285 HighlightKey::Editor,
7286 &ranges_to_highlight,
7287 |_, theme| theme.colors().editor_highlighted_line_background,
7288 cx,
7289 );
7290 });
7291 })?;
7292
7293 Ok(())
7294 }
7295
7296 pub fn clear_code_action_providers(&mut self) {
7297 self.code_action_providers.clear();
7298 self.available_code_actions.take();
7299 }
7300
7301 pub fn add_code_action_provider(
7302 &mut self,
7303 provider: Rc<dyn CodeActionProvider>,
7304 window: &mut Window,
7305 cx: &mut Context<Self>,
7306 ) {
7307 if self
7308 .code_action_providers
7309 .iter()
7310 .any(|existing_provider| existing_provider.id() == provider.id())
7311 {
7312 return;
7313 }
7314
7315 self.code_action_providers.push(provider);
7316 self.refresh_code_actions(window, cx);
7317 }
7318
7319 pub fn remove_code_action_provider(
7320 &mut self,
7321 id: Arc<str>,
7322 window: &mut Window,
7323 cx: &mut Context<Self>,
7324 ) {
7325 self.code_action_providers
7326 .retain(|provider| provider.id() != id);
7327 self.refresh_code_actions(window, cx);
7328 }
7329
7330 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
7331 !self.code_action_providers.is_empty()
7332 && EditorSettings::get_global(cx).toolbar.code_actions
7333 }
7334
7335 pub fn has_available_code_actions(&self) -> bool {
7336 self.available_code_actions
7337 .as_ref()
7338 .is_some_and(|(_, actions)| !actions.is_empty())
7339 }
7340
7341 fn render_inline_code_actions(
7342 &self,
7343 icon_size: ui::IconSize,
7344 display_row: DisplayRow,
7345 is_active: bool,
7346 cx: &mut Context<Self>,
7347 ) -> AnyElement {
7348 let show_tooltip = !self.context_menu_visible();
7349 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
7350 .icon_size(icon_size)
7351 .shape(ui::IconButtonShape::Square)
7352 .icon_color(ui::Color::Hidden)
7353 .toggle_state(is_active)
7354 .when(show_tooltip, |this| {
7355 this.tooltip({
7356 let focus_handle = self.focus_handle.clone();
7357 move |_window, cx| {
7358 Tooltip::for_action_in(
7359 "Toggle Code Actions",
7360 &ToggleCodeActions {
7361 deployed_from: None,
7362 quick_launch: false,
7363 },
7364 &focus_handle,
7365 cx,
7366 )
7367 }
7368 })
7369 })
7370 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
7371 window.focus(&editor.focus_handle(cx), cx);
7372 editor.toggle_code_actions(
7373 &crate::actions::ToggleCodeActions {
7374 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
7375 display_row,
7376 )),
7377 quick_launch: false,
7378 },
7379 window,
7380 cx,
7381 );
7382 }))
7383 .into_any_element()
7384 }
7385
7386 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
7387 &self.context_menu
7388 }
7389
7390 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7391 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
7392 cx.background_executor()
7393 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
7394 .await;
7395
7396 let (start_buffer, start, _, end, _newest_selection) = this
7397 .update(cx, |this, cx| {
7398 let newest_selection = this.selections.newest_anchor().clone();
7399 if newest_selection.head().diff_base_anchor().is_some() {
7400 return None;
7401 }
7402 let display_snapshot = this.display_snapshot(cx);
7403 let newest_selection_adjusted =
7404 this.selections.newest_adjusted(&display_snapshot);
7405 let buffer = this.buffer.read(cx);
7406
7407 let (start_buffer, start) =
7408 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
7409 let (end_buffer, end) =
7410 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
7411
7412 Some((start_buffer, start, end_buffer, end, newest_selection))
7413 })?
7414 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
7415 .context(
7416 "Expected selection to lie in a single buffer when refreshing code actions",
7417 )?;
7418 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
7419 let providers = this.code_action_providers.clone();
7420 let tasks = this
7421 .code_action_providers
7422 .iter()
7423 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
7424 .collect::<Vec<_>>();
7425 (providers, tasks)
7426 })?;
7427
7428 let mut actions = Vec::new();
7429 for (provider, provider_actions) in
7430 providers.into_iter().zip(future::join_all(tasks).await)
7431 {
7432 if let Some(provider_actions) = provider_actions.log_err() {
7433 actions.extend(provider_actions.into_iter().map(|action| {
7434 AvailableCodeAction {
7435 action,
7436 provider: provider.clone(),
7437 }
7438 }));
7439 }
7440 }
7441
7442 this.update(cx, |this, cx| {
7443 this.available_code_actions = if actions.is_empty() {
7444 None
7445 } else {
7446 Some((
7447 Location {
7448 buffer: start_buffer,
7449 range: start..end,
7450 },
7451 actions.into(),
7452 ))
7453 };
7454 cx.notify();
7455 })
7456 }));
7457 }
7458
7459 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7460 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
7461 self.show_git_blame_inline = false;
7462
7463 self.show_git_blame_inline_delay_task =
7464 Some(cx.spawn_in(window, async move |this, cx| {
7465 cx.background_executor().timer(delay).await;
7466
7467 this.update(cx, |this, cx| {
7468 this.show_git_blame_inline = true;
7469 cx.notify();
7470 })
7471 .log_err();
7472 }));
7473 }
7474 }
7475
7476 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
7477 let snapshot = self.snapshot(window, cx);
7478 let cursor = self
7479 .selections
7480 .newest::<Point>(&snapshot.display_snapshot)
7481 .head();
7482 let Some((buffer, point)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor) else {
7483 return;
7484 };
7485
7486 if self.blame.is_none() {
7487 self.start_git_blame(true, window, cx);
7488 }
7489 let Some(blame) = self.blame.as_ref() else {
7490 return;
7491 };
7492
7493 let row_info = RowInfo {
7494 buffer_id: Some(buffer.remote_id()),
7495 buffer_row: Some(point.row),
7496 ..Default::default()
7497 };
7498 let Some((buffer, blame_entry)) = blame
7499 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
7500 .flatten()
7501 else {
7502 return;
7503 };
7504
7505 let anchor = self.selections.newest_anchor().head();
7506 let position = self.to_pixel_point(anchor, &snapshot, window, cx);
7507 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
7508 self.show_blame_popover(
7509 buffer,
7510 &blame_entry,
7511 position + last_bounds.origin,
7512 true,
7513 cx,
7514 );
7515 };
7516 }
7517
7518 fn show_blame_popover(
7519 &mut self,
7520 buffer: BufferId,
7521 blame_entry: &BlameEntry,
7522 position: gpui::Point<Pixels>,
7523 ignore_timeout: bool,
7524 cx: &mut Context<Self>,
7525 ) {
7526 if let Some(state) = &mut self.inline_blame_popover {
7527 state.hide_task.take();
7528 } else {
7529 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
7530 let blame_entry = blame_entry.clone();
7531 let show_task = cx.spawn(async move |editor, cx| {
7532 if !ignore_timeout {
7533 cx.background_executor()
7534 .timer(std::time::Duration::from_millis(blame_popover_delay))
7535 .await;
7536 }
7537 editor
7538 .update(cx, |editor, cx| {
7539 editor.inline_blame_popover_show_task.take();
7540 let Some(blame) = editor.blame.as_ref() else {
7541 return;
7542 };
7543 let blame = blame.read(cx);
7544 let details = blame.details_for_entry(buffer, &blame_entry);
7545 let markdown = cx.new(|cx| {
7546 Markdown::new(
7547 details
7548 .as_ref()
7549 .map(|message| message.message.clone())
7550 .unwrap_or_default(),
7551 None,
7552 None,
7553 cx,
7554 )
7555 });
7556 editor.inline_blame_popover = Some(InlineBlamePopover {
7557 position,
7558 hide_task: None,
7559 popover_bounds: None,
7560 popover_state: InlineBlamePopoverState {
7561 scroll_handle: ScrollHandle::new(),
7562 commit_message: details,
7563 markdown,
7564 },
7565 keyboard_grace: ignore_timeout,
7566 });
7567 cx.notify();
7568 })
7569 .ok();
7570 });
7571 self.inline_blame_popover_show_task = Some(show_task);
7572 }
7573 }
7574
7575 pub fn has_mouse_context_menu(&self) -> bool {
7576 self.mouse_context_menu.is_some()
7577 }
7578
7579 pub fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
7580 self.inline_blame_popover_show_task.take();
7581 if let Some(state) = &mut self.inline_blame_popover {
7582 let hide_task = cx.spawn(async move |editor, cx| {
7583 if !ignore_timeout {
7584 cx.background_executor()
7585 .timer(std::time::Duration::from_millis(100))
7586 .await;
7587 }
7588 editor
7589 .update(cx, |editor, cx| {
7590 editor.inline_blame_popover.take();
7591 cx.notify();
7592 })
7593 .ok();
7594 });
7595 state.hide_task = Some(hide_task);
7596 true
7597 } else {
7598 false
7599 }
7600 }
7601
7602 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
7603 if self.pending_rename.is_some() {
7604 return None;
7605 }
7606
7607 let provider = self.semantics_provider.clone()?;
7608 let buffer = self.buffer.read(cx);
7609 let newest_selection = self.selections.newest_anchor().clone();
7610 let cursor_position = newest_selection.head();
7611 let (cursor_buffer, cursor_buffer_position) =
7612 buffer.text_anchor_for_position(cursor_position, cx)?;
7613 let (tail_buffer, tail_buffer_position) =
7614 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
7615 if cursor_buffer != tail_buffer {
7616 return None;
7617 }
7618
7619 let snapshot = cursor_buffer.read(cx).snapshot();
7620 let word_ranges = cx.background_spawn(async move {
7621 // this might look odd to put on the background thread, but
7622 // `surrounding_word` can be quite expensive as it calls into
7623 // tree-sitter language scopes
7624 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
7625 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
7626 (start_word_range, end_word_range)
7627 });
7628
7629 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
7630 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
7631 let (start_word_range, end_word_range) = word_ranges.await;
7632 if start_word_range != end_word_range {
7633 this.update(cx, |this, cx| {
7634 this.document_highlights_task.take();
7635 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
7636 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
7637 })
7638 .ok();
7639 return;
7640 }
7641 cx.background_executor()
7642 .timer(Duration::from_millis(debounce))
7643 .await;
7644
7645 let highlights = if let Some(highlights) = cx.update(|cx| {
7646 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
7647 }) {
7648 highlights.await.log_err()
7649 } else {
7650 None
7651 };
7652
7653 if let Some(highlights) = highlights {
7654 this.update(cx, |this, cx| {
7655 if this.pending_rename.is_some() {
7656 return;
7657 }
7658
7659 let buffer = this.buffer.read(cx);
7660 if buffer
7661 .text_anchor_for_position(cursor_position, cx)
7662 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
7663 {
7664 return;
7665 }
7666
7667 let mut write_ranges = Vec::new();
7668 let mut read_ranges = Vec::new();
7669 let multibuffer_snapshot = buffer.snapshot(cx);
7670 for highlight in highlights {
7671 for range in
7672 multibuffer_snapshot.buffer_range_to_excerpt_ranges(highlight.range)
7673 {
7674 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7675 write_ranges.push(range);
7676 } else {
7677 read_ranges.push(range);
7678 }
7679 }
7680 }
7681
7682 this.highlight_background(
7683 HighlightKey::DocumentHighlightRead,
7684 &read_ranges,
7685 |_, theme| theme.colors().editor_document_highlight_read_background,
7686 cx,
7687 );
7688 this.highlight_background(
7689 HighlightKey::DocumentHighlightWrite,
7690 &write_ranges,
7691 |_, theme| theme.colors().editor_document_highlight_write_background,
7692 cx,
7693 );
7694 cx.notify();
7695 })
7696 .log_err();
7697 }
7698 }));
7699 None
7700 }
7701
7702 fn prepare_highlight_query_from_selection(
7703 &mut self,
7704 snapshot: &DisplaySnapshot,
7705 cx: &mut Context<Editor>,
7706 ) -> Option<(String, Range<Anchor>)> {
7707 if matches!(self.mode, EditorMode::SingleLine) {
7708 return None;
7709 }
7710 if !self.use_selection_highlight || !EditorSettings::get_global(cx).selection_highlight {
7711 return None;
7712 }
7713 if self.selections.count() != 1 || self.selections.line_mode() {
7714 return None;
7715 }
7716 let selection = self.selections.newest::<Point>(&snapshot);
7717 // If the selection spans multiple rows OR it is empty
7718 if selection.start.row != selection.end.row
7719 || selection.start.column == selection.end.column
7720 {
7721 return None;
7722 }
7723 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7724 let query = snapshot
7725 .buffer_snapshot()
7726 .text_for_range(selection_anchor_range.clone())
7727 .collect::<String>();
7728 if query.trim().is_empty() {
7729 return None;
7730 }
7731 Some((query, selection_anchor_range))
7732 }
7733
7734 #[ztracing::instrument(skip_all)]
7735 fn update_selection_occurrence_highlights(
7736 &mut self,
7737 multi_buffer_snapshot: MultiBufferSnapshot,
7738 query_text: String,
7739 query_range: Range<Anchor>,
7740 multi_buffer_range_to_query: Range<Point>,
7741 use_debounce: bool,
7742 window: &mut Window,
7743 cx: &mut Context<Editor>,
7744 ) -> Task<()> {
7745 cx.spawn_in(window, async move |editor, cx| {
7746 if use_debounce {
7747 cx.background_executor()
7748 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7749 .await;
7750 }
7751 let match_task = cx.background_spawn(async move {
7752 let buffer_ranges = multi_buffer_snapshot
7753 .range_to_buffer_ranges(
7754 multi_buffer_range_to_query.start..multi_buffer_range_to_query.end,
7755 )
7756 .into_iter()
7757 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7758 let mut match_ranges = Vec::new();
7759 let Ok(regex) = project::search::SearchQuery::text(
7760 query_text,
7761 false,
7762 false,
7763 false,
7764 Default::default(),
7765 Default::default(),
7766 false,
7767 None,
7768 ) else {
7769 return Vec::default();
7770 };
7771 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7772 for (buffer_snapshot, search_range, _) in buffer_ranges {
7773 match_ranges.extend(
7774 regex
7775 .search(
7776 &buffer_snapshot,
7777 Some(search_range.start.0..search_range.end.0),
7778 )
7779 .await
7780 .into_iter()
7781 .filter_map(|match_range| {
7782 let match_start = buffer_snapshot
7783 .anchor_after(search_range.start + match_range.start);
7784 let match_end = buffer_snapshot
7785 .anchor_before(search_range.start + match_range.end);
7786 {
7787 let range = multi_buffer_snapshot
7788 .anchor_in_buffer(match_start)?
7789 ..multi_buffer_snapshot.anchor_in_buffer(match_end)?;
7790 Some(range).filter(|match_anchor_range| {
7791 match_anchor_range != &query_range
7792 })
7793 }
7794 }),
7795 );
7796 }
7797 match_ranges
7798 });
7799 let match_ranges = match_task.await;
7800 editor
7801 .update_in(cx, |editor, _, cx| {
7802 if use_debounce {
7803 editor.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
7804 editor.debounced_selection_highlight_complete = true;
7805 } else if editor.debounced_selection_highlight_complete {
7806 return;
7807 }
7808 if !match_ranges.is_empty() {
7809 editor.highlight_background(
7810 HighlightKey::SelectedTextHighlight,
7811 &match_ranges,
7812 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7813 cx,
7814 )
7815 }
7816 })
7817 .log_err();
7818 })
7819 }
7820
7821 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7822 struct NewlineFold;
7823 let type_id = std::any::TypeId::of::<NewlineFold>();
7824 if !self.mode.is_single_line() {
7825 return;
7826 }
7827 let snapshot = self.snapshot(window, cx);
7828 if snapshot.buffer_snapshot().max_point().row == 0 {
7829 return;
7830 }
7831 let task = cx.background_spawn(async move {
7832 let new_newlines = snapshot
7833 .buffer_chars_at(MultiBufferOffset(0))
7834 .filter_map(|(c, i)| {
7835 if c == '\n' {
7836 Some(
7837 snapshot.buffer_snapshot().anchor_after(i)
7838 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7839 )
7840 } else {
7841 None
7842 }
7843 })
7844 .collect::<Vec<_>>();
7845 let existing_newlines = snapshot
7846 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7847 .filter_map(|fold| {
7848 if fold.placeholder.type_tag == Some(type_id) {
7849 Some(fold.range.start..fold.range.end)
7850 } else {
7851 None
7852 }
7853 })
7854 .collect::<Vec<_>>();
7855
7856 (new_newlines, existing_newlines)
7857 });
7858 self.folding_newlines = cx.spawn(async move |this, cx| {
7859 let (new_newlines, existing_newlines) = task.await;
7860 if new_newlines == existing_newlines {
7861 return;
7862 }
7863 let placeholder = FoldPlaceholder {
7864 render: Arc::new(move |_, _, cx| {
7865 div()
7866 .bg(cx.theme().status().hint_background)
7867 .border_b_1()
7868 .size_full()
7869 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7870 .border_color(cx.theme().status().hint)
7871 .child("\\n")
7872 .into_any()
7873 }),
7874 constrain_width: false,
7875 merge_adjacent: false,
7876 type_tag: Some(type_id),
7877 collapsed_text: None,
7878 };
7879 let creases = new_newlines
7880 .into_iter()
7881 .map(|range| Crease::simple(range, placeholder.clone()))
7882 .collect();
7883 this.update(cx, |this, cx| {
7884 this.display_map.update(cx, |display_map, cx| {
7885 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7886 display_map.fold(creases, cx);
7887 });
7888 })
7889 .ok();
7890 });
7891 }
7892
7893 #[ztracing::instrument(skip_all)]
7894 fn refresh_outline_symbols_at_cursor(&mut self, cx: &mut Context<Editor>) {
7895 if !self.lsp_data_enabled() {
7896 return;
7897 }
7898 let cursor = self.selections.newest_anchor().head();
7899 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7900
7901 if self.uses_lsp_document_symbols(cursor, &multi_buffer_snapshot, cx) {
7902 self.outline_symbols_at_cursor =
7903 self.lsp_symbols_at_cursor(cursor, &multi_buffer_snapshot, cx);
7904 cx.emit(EditorEvent::OutlineSymbolsChanged);
7905 cx.notify();
7906 } else {
7907 let syntax = cx.theme().syntax().clone();
7908 let background_task = cx.background_spawn(async move {
7909 multi_buffer_snapshot.symbols_containing(cursor, Some(&syntax))
7910 });
7911 self.refresh_outline_symbols_at_cursor_at_cursor_task =
7912 cx.spawn(async move |this, cx| {
7913 let symbols = background_task.await;
7914 this.update(cx, |this, cx| {
7915 this.outline_symbols_at_cursor = symbols;
7916 cx.emit(EditorEvent::OutlineSymbolsChanged);
7917 cx.notify();
7918 })
7919 .ok();
7920 });
7921 }
7922 }
7923
7924 #[ztracing::instrument(skip_all)]
7925 fn refresh_selected_text_highlights(
7926 &mut self,
7927 snapshot: &DisplaySnapshot,
7928 on_buffer_edit: bool,
7929 window: &mut Window,
7930 cx: &mut Context<Editor>,
7931 ) {
7932 let Some((query_text, query_range)) =
7933 self.prepare_highlight_query_from_selection(snapshot, cx)
7934 else {
7935 self.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
7936 self.quick_selection_highlight_task.take();
7937 self.debounced_selection_highlight_task.take();
7938 self.debounced_selection_highlight_complete = false;
7939 return;
7940 };
7941 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7942 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7943 let query_changed = self
7944 .quick_selection_highlight_task
7945 .as_ref()
7946 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range);
7947 if query_changed {
7948 self.debounced_selection_highlight_complete = false;
7949 }
7950 if on_buffer_edit || query_changed {
7951 self.quick_selection_highlight_task = Some((
7952 query_range.clone(),
7953 self.update_selection_occurrence_highlights(
7954 snapshot.buffer.clone(),
7955 query_text.clone(),
7956 query_range.clone(),
7957 self.multi_buffer_visible_range(&display_snapshot, cx),
7958 false,
7959 window,
7960 cx,
7961 ),
7962 ));
7963 }
7964 if on_buffer_edit
7965 || self
7966 .debounced_selection_highlight_task
7967 .as_ref()
7968 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7969 {
7970 let multi_buffer_start = multi_buffer_snapshot
7971 .anchor_before(MultiBufferOffset(0))
7972 .to_point(&multi_buffer_snapshot);
7973 let multi_buffer_end = multi_buffer_snapshot
7974 .anchor_after(multi_buffer_snapshot.len())
7975 .to_point(&multi_buffer_snapshot);
7976 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7977 self.debounced_selection_highlight_task = Some((
7978 query_range.clone(),
7979 self.update_selection_occurrence_highlights(
7980 snapshot.buffer.clone(),
7981 query_text,
7982 query_range,
7983 multi_buffer_full_range,
7984 true,
7985 window,
7986 cx,
7987 ),
7988 ));
7989 }
7990 }
7991
7992 pub fn multi_buffer_visible_range(
7993 &self,
7994 display_snapshot: &DisplaySnapshot,
7995 cx: &App,
7996 ) -> Range<Point> {
7997 let visible_start = self
7998 .scroll_manager
7999 .native_anchor(display_snapshot, cx)
8000 .anchor
8001 .to_point(display_snapshot.buffer_snapshot())
8002 .to_display_point(display_snapshot);
8003
8004 let mut target_end = visible_start;
8005 *target_end.row_mut() += self.visible_line_count().unwrap_or(0.).ceil() as u32;
8006
8007 visible_start.to_point(display_snapshot)
8008 ..display_snapshot
8009 .clip_point(target_end, Bias::Right)
8010 .to_point(display_snapshot)
8011 }
8012
8013 pub fn refresh_edit_prediction(
8014 &mut self,
8015 debounce: bool,
8016 user_requested: bool,
8017 window: &mut Window,
8018 cx: &mut Context<Self>,
8019 ) -> Option<()> {
8020 if self.leader_id.is_some() {
8021 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8022 return None;
8023 }
8024
8025 let cursor = self.selections.newest_anchor().head();
8026 let (buffer, cursor_buffer_position) =
8027 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8028
8029 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
8030 return None;
8031 }
8032
8033 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
8034 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8035 return None;
8036 }
8037
8038 self.update_visible_edit_prediction(window, cx);
8039
8040 if !user_requested
8041 && (!self.should_show_edit_predictions()
8042 || !self.is_focused(window)
8043 || buffer.read(cx).is_empty())
8044 {
8045 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8046 return None;
8047 }
8048
8049 self.edit_prediction_provider()?
8050 .refresh(buffer, cursor_buffer_position, debounce, cx);
8051 Some(())
8052 }
8053
8054 fn show_edit_predictions_in_menu(&self) -> bool {
8055 match self.edit_prediction_settings {
8056 EditPredictionSettings::Disabled => false,
8057 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
8058 }
8059 }
8060
8061 pub fn edit_predictions_enabled(&self) -> bool {
8062 match self.edit_prediction_settings {
8063 EditPredictionSettings::Disabled => false,
8064 EditPredictionSettings::Enabled { .. } => true,
8065 }
8066 }
8067
8068 fn edit_prediction_requires_modifier(&self) -> bool {
8069 match self.edit_prediction_settings {
8070 EditPredictionSettings::Disabled => false,
8071 EditPredictionSettings::Enabled {
8072 preview_requires_modifier,
8073 ..
8074 } => preview_requires_modifier,
8075 }
8076 }
8077
8078 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
8079 if self.edit_prediction_provider.is_none() {
8080 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8081 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8082 return;
8083 }
8084
8085 let selection = self.selections.newest_anchor();
8086 let cursor = selection.head();
8087
8088 if let Some((buffer, cursor_buffer_position)) =
8089 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
8090 {
8091 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
8092 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8093 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8094 return;
8095 }
8096 self.edit_prediction_settings =
8097 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8098 }
8099 }
8100
8101 fn edit_prediction_settings_at_position(
8102 &self,
8103 buffer: &Entity<Buffer>,
8104 buffer_position: language::Anchor,
8105 cx: &App,
8106 ) -> EditPredictionSettings {
8107 if !self.mode.is_full()
8108 || !self.show_edit_predictions_override.unwrap_or(true)
8109 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
8110 {
8111 return EditPredictionSettings::Disabled;
8112 }
8113
8114 if !LanguageSettings::for_buffer(&buffer.read(cx), cx).show_edit_predictions {
8115 return EditPredictionSettings::Disabled;
8116 };
8117
8118 let by_provider = matches!(
8119 self.menu_edit_predictions_policy,
8120 MenuEditPredictionsPolicy::ByProvider
8121 );
8122
8123 let show_in_menu = by_provider
8124 && self
8125 .edit_prediction_provider
8126 .as_ref()
8127 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
8128
8129 let file = buffer.read(cx).file();
8130 let preview_requires_modifier =
8131 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
8132
8133 EditPredictionSettings::Enabled {
8134 show_in_menu,
8135 preview_requires_modifier,
8136 }
8137 }
8138
8139 fn should_show_edit_predictions(&self) -> bool {
8140 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
8141 }
8142
8143 pub fn edit_prediction_preview_is_active(&self) -> bool {
8144 matches!(
8145 self.edit_prediction_preview,
8146 EditPredictionPreview::Active { .. }
8147 )
8148 }
8149
8150 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
8151 let cursor = self.selections.newest_anchor().head();
8152 if let Some((buffer, cursor_position)) =
8153 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
8154 {
8155 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
8156 } else {
8157 false
8158 }
8159 }
8160
8161 pub fn supports_minimap(&self, cx: &App) -> bool {
8162 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
8163 }
8164
8165 fn edit_predictions_enabled_in_buffer(
8166 &self,
8167 buffer: &Entity<Buffer>,
8168 buffer_position: language::Anchor,
8169 cx: &App,
8170 ) -> bool {
8171 maybe!({
8172 if self.read_only(cx) || self.leader_id.is_some() {
8173 return Some(false);
8174 }
8175 let provider = self.edit_prediction_provider()?;
8176 if !provider.is_enabled(buffer, buffer_position, cx) {
8177 return Some(false);
8178 }
8179 let buffer = buffer.read(cx);
8180 let Some(file) = buffer.file() else {
8181 return Some(true);
8182 };
8183 let settings = all_language_settings(Some(file), cx);
8184 Some(settings.edit_predictions_enabled_for_file(file, cx))
8185 })
8186 .unwrap_or(false)
8187 }
8188
8189 pub fn show_edit_prediction(
8190 &mut self,
8191 _: &ShowEditPrediction,
8192 window: &mut Window,
8193 cx: &mut Context<Self>,
8194 ) {
8195 if !self.has_active_edit_prediction() {
8196 self.refresh_edit_prediction(false, true, window, cx);
8197 return;
8198 }
8199
8200 self.update_visible_edit_prediction(window, cx);
8201 }
8202
8203 pub fn display_cursor_names(
8204 &mut self,
8205 _: &DisplayCursorNames,
8206 window: &mut Window,
8207 cx: &mut Context<Self>,
8208 ) {
8209 self.show_cursor_names(window, cx);
8210 }
8211
8212 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8213 self.show_cursor_names = true;
8214 cx.notify();
8215 cx.spawn_in(window, async move |this, cx| {
8216 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
8217 this.update(cx, |this, cx| {
8218 this.show_cursor_names = false;
8219 cx.notify()
8220 })
8221 .ok()
8222 })
8223 .detach();
8224 }
8225
8226 pub fn accept_partial_edit_prediction(
8227 &mut self,
8228 granularity: EditPredictionGranularity,
8229 window: &mut Window,
8230 cx: &mut Context<Self>,
8231 ) {
8232 if self.show_edit_predictions_in_menu() {
8233 self.hide_context_menu(window, cx);
8234 }
8235
8236 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
8237 return;
8238 };
8239
8240 if !matches!(granularity, EditPredictionGranularity::Full) && self.selections.count() != 1 {
8241 return;
8242 }
8243
8244 match &active_edit_prediction.completion {
8245 EditPrediction::MoveWithin { target, .. } => {
8246 let target = *target;
8247
8248 if matches!(granularity, EditPredictionGranularity::Full) {
8249 if let Some(position_map) = &self.last_position_map {
8250 let target_row = target.to_display_point(&position_map.snapshot).row();
8251 let is_visible = position_map.visible_row_range.contains(&target_row);
8252
8253 if is_visible || !self.edit_prediction_requires_modifier() {
8254 self.unfold_ranges(&[target..target], true, false, cx);
8255 self.change_selections(
8256 SelectionEffects::scroll(Autoscroll::newest()),
8257 window,
8258 cx,
8259 |selections| {
8260 selections.select_anchor_ranges([target..target]);
8261 },
8262 );
8263 self.clear_row_highlights::<EditPredictionPreview>();
8264 self.edit_prediction_preview
8265 .set_previous_scroll_position(None);
8266 } else {
8267 // Highlight and request scroll
8268 self.edit_prediction_preview
8269 .set_previous_scroll_position(Some(
8270 position_map.snapshot.scroll_anchor,
8271 ));
8272 self.highlight_rows::<EditPredictionPreview>(
8273 target..target,
8274 cx.theme().colors().editor_highlighted_line_background,
8275 RowHighlightOptions {
8276 autoscroll: true,
8277 ..Default::default()
8278 },
8279 cx,
8280 );
8281 self.request_autoscroll(Autoscroll::fit(), cx);
8282 }
8283 }
8284 } else {
8285 self.change_selections(
8286 SelectionEffects::scroll(Autoscroll::newest()),
8287 window,
8288 cx,
8289 |selections| {
8290 selections.select_anchor_ranges([target..target]);
8291 },
8292 );
8293 }
8294 }
8295 EditPrediction::MoveOutside { snapshot, target } => {
8296 if let Some(workspace) = self.workspace() {
8297 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
8298 .detach_and_log_err(cx);
8299 }
8300 }
8301 EditPrediction::Edit {
8302 edits,
8303 cursor_position,
8304 ..
8305 } => {
8306 self.report_edit_prediction_event(
8307 active_edit_prediction.completion_id.clone(),
8308 true,
8309 cx,
8310 );
8311
8312 match granularity {
8313 EditPredictionGranularity::Full => {
8314 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
8315
8316 // Compute fallback cursor position BEFORE applying the edit,
8317 // so the anchor tracks through the edit correctly
8318 let fallback_cursor_target = {
8319 let snapshot = self.buffer.read(cx).snapshot(cx);
8320 edits.last().unwrap().0.end.bias_right(&snapshot)
8321 };
8322
8323 self.buffer.update(cx, |buffer, cx| {
8324 buffer.edit(edits.iter().cloned(), None, cx)
8325 });
8326
8327 if let Some(provider) = self.edit_prediction_provider() {
8328 provider.accept(cx);
8329 }
8330
8331 // Resolve cursor position after the edit is applied
8332 let cursor_target = if let Some((anchor, offset)) = cursor_position {
8333 // The anchor tracks through the edit, then we add the offset
8334 let snapshot = self.buffer.read(cx).snapshot(cx);
8335 let base_offset = anchor.to_offset(&snapshot).0;
8336 let target_offset =
8337 MultiBufferOffset((base_offset + offset).min(snapshot.len().0));
8338 snapshot.anchor_after(target_offset)
8339 } else {
8340 fallback_cursor_target
8341 };
8342
8343 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
8344 s.select_anchor_ranges([cursor_target..cursor_target]);
8345 });
8346
8347 let selections = self.selections.disjoint_anchors_arc();
8348 if let Some(transaction_id_now) =
8349 self.buffer.read(cx).last_transaction_id(cx)
8350 {
8351 if transaction_id_prev != Some(transaction_id_now) {
8352 self.selection_history
8353 .insert_transaction(transaction_id_now, selections);
8354 }
8355 }
8356
8357 self.update_visible_edit_prediction(window, cx);
8358 if self.active_edit_prediction.is_none() {
8359 self.refresh_edit_prediction(true, true, window, cx);
8360 }
8361 cx.notify();
8362 }
8363 _ => {
8364 let snapshot = self.buffer.read(cx).snapshot(cx);
8365 let cursor_offset = self
8366 .selections
8367 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8368 .head();
8369
8370 let insertion = edits.iter().find_map(|(range, text)| {
8371 let range = range.to_offset(&snapshot);
8372 if range.is_empty() && range.start == cursor_offset {
8373 Some(text)
8374 } else {
8375 None
8376 }
8377 });
8378
8379 if let Some(text) = insertion {
8380 let text_to_insert = match granularity {
8381 EditPredictionGranularity::Word => {
8382 let mut partial = text
8383 .chars()
8384 .by_ref()
8385 .take_while(|c| c.is_alphabetic())
8386 .collect::<String>();
8387 if partial.is_empty() {
8388 partial = text
8389 .chars()
8390 .by_ref()
8391 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
8392 .collect::<String>();
8393 }
8394 partial
8395 }
8396 EditPredictionGranularity::Line => {
8397 if let Some(line) = text.split_inclusive('\n').next() {
8398 line.to_string()
8399 } else {
8400 text.to_string()
8401 }
8402 }
8403 EditPredictionGranularity::Full => unreachable!(),
8404 };
8405
8406 cx.emit(EditorEvent::InputHandled {
8407 utf16_range_to_replace: None,
8408 text: text_to_insert.clone().into(),
8409 });
8410
8411 self.replace_selections(&text_to_insert, None, window, cx, false);
8412 self.refresh_edit_prediction(true, true, window, cx);
8413 cx.notify();
8414 } else {
8415 self.accept_partial_edit_prediction(
8416 EditPredictionGranularity::Full,
8417 window,
8418 cx,
8419 );
8420 }
8421 }
8422 }
8423 }
8424 }
8425 }
8426
8427 pub fn accept_next_word_edit_prediction(
8428 &mut self,
8429 _: &AcceptNextWordEditPrediction,
8430 window: &mut Window,
8431 cx: &mut Context<Self>,
8432 ) {
8433 self.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
8434 }
8435
8436 pub fn accept_next_line_edit_prediction(
8437 &mut self,
8438 _: &AcceptNextLineEditPrediction,
8439 window: &mut Window,
8440 cx: &mut Context<Self>,
8441 ) {
8442 self.accept_partial_edit_prediction(EditPredictionGranularity::Line, window, cx);
8443 }
8444
8445 pub fn accept_edit_prediction(
8446 &mut self,
8447 _: &AcceptEditPrediction,
8448 window: &mut Window,
8449 cx: &mut Context<Self>,
8450 ) {
8451 self.accept_partial_edit_prediction(EditPredictionGranularity::Full, window, cx);
8452 }
8453
8454 fn discard_edit_prediction(
8455 &mut self,
8456 reason: EditPredictionDiscardReason,
8457 cx: &mut Context<Self>,
8458 ) -> bool {
8459 if reason == EditPredictionDiscardReason::Rejected {
8460 let completion_id = self
8461 .active_edit_prediction
8462 .as_ref()
8463 .and_then(|active_completion| active_completion.completion_id.clone());
8464
8465 self.report_edit_prediction_event(completion_id, false, cx);
8466 }
8467
8468 if let Some(provider) = self.edit_prediction_provider() {
8469 provider.discard(reason, cx);
8470 }
8471
8472 self.take_active_edit_prediction(reason == EditPredictionDiscardReason::Ignored, cx)
8473 }
8474
8475 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
8476 let Some(provider) = self.edit_prediction_provider() else {
8477 return;
8478 };
8479
8480 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
8481 let Some((position, _)) =
8482 buffer_snapshot.anchor_to_buffer_anchor(self.selections.newest_anchor().head())
8483 else {
8484 return;
8485 };
8486 let Some(buffer) = self.buffer.read(cx).buffer(position.buffer_id) else {
8487 return;
8488 };
8489
8490 let extension = buffer
8491 .read(cx)
8492 .file()
8493 .and_then(|file| Some(file.path().extension()?.to_string()));
8494
8495 let event_type = match accepted {
8496 true => "Edit Prediction Accepted",
8497 false => "Edit Prediction Discarded",
8498 };
8499 telemetry::event!(
8500 event_type,
8501 provider = provider.name(),
8502 prediction_id = id,
8503 suggestion_accepted = accepted,
8504 file_extension = extension,
8505 );
8506 }
8507
8508 fn open_editor_at_anchor(
8509 snapshot: &language::BufferSnapshot,
8510 target: language::Anchor,
8511 workspace: &Entity<Workspace>,
8512 window: &mut Window,
8513 cx: &mut App,
8514 ) -> Task<Result<()>> {
8515 workspace.update(cx, |workspace, cx| {
8516 let path = snapshot.file().map(|file| file.full_path(cx));
8517 let Some(path) =
8518 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
8519 else {
8520 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
8521 };
8522 let target = text::ToPoint::to_point(&target, snapshot);
8523 let item = workspace.open_path(path, None, true, window, cx);
8524 window.spawn(cx, async move |cx| {
8525 let Some(editor) = item.await?.downcast::<Editor>() else {
8526 return Ok(());
8527 };
8528 editor
8529 .update_in(cx, |editor, window, cx| {
8530 editor.go_to_singleton_buffer_point(target, window, cx);
8531 })
8532 .ok();
8533 anyhow::Ok(())
8534 })
8535 })
8536 }
8537
8538 pub fn has_active_edit_prediction(&self) -> bool {
8539 self.active_edit_prediction.is_some()
8540 }
8541
8542 fn take_active_edit_prediction(
8543 &mut self,
8544 preserve_stale_in_menu: bool,
8545 cx: &mut Context<Self>,
8546 ) -> bool {
8547 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
8548 if !preserve_stale_in_menu {
8549 self.stale_edit_prediction_in_menu = None;
8550 }
8551 return false;
8552 };
8553
8554 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
8555 self.clear_highlights(HighlightKey::EditPredictionHighlight, cx);
8556 self.stale_edit_prediction_in_menu =
8557 preserve_stale_in_menu.then_some(active_edit_prediction);
8558 true
8559 }
8560
8561 /// Returns true when we're displaying the edit prediction popover below the cursor
8562 /// like we are not previewing and the LSP autocomplete menu is visible
8563 /// or we are in `when_holding_modifier` mode.
8564 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
8565 if self.edit_prediction_preview_is_active()
8566 || !self.show_edit_predictions_in_menu()
8567 || !self.edit_predictions_enabled()
8568 {
8569 return false;
8570 }
8571
8572 if self.has_visible_completions_menu() {
8573 return true;
8574 }
8575
8576 has_completion && self.edit_prediction_requires_modifier()
8577 }
8578
8579 fn handle_modifiers_changed(
8580 &mut self,
8581 modifiers: Modifiers,
8582 position_map: &PositionMap,
8583 window: &mut Window,
8584 cx: &mut Context<Self>,
8585 ) {
8586 self.update_edit_prediction_settings(cx);
8587
8588 // Ensure that the edit prediction preview is updated, even when not
8589 // enabled, if there's an active edit prediction preview.
8590 if self.show_edit_predictions_in_menu()
8591 || self.edit_prediction_requires_modifier()
8592 || matches!(
8593 self.edit_prediction_preview,
8594 EditPredictionPreview::Active { .. }
8595 )
8596 {
8597 self.update_edit_prediction_preview(&modifiers, window, cx);
8598 }
8599
8600 self.update_selection_mode(&modifiers, position_map, window, cx);
8601
8602 let mouse_position = window.mouse_position();
8603 if !position_map.text_hitbox.is_hovered(window) {
8604 return;
8605 }
8606
8607 self.update_hovered_link(
8608 position_map.point_for_position(mouse_position),
8609 Some(mouse_position),
8610 &position_map.snapshot,
8611 modifiers,
8612 window,
8613 cx,
8614 )
8615 }
8616
8617 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8618 match EditorSettings::get_global(cx).multi_cursor_modifier {
8619 MultiCursorModifier::Alt => modifiers.secondary(),
8620 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
8621 }
8622 }
8623
8624 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8625 match EditorSettings::get_global(cx).multi_cursor_modifier {
8626 MultiCursorModifier::Alt => modifiers.alt,
8627 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
8628 }
8629 }
8630
8631 fn columnar_selection_mode(
8632 modifiers: &Modifiers,
8633 cx: &mut Context<Self>,
8634 ) -> Option<ColumnarMode> {
8635 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
8636 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
8637 Some(ColumnarMode::FromMouse)
8638 } else if Self::is_alt_pressed(modifiers, cx) {
8639 Some(ColumnarMode::FromSelection)
8640 } else {
8641 None
8642 }
8643 } else {
8644 None
8645 }
8646 }
8647
8648 fn update_selection_mode(
8649 &mut self,
8650 modifiers: &Modifiers,
8651 position_map: &PositionMap,
8652 window: &mut Window,
8653 cx: &mut Context<Self>,
8654 ) {
8655 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
8656 return;
8657 };
8658 if self.selections.pending_anchor().is_none() {
8659 return;
8660 }
8661
8662 let mouse_position = window.mouse_position();
8663 let point_for_position = position_map.point_for_position(mouse_position);
8664 let position = point_for_position.previous_valid;
8665
8666 self.select(
8667 SelectPhase::BeginColumnar {
8668 position,
8669 reset: false,
8670 mode,
8671 goal_column: point_for_position.exact_unclipped.column(),
8672 },
8673 window,
8674 cx,
8675 );
8676 }
8677
8678 fn update_edit_prediction_preview(
8679 &mut self,
8680 modifiers: &Modifiers,
8681 window: &mut Window,
8682 cx: &mut Context<Self>,
8683 ) {
8684 let modifiers_held = self.edit_prediction_preview_modifiers_held(modifiers, window, cx);
8685
8686 if modifiers_held {
8687 if matches!(
8688 self.edit_prediction_preview,
8689 EditPredictionPreview::Inactive { .. }
8690 ) {
8691 self.edit_prediction_preview = EditPredictionPreview::Active {
8692 previous_scroll_position: None,
8693 since: Instant::now(),
8694 };
8695
8696 self.update_visible_edit_prediction(window, cx);
8697 cx.notify();
8698 }
8699 } else if let EditPredictionPreview::Active {
8700 previous_scroll_position,
8701 since,
8702 } = self.edit_prediction_preview
8703 {
8704 if let (Some(previous_scroll_position), Some(position_map)) =
8705 (previous_scroll_position, self.last_position_map.as_ref())
8706 {
8707 self.set_scroll_position(
8708 previous_scroll_position
8709 .scroll_position(&position_map.snapshot.display_snapshot),
8710 window,
8711 cx,
8712 );
8713 }
8714
8715 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8716 released_too_fast: since.elapsed() < Duration::from_millis(200),
8717 };
8718 self.clear_row_highlights::<EditPredictionPreview>();
8719 self.update_visible_edit_prediction(window, cx);
8720 cx.notify();
8721 }
8722 }
8723
8724 fn update_visible_edit_prediction(
8725 &mut self,
8726 _window: &mut Window,
8727 cx: &mut Context<Self>,
8728 ) -> Option<()> {
8729 if self.ime_transaction.is_some() {
8730 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8731 return None;
8732 }
8733
8734 let selection = self.selections.newest_anchor();
8735 let multibuffer = self.buffer.read(cx).snapshot(cx);
8736 let cursor = selection.head();
8737 let (cursor_text_anchor, _) = multibuffer.anchor_to_buffer_anchor(cursor)?;
8738 let buffer = self.buffer.read(cx).buffer(cursor_text_anchor.buffer_id)?;
8739
8740 // Check project-level disable_ai setting for the current buffer
8741 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
8742 return None;
8743 }
8744 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8745
8746 let show_in_menu = self.show_edit_predictions_in_menu();
8747 let completions_menu_has_precedence = !show_in_menu
8748 && (self.context_menu.borrow().is_some()
8749 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8750
8751 if completions_menu_has_precedence
8752 || !offset_selection.is_empty()
8753 || self
8754 .active_edit_prediction
8755 .as_ref()
8756 .is_some_and(|completion| {
8757 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8758 return false;
8759 };
8760 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8761 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8762 !invalidation_range.contains(&offset_selection.head())
8763 })
8764 {
8765 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8766 return None;
8767 }
8768
8769 self.take_active_edit_prediction(true, cx);
8770 let Some(provider) = self.edit_prediction_provider() else {
8771 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8772 return None;
8773 };
8774
8775 self.edit_prediction_settings =
8776 self.edit_prediction_settings_at_position(&buffer, cursor_text_anchor, cx);
8777
8778 self.in_leading_whitespace = multibuffer.is_line_whitespace_upto(cursor);
8779
8780 if self.in_leading_whitespace {
8781 let cursor_point = cursor.to_point(&multibuffer);
8782 let mut suggested_indent = None;
8783 multibuffer.suggested_indents_callback(
8784 cursor_point.row..cursor_point.row + 1,
8785 &mut |_, indent| {
8786 suggested_indent = Some(indent);
8787 ControlFlow::Break(())
8788 },
8789 cx,
8790 );
8791
8792 if let Some(indent) = suggested_indent
8793 && indent.len == cursor_point.column
8794 {
8795 self.in_leading_whitespace = false;
8796 }
8797 }
8798
8799 let edit_prediction = provider.suggest(&buffer, cursor_text_anchor, cx)?;
8800
8801 let (completion_id, edits, predicted_cursor_position, edit_preview) = match edit_prediction
8802 {
8803 edit_prediction_types::EditPrediction::Local {
8804 id,
8805 edits,
8806 cursor_position,
8807 edit_preview,
8808 } => (id, edits, cursor_position, edit_preview),
8809 edit_prediction_types::EditPrediction::Jump {
8810 id,
8811 snapshot,
8812 target,
8813 } => {
8814 if let Some(provider) = &self.edit_prediction_provider {
8815 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8816 }
8817 self.stale_edit_prediction_in_menu = None;
8818 self.active_edit_prediction = Some(EditPredictionState {
8819 inlay_ids: vec![],
8820 completion: EditPrediction::MoveOutside { snapshot, target },
8821 completion_id: id,
8822 invalidation_range: None,
8823 });
8824 cx.notify();
8825 return Some(());
8826 }
8827 };
8828
8829 let edits = edits
8830 .into_iter()
8831 .flat_map(|(range, new_text)| {
8832 Some((
8833 multibuffer.buffer_anchor_range_to_anchor_range(range)?,
8834 new_text,
8835 ))
8836 })
8837 .collect::<Vec<_>>();
8838 if edits.is_empty() {
8839 return None;
8840 }
8841
8842 let cursor_position = predicted_cursor_position.and_then(|predicted| {
8843 let anchor = multibuffer.anchor_in_excerpt(predicted.anchor)?;
8844 Some((anchor, predicted.offset))
8845 });
8846
8847 let first_edit_start = edits.first().unwrap().0.start;
8848 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8849 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8850
8851 let last_edit_end = edits.last().unwrap().0.end;
8852 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8853 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8854
8855 let cursor_row = cursor.to_point(&multibuffer).row;
8856
8857 let snapshot = multibuffer
8858 .buffer_for_id(cursor_text_anchor.buffer_id)
8859 .cloned()?;
8860
8861 let mut inlay_ids = Vec::new();
8862 let invalidation_row_range;
8863 let move_invalidation_row_range = if cursor_row < edit_start_row {
8864 Some(cursor_row..edit_end_row)
8865 } else if cursor_row > edit_end_row {
8866 Some(edit_start_row..cursor_row)
8867 } else {
8868 None
8869 };
8870 let supports_jump = self
8871 .edit_prediction_provider
8872 .as_ref()
8873 .map(|provider| provider.provider.supports_jump_to_edit())
8874 .unwrap_or(true);
8875
8876 let is_move = supports_jump
8877 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8878 let completion = if is_move {
8879 if let Some(provider) = &self.edit_prediction_provider {
8880 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8881 }
8882 invalidation_row_range =
8883 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8884 let target = first_edit_start;
8885 EditPrediction::MoveWithin { target, snapshot }
8886 } else {
8887 let show_completions_in_menu = self.has_visible_completions_menu();
8888 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8889 && !self.edit_predictions_hidden_for_vim_mode;
8890
8891 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8892 if provider.show_tab_accept_marker() {
8893 EditDisplayMode::TabAccept
8894 } else {
8895 EditDisplayMode::Inline
8896 }
8897 } else {
8898 EditDisplayMode::DiffPopover
8899 };
8900
8901 let report_shown = match display_mode {
8902 EditDisplayMode::DiffPopover | EditDisplayMode::Inline => {
8903 show_completions_in_buffer || show_completions_in_menu
8904 }
8905 EditDisplayMode::TabAccept => {
8906 show_completions_in_menu || self.edit_prediction_preview_is_active()
8907 }
8908 };
8909
8910 if report_shown && let Some(provider) = &self.edit_prediction_provider {
8911 let suggestion_display_type = match display_mode {
8912 EditDisplayMode::DiffPopover => SuggestionDisplayType::DiffPopover,
8913 EditDisplayMode::Inline | EditDisplayMode::TabAccept => {
8914 SuggestionDisplayType::GhostText
8915 }
8916 };
8917 provider.provider.did_show(suggestion_display_type, cx);
8918 }
8919
8920 if show_completions_in_buffer {
8921 if edits
8922 .iter()
8923 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8924 {
8925 let mut inlays = Vec::new();
8926 for (range, new_text) in &edits {
8927 let inlay = Inlay::edit_prediction(
8928 post_inc(&mut self.next_inlay_id),
8929 range.start,
8930 new_text.as_ref(),
8931 );
8932 inlay_ids.push(inlay.id);
8933 inlays.push(inlay);
8934 }
8935
8936 self.splice_inlays(&[], inlays, cx);
8937 } else {
8938 let background_color = cx.theme().status().deleted_background;
8939 self.highlight_text(
8940 HighlightKey::EditPredictionHighlight,
8941 edits.iter().map(|(range, _)| range.clone()).collect(),
8942 HighlightStyle {
8943 background_color: Some(background_color),
8944 ..Default::default()
8945 },
8946 cx,
8947 );
8948 }
8949 }
8950
8951 invalidation_row_range = edit_start_row..edit_end_row;
8952
8953 EditPrediction::Edit {
8954 edits,
8955 cursor_position,
8956 edit_preview,
8957 display_mode,
8958 snapshot,
8959 }
8960 };
8961
8962 let invalidation_range = multibuffer
8963 .anchor_before(Point::new(invalidation_row_range.start, 0))
8964 ..multibuffer.anchor_after(Point::new(
8965 invalidation_row_range.end,
8966 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8967 ));
8968
8969 self.stale_edit_prediction_in_menu = None;
8970 self.active_edit_prediction = Some(EditPredictionState {
8971 inlay_ids,
8972 completion,
8973 completion_id,
8974 invalidation_range: Some(invalidation_range),
8975 });
8976
8977 cx.notify();
8978
8979 Some(())
8980 }
8981
8982 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8983 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8984 }
8985
8986 /// Get all display points of breakpoints that will be rendered within editor
8987 ///
8988 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8989 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8990 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8991 fn active_breakpoints(
8992 &self,
8993 range: Range<DisplayRow>,
8994 window: &mut Window,
8995 cx: &mut Context<Self>,
8996 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8997 let mut breakpoint_display_points = HashMap::default();
8998
8999 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
9000 return breakpoint_display_points;
9001 };
9002
9003 let snapshot = self.snapshot(window, cx);
9004
9005 let multi_buffer_snapshot = snapshot.buffer_snapshot();
9006
9007 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
9008 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
9009
9010 for (buffer_snapshot, range, _) in
9011 multi_buffer_snapshot.range_to_buffer_ranges(range.start..range.end)
9012 {
9013 let Some(buffer) = self.buffer().read(cx).buffer(buffer_snapshot.remote_id()) else {
9014 continue;
9015 };
9016 let breakpoints = breakpoint_store.read(cx).breakpoints(
9017 &buffer,
9018 Some(
9019 buffer_snapshot.anchor_before(range.start)
9020 ..buffer_snapshot.anchor_after(range.end),
9021 ),
9022 &buffer_snapshot,
9023 cx,
9024 );
9025 for (breakpoint, state) in breakpoints {
9026 let Some(multi_buffer_anchor) =
9027 multi_buffer_snapshot.anchor_in_excerpt(breakpoint.position)
9028 else {
9029 continue;
9030 };
9031 let position = multi_buffer_anchor
9032 .to_point(&multi_buffer_snapshot)
9033 .to_display_point(&snapshot);
9034
9035 breakpoint_display_points.insert(
9036 position.row(),
9037 (multi_buffer_anchor, breakpoint.bp.clone(), state),
9038 );
9039 }
9040 }
9041
9042 breakpoint_display_points
9043 }
9044
9045 fn breakpoint_context_menu(
9046 &self,
9047 anchor: Anchor,
9048 window: &mut Window,
9049 cx: &mut Context<Self>,
9050 ) -> Entity<ui::ContextMenu> {
9051 let weak_editor = cx.weak_entity();
9052 let focus_handle = self.focus_handle(cx);
9053
9054 let row = self
9055 .buffer
9056 .read(cx)
9057 .snapshot(cx)
9058 .summary_for_anchor::<Point>(&anchor)
9059 .row;
9060
9061 let breakpoint = self
9062 .breakpoint_at_row(row, window, cx)
9063 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
9064
9065 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
9066 "Edit Log Breakpoint"
9067 } else {
9068 "Set Log Breakpoint"
9069 };
9070
9071 let condition_breakpoint_msg = if breakpoint
9072 .as_ref()
9073 .is_some_and(|bp| bp.1.condition.is_some())
9074 {
9075 "Edit Condition Breakpoint"
9076 } else {
9077 "Set Condition Breakpoint"
9078 };
9079
9080 let hit_condition_breakpoint_msg = if breakpoint
9081 .as_ref()
9082 .is_some_and(|bp| bp.1.hit_condition.is_some())
9083 {
9084 "Edit Hit Condition Breakpoint"
9085 } else {
9086 "Set Hit Condition Breakpoint"
9087 };
9088
9089 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
9090 "Unset Breakpoint"
9091 } else {
9092 "Set Breakpoint"
9093 };
9094
9095 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
9096
9097 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
9098 BreakpointState::Enabled => Some("Disable"),
9099 BreakpointState::Disabled => Some("Enable"),
9100 });
9101
9102 let (anchor, breakpoint) =
9103 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
9104
9105 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
9106 menu.on_blur_subscription(Subscription::new(|| {}))
9107 .context(focus_handle)
9108 .when(run_to_cursor, |this| {
9109 let weak_editor = weak_editor.clone();
9110 this.entry("Run to Cursor", None, move |window, cx| {
9111 weak_editor
9112 .update(cx, |editor, cx| {
9113 editor.change_selections(
9114 SelectionEffects::no_scroll(),
9115 window,
9116 cx,
9117 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
9118 );
9119 })
9120 .ok();
9121
9122 window.dispatch_action(Box::new(RunToCursor), cx);
9123 })
9124 .separator()
9125 })
9126 .when_some(toggle_state_msg, |this, msg| {
9127 this.entry(msg, None, {
9128 let weak_editor = weak_editor.clone();
9129 let breakpoint = breakpoint.clone();
9130 move |_window, cx| {
9131 weak_editor
9132 .update(cx, |this, cx| {
9133 this.edit_breakpoint_at_anchor(
9134 anchor,
9135 breakpoint.as_ref().clone(),
9136 BreakpointEditAction::InvertState,
9137 cx,
9138 );
9139 })
9140 .log_err();
9141 }
9142 })
9143 })
9144 .entry(set_breakpoint_msg, None, {
9145 let weak_editor = weak_editor.clone();
9146 let breakpoint = breakpoint.clone();
9147 move |_window, cx| {
9148 weak_editor
9149 .update(cx, |this, cx| {
9150 this.edit_breakpoint_at_anchor(
9151 anchor,
9152 breakpoint.as_ref().clone(),
9153 BreakpointEditAction::Toggle,
9154 cx,
9155 );
9156 })
9157 .log_err();
9158 }
9159 })
9160 .entry(log_breakpoint_msg, None, {
9161 let breakpoint = breakpoint.clone();
9162 let weak_editor = weak_editor.clone();
9163 move |window, cx| {
9164 weak_editor
9165 .update(cx, |this, cx| {
9166 this.add_edit_breakpoint_block(
9167 anchor,
9168 breakpoint.as_ref(),
9169 BreakpointPromptEditAction::Log,
9170 window,
9171 cx,
9172 );
9173 })
9174 .log_err();
9175 }
9176 })
9177 .entry(condition_breakpoint_msg, None, {
9178 let breakpoint = breakpoint.clone();
9179 let weak_editor = weak_editor.clone();
9180 move |window, cx| {
9181 weak_editor
9182 .update(cx, |this, cx| {
9183 this.add_edit_breakpoint_block(
9184 anchor,
9185 breakpoint.as_ref(),
9186 BreakpointPromptEditAction::Condition,
9187 window,
9188 cx,
9189 );
9190 })
9191 .log_err();
9192 }
9193 })
9194 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
9195 weak_editor
9196 .update(cx, |this, cx| {
9197 this.add_edit_breakpoint_block(
9198 anchor,
9199 breakpoint.as_ref(),
9200 BreakpointPromptEditAction::HitCondition,
9201 window,
9202 cx,
9203 );
9204 })
9205 .log_err();
9206 })
9207 })
9208 }
9209
9210 fn render_breakpoint(
9211 &self,
9212 position: Anchor,
9213 row: DisplayRow,
9214 breakpoint: &Breakpoint,
9215 state: Option<BreakpointSessionState>,
9216 cx: &mut Context<Self>,
9217 ) -> IconButton {
9218 let is_rejected = state.is_some_and(|s| !s.verified);
9219 // Is it a breakpoint that shows up when hovering over gutter?
9220 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
9221 (false, false),
9222 |PhantomBreakpointIndicator {
9223 is_active,
9224 display_row,
9225 collides_with_existing_breakpoint,
9226 }| {
9227 (
9228 is_active && display_row == row,
9229 collides_with_existing_breakpoint,
9230 )
9231 },
9232 );
9233
9234 let (color, icon) = {
9235 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
9236 (false, false) => ui::IconName::DebugBreakpoint,
9237 (true, false) => ui::IconName::DebugLogBreakpoint,
9238 (false, true) => ui::IconName::DebugDisabledBreakpoint,
9239 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
9240 };
9241
9242 let theme_colors = cx.theme().colors();
9243
9244 let color = if is_phantom {
9245 if collides_with_existing {
9246 Color::Custom(
9247 theme_colors
9248 .debugger_accent
9249 .blend(theme_colors.text.opacity(0.6)),
9250 )
9251 } else {
9252 Color::Hint
9253 }
9254 } else if is_rejected {
9255 Color::Disabled
9256 } else {
9257 Color::Debugger
9258 };
9259
9260 (color, icon)
9261 };
9262
9263 let breakpoint = Arc::from(breakpoint.clone());
9264
9265 let alt_as_text = gpui::Keystroke {
9266 modifiers: Modifiers::secondary_key(),
9267 ..Default::default()
9268 };
9269 let primary_action_text = if breakpoint.is_disabled() {
9270 "Enable breakpoint"
9271 } else if is_phantom && !collides_with_existing {
9272 "Set breakpoint"
9273 } else {
9274 "Unset breakpoint"
9275 };
9276 let focus_handle = self.focus_handle.clone();
9277
9278 let meta = if is_rejected {
9279 SharedString::from("No executable code is associated with this line.")
9280 } else if collides_with_existing && !breakpoint.is_disabled() {
9281 SharedString::from(format!(
9282 "{alt_as_text}-click to disable,\nright-click for more options."
9283 ))
9284 } else {
9285 SharedString::from("Right-click for more options.")
9286 };
9287 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
9288 .icon_size(IconSize::XSmall)
9289 .size(ui::ButtonSize::None)
9290 .when(is_rejected, |this| {
9291 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
9292 })
9293 .icon_color(color)
9294 .style(ButtonStyle::Transparent)
9295 .on_click(cx.listener({
9296 move |editor, event: &ClickEvent, window, cx| {
9297 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
9298 BreakpointEditAction::InvertState
9299 } else {
9300 BreakpointEditAction::Toggle
9301 };
9302
9303 window.focus(&editor.focus_handle(cx), cx);
9304 editor.update_breakpoint_collision_on_toggle(row, &edit_action);
9305 editor.edit_breakpoint_at_anchor(
9306 position,
9307 breakpoint.as_ref().clone(),
9308 edit_action,
9309 cx,
9310 );
9311 }
9312 }))
9313 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
9314 editor.set_breakpoint_context_menu(
9315 row,
9316 Some(position),
9317 event.position(),
9318 window,
9319 cx,
9320 );
9321 }))
9322 .tooltip(move |_window, cx| {
9323 Tooltip::with_meta_in(
9324 primary_action_text,
9325 Some(&ToggleBreakpoint),
9326 meta.clone(),
9327 &focus_handle,
9328 cx,
9329 )
9330 })
9331 }
9332
9333 fn build_tasks_context(
9334 project: &Entity<Project>,
9335 buffer: &Entity<Buffer>,
9336 buffer_row: u32,
9337 tasks: &Arc<RunnableTasks>,
9338 cx: &mut Context<Self>,
9339 ) -> Task<Option<task::TaskContext>> {
9340 let position = Point::new(buffer_row, tasks.column);
9341 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
9342 let location = Location {
9343 buffer: buffer.clone(),
9344 range: range_start..range_start,
9345 };
9346 // Fill in the environmental variables from the tree-sitter captures
9347 let mut captured_task_variables = TaskVariables::default();
9348 for (capture_name, value) in tasks.extra_variables.clone() {
9349 captured_task_variables.insert(
9350 task::VariableName::Custom(capture_name.into()),
9351 value.clone(),
9352 );
9353 }
9354 project.update(cx, |project, cx| {
9355 project.task_store().update(cx, |task_store, cx| {
9356 task_store.task_context_for_location(captured_task_variables, location, cx)
9357 })
9358 })
9359 }
9360
9361 pub fn context_menu_visible(&self) -> bool {
9362 !self.edit_prediction_preview_is_active()
9363 && self
9364 .context_menu
9365 .borrow()
9366 .as_ref()
9367 .is_some_and(|menu| menu.visible())
9368 }
9369
9370 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
9371 self.context_menu
9372 .borrow()
9373 .as_ref()
9374 .map(|menu| menu.origin())
9375 }
9376
9377 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
9378 self.context_menu_options = Some(options);
9379 }
9380
9381 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
9382 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
9383
9384 fn render_edit_prediction_popover(
9385 &mut self,
9386 text_bounds: &Bounds<Pixels>,
9387 content_origin: gpui::Point<Pixels>,
9388 right_margin: Pixels,
9389 editor_snapshot: &EditorSnapshot,
9390 visible_row_range: Range<DisplayRow>,
9391 scroll_top: ScrollOffset,
9392 scroll_bottom: ScrollOffset,
9393 line_layouts: &[LineWithInvisibles],
9394 line_height: Pixels,
9395 scroll_position: gpui::Point<ScrollOffset>,
9396 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9397 newest_selection_head: Option<DisplayPoint>,
9398 editor_width: Pixels,
9399 style: &EditorStyle,
9400 window: &mut Window,
9401 cx: &mut App,
9402 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9403 if self.mode().is_minimap() {
9404 return None;
9405 }
9406 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
9407
9408 if self.edit_prediction_visible_in_cursor_popover(true) {
9409 return None;
9410 }
9411
9412 match &active_edit_prediction.completion {
9413 EditPrediction::MoveWithin { target, .. } => {
9414 let target_display_point = target.to_display_point(editor_snapshot);
9415
9416 if self.edit_prediction_requires_modifier() {
9417 if !self.edit_prediction_preview_is_active() {
9418 return None;
9419 }
9420
9421 self.render_edit_prediction_modifier_jump_popover(
9422 text_bounds,
9423 content_origin,
9424 visible_row_range,
9425 line_layouts,
9426 line_height,
9427 scroll_pixel_position,
9428 newest_selection_head,
9429 target_display_point,
9430 window,
9431 cx,
9432 )
9433 } else {
9434 self.render_edit_prediction_eager_jump_popover(
9435 text_bounds,
9436 content_origin,
9437 editor_snapshot,
9438 visible_row_range,
9439 scroll_top,
9440 scroll_bottom,
9441 line_height,
9442 scroll_pixel_position,
9443 target_display_point,
9444 editor_width,
9445 window,
9446 cx,
9447 )
9448 }
9449 }
9450 EditPrediction::Edit {
9451 display_mode: EditDisplayMode::Inline,
9452 ..
9453 } => None,
9454 EditPrediction::Edit {
9455 display_mode: EditDisplayMode::TabAccept,
9456 edits,
9457 ..
9458 } => {
9459 let range = &edits.first()?.0;
9460 let target_display_point = range.end.to_display_point(editor_snapshot);
9461
9462 self.render_edit_prediction_end_of_line_popover(
9463 "Accept",
9464 editor_snapshot,
9465 visible_row_range,
9466 target_display_point,
9467 line_height,
9468 scroll_pixel_position,
9469 content_origin,
9470 editor_width,
9471 window,
9472 cx,
9473 )
9474 }
9475 EditPrediction::Edit {
9476 edits,
9477 edit_preview,
9478 display_mode: EditDisplayMode::DiffPopover,
9479 snapshot,
9480 ..
9481 } => self.render_edit_prediction_diff_popover(
9482 text_bounds,
9483 content_origin,
9484 right_margin,
9485 editor_snapshot,
9486 visible_row_range,
9487 line_layouts,
9488 line_height,
9489 scroll_position,
9490 scroll_pixel_position,
9491 newest_selection_head,
9492 editor_width,
9493 style,
9494 edits,
9495 edit_preview,
9496 snapshot,
9497 window,
9498 cx,
9499 ),
9500 EditPrediction::MoveOutside { snapshot, .. } => {
9501 let mut element = self
9502 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
9503 .into_any();
9504
9505 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9506 let origin_x = text_bounds.size.width - size.width - px(30.);
9507 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
9508 element.prepaint_at(origin, window, cx);
9509
9510 Some((element, origin))
9511 }
9512 }
9513 }
9514
9515 fn render_edit_prediction_modifier_jump_popover(
9516 &mut self,
9517 text_bounds: &Bounds<Pixels>,
9518 content_origin: gpui::Point<Pixels>,
9519 visible_row_range: Range<DisplayRow>,
9520 line_layouts: &[LineWithInvisibles],
9521 line_height: Pixels,
9522 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9523 newest_selection_head: Option<DisplayPoint>,
9524 target_display_point: DisplayPoint,
9525 window: &mut Window,
9526 cx: &mut App,
9527 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9528 let scrolled_content_origin =
9529 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
9530
9531 const SCROLL_PADDING_Y: Pixels = px(12.);
9532
9533 if target_display_point.row() < visible_row_range.start {
9534 return self.render_edit_prediction_scroll_popover(
9535 &|_| SCROLL_PADDING_Y,
9536 IconName::ArrowUp,
9537 visible_row_range,
9538 line_layouts,
9539 newest_selection_head,
9540 scrolled_content_origin,
9541 window,
9542 cx,
9543 );
9544 } else if target_display_point.row() >= visible_row_range.end {
9545 return self.render_edit_prediction_scroll_popover(
9546 &|size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
9547 IconName::ArrowDown,
9548 visible_row_range,
9549 line_layouts,
9550 newest_selection_head,
9551 scrolled_content_origin,
9552 window,
9553 cx,
9554 );
9555 }
9556
9557 const POLE_WIDTH: Pixels = px(2.);
9558
9559 let line_layout =
9560 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
9561 let target_column = target_display_point.column() as usize;
9562
9563 let target_x = line_layout.x_for_index(target_column);
9564 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
9565 - scroll_pixel_position.y;
9566
9567 let flag_on_right = target_x < text_bounds.size.width / 2.;
9568
9569 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
9570 border_color.l += 0.001;
9571
9572 let mut element = v_flex()
9573 .items_end()
9574 .when(flag_on_right, |el| el.items_start())
9575 .child(if flag_on_right {
9576 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9577 .rounded_bl(px(0.))
9578 .rounded_tl(px(0.))
9579 .border_l_2()
9580 .border_color(border_color)
9581 } else {
9582 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9583 .rounded_br(px(0.))
9584 .rounded_tr(px(0.))
9585 .border_r_2()
9586 .border_color(border_color)
9587 })
9588 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9589 .into_any();
9590
9591 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9592
9593 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9594 - point(
9595 if flag_on_right {
9596 POLE_WIDTH
9597 } else {
9598 size.width - POLE_WIDTH
9599 },
9600 size.height - line_height,
9601 );
9602
9603 origin.x = origin.x.max(content_origin.x);
9604
9605 element.prepaint_at(origin, window, cx);
9606
9607 Some((element, origin))
9608 }
9609
9610 fn render_edit_prediction_scroll_popover(
9611 &mut self,
9612 to_y: &dyn Fn(Size<Pixels>) -> Pixels,
9613 scroll_icon: IconName,
9614 visible_row_range: Range<DisplayRow>,
9615 line_layouts: &[LineWithInvisibles],
9616 newest_selection_head: Option<DisplayPoint>,
9617 scrolled_content_origin: gpui::Point<Pixels>,
9618 window: &mut Window,
9619 cx: &mut App,
9620 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9621 let mut element = self
9622 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9623 .into_any();
9624
9625 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9626
9627 let cursor = newest_selection_head?;
9628 let cursor_row_layout =
9629 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9630 let cursor_column = cursor.column() as usize;
9631
9632 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9633
9634 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9635
9636 element.prepaint_at(origin, window, cx);
9637 Some((element, origin))
9638 }
9639
9640 fn render_edit_prediction_eager_jump_popover(
9641 &mut self,
9642 text_bounds: &Bounds<Pixels>,
9643 content_origin: gpui::Point<Pixels>,
9644 editor_snapshot: &EditorSnapshot,
9645 visible_row_range: Range<DisplayRow>,
9646 scroll_top: ScrollOffset,
9647 scroll_bottom: ScrollOffset,
9648 line_height: Pixels,
9649 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9650 target_display_point: DisplayPoint,
9651 editor_width: Pixels,
9652 window: &mut Window,
9653 cx: &mut App,
9654 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9655 if target_display_point.row().as_f64() < scroll_top {
9656 let mut element = self
9657 .render_edit_prediction_line_popover(
9658 "Jump to Edit",
9659 Some(IconName::ArrowUp),
9660 window,
9661 cx,
9662 )
9663 .into_any();
9664
9665 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9666 let offset = point(
9667 (text_bounds.size.width - size.width) / 2.,
9668 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9669 );
9670
9671 let origin = text_bounds.origin + offset;
9672 element.prepaint_at(origin, window, cx);
9673 Some((element, origin))
9674 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9675 let mut element = self
9676 .render_edit_prediction_line_popover(
9677 "Jump to Edit",
9678 Some(IconName::ArrowDown),
9679 window,
9680 cx,
9681 )
9682 .into_any();
9683
9684 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9685 let offset = point(
9686 (text_bounds.size.width - size.width) / 2.,
9687 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9688 );
9689
9690 let origin = text_bounds.origin + offset;
9691 element.prepaint_at(origin, window, cx);
9692 Some((element, origin))
9693 } else {
9694 self.render_edit_prediction_end_of_line_popover(
9695 "Jump to Edit",
9696 editor_snapshot,
9697 visible_row_range,
9698 target_display_point,
9699 line_height,
9700 scroll_pixel_position,
9701 content_origin,
9702 editor_width,
9703 window,
9704 cx,
9705 )
9706 }
9707 }
9708
9709 fn render_edit_prediction_end_of_line_popover(
9710 self: &mut Editor,
9711 label: &'static str,
9712 editor_snapshot: &EditorSnapshot,
9713 visible_row_range: Range<DisplayRow>,
9714 target_display_point: DisplayPoint,
9715 line_height: Pixels,
9716 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9717 content_origin: gpui::Point<Pixels>,
9718 editor_width: Pixels,
9719 window: &mut Window,
9720 cx: &mut App,
9721 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9722 let target_line_end = DisplayPoint::new(
9723 target_display_point.row(),
9724 editor_snapshot.line_len(target_display_point.row()),
9725 );
9726
9727 let mut element = self
9728 .render_edit_prediction_line_popover(label, None, window, cx)
9729 .into_any();
9730
9731 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9732
9733 let line_origin =
9734 self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
9735
9736 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9737 let mut origin = start_point
9738 + line_origin
9739 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9740 origin.x = origin.x.max(content_origin.x);
9741
9742 let max_x = content_origin.x + editor_width - size.width;
9743
9744 if origin.x > max_x {
9745 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9746
9747 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9748 origin.y += offset;
9749 IconName::ArrowUp
9750 } else {
9751 origin.y -= offset;
9752 IconName::ArrowDown
9753 };
9754
9755 element = self
9756 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9757 .into_any();
9758
9759 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9760
9761 origin.x = content_origin.x + editor_width - size.width - px(2.);
9762 }
9763
9764 element.prepaint_at(origin, window, cx);
9765 Some((element, origin))
9766 }
9767
9768 fn render_edit_prediction_diff_popover(
9769 self: &Editor,
9770 text_bounds: &Bounds<Pixels>,
9771 content_origin: gpui::Point<Pixels>,
9772 right_margin: Pixels,
9773 editor_snapshot: &EditorSnapshot,
9774 visible_row_range: Range<DisplayRow>,
9775 line_layouts: &[LineWithInvisibles],
9776 line_height: Pixels,
9777 scroll_position: gpui::Point<ScrollOffset>,
9778 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9779 newest_selection_head: Option<DisplayPoint>,
9780 editor_width: Pixels,
9781 style: &EditorStyle,
9782 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9783 edit_preview: &Option<language::EditPreview>,
9784 snapshot: &language::BufferSnapshot,
9785 window: &mut Window,
9786 cx: &mut App,
9787 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9788 let edit_start = edits
9789 .first()
9790 .unwrap()
9791 .0
9792 .start
9793 .to_display_point(editor_snapshot);
9794 let edit_end = edits
9795 .last()
9796 .unwrap()
9797 .0
9798 .end
9799 .to_display_point(editor_snapshot);
9800
9801 let is_visible = visible_row_range.contains(&edit_start.row())
9802 || visible_row_range.contains(&edit_end.row());
9803 if !is_visible {
9804 return None;
9805 }
9806
9807 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9808 crate::edit_prediction_edit_text(
9809 snapshot,
9810 edits,
9811 edit_preview,
9812 false,
9813 editor_snapshot.buffer_snapshot(),
9814 cx,
9815 )
9816 } else {
9817 // Fallback for providers without edit_preview
9818 crate::edit_prediction_fallback_text(edits, cx)
9819 };
9820
9821 let styled_text = highlighted_edits.to_styled_text(&style.text);
9822 let line_count = highlighted_edits.text.lines().count();
9823
9824 const BORDER_WIDTH: Pixels = px(1.);
9825
9826 let keybind = self.render_edit_prediction_keybind(window, cx);
9827 let has_keybind = keybind.is_some();
9828
9829 let mut element = h_flex()
9830 .items_start()
9831 .child(
9832 h_flex()
9833 .bg(cx.theme().colors().editor_background)
9834 .border(BORDER_WIDTH)
9835 .shadow_xs()
9836 .border_color(cx.theme().colors().border)
9837 .rounded_l_lg()
9838 .when(line_count > 1, |el| el.rounded_br_lg())
9839 .pr_1()
9840 .child(styled_text),
9841 )
9842 .child(
9843 h_flex()
9844 .h(line_height + BORDER_WIDTH * 2.)
9845 .px_1p5()
9846 .gap_1()
9847 // Workaround: For some reason, there's a gap if we don't do this
9848 .ml(-BORDER_WIDTH)
9849 .shadow(vec![gpui::BoxShadow {
9850 color: gpui::black().opacity(0.05),
9851 offset: point(px(1.), px(1.)),
9852 blur_radius: px(2.),
9853 spread_radius: px(0.),
9854 }])
9855 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9856 .border(BORDER_WIDTH)
9857 .border_color(cx.theme().colors().border)
9858 .rounded_r_lg()
9859 .id("edit_prediction_diff_popover_keybind")
9860 .when(!has_keybind, |el| {
9861 let status_colors = cx.theme().status();
9862
9863 el.bg(status_colors.error_background)
9864 .border_color(status_colors.error.opacity(0.6))
9865 .child(Icon::new(IconName::Info).color(Color::Error))
9866 .cursor_default()
9867 .hoverable_tooltip(move |_window, cx| {
9868 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9869 })
9870 })
9871 .children(keybind),
9872 )
9873 .into_any();
9874
9875 let longest_row =
9876 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9877 let longest_line_width = if visible_row_range.contains(&longest_row) {
9878 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9879 } else {
9880 layout_line(
9881 longest_row,
9882 editor_snapshot,
9883 style,
9884 editor_width,
9885 |_| false,
9886 window,
9887 cx,
9888 )
9889 .width
9890 };
9891
9892 let viewport_bounds =
9893 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9894 right: -right_margin,
9895 ..Default::default()
9896 });
9897
9898 let x_after_longest = Pixels::from(
9899 ScrollPixelOffset::from(
9900 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9901 ) - scroll_pixel_position.x,
9902 );
9903
9904 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9905
9906 // Fully visible if it can be displayed within the window (allow overlapping other
9907 // panes). However, this is only allowed if the popover starts within text_bounds.
9908 let can_position_to_the_right = x_after_longest < text_bounds.right()
9909 && x_after_longest + element_bounds.width < viewport_bounds.right();
9910
9911 let mut origin = if can_position_to_the_right {
9912 point(
9913 x_after_longest,
9914 text_bounds.origin.y
9915 + Pixels::from(
9916 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9917 - scroll_pixel_position.y,
9918 ),
9919 )
9920 } else {
9921 let cursor_row = newest_selection_head.map(|head| head.row());
9922 let above_edit = edit_start
9923 .row()
9924 .0
9925 .checked_sub(line_count as u32)
9926 .map(DisplayRow);
9927 let below_edit = Some(edit_end.row() + 1);
9928 let above_cursor =
9929 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9930 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9931
9932 // Place the edit popover adjacent to the edit if there is a location
9933 // available that is onscreen and does not obscure the cursor. Otherwise,
9934 // place it adjacent to the cursor.
9935 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9936 .into_iter()
9937 .flatten()
9938 .find(|&start_row| {
9939 let end_row = start_row + line_count as u32;
9940 visible_row_range.contains(&start_row)
9941 && visible_row_range.contains(&end_row)
9942 && cursor_row
9943 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9944 })?;
9945
9946 content_origin
9947 + point(
9948 Pixels::from(-scroll_pixel_position.x),
9949 Pixels::from(
9950 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9951 ),
9952 )
9953 };
9954
9955 origin.x -= BORDER_WIDTH;
9956
9957 window.with_content_mask(
9958 Some(gpui::ContentMask {
9959 bounds: *text_bounds,
9960 }),
9961 |window| {
9962 window.defer_draw(element, origin, 1, Some(window.content_mask()));
9963 },
9964 );
9965
9966 // Do not return an element, since it will already be drawn due to defer_draw.
9967 None
9968 }
9969
9970 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9971 px(30.)
9972 }
9973
9974 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9975 if self.read_only(cx) {
9976 cx.theme().players().read_only()
9977 } else {
9978 self.style.as_ref().unwrap().local_player
9979 }
9980 }
9981
9982 fn render_edit_prediction_inline_keystroke(
9983 &self,
9984 keystroke: &gpui::KeybindingKeystroke,
9985 modifiers_color: Color,
9986 cx: &App,
9987 ) -> AnyElement {
9988 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9989
9990 h_flex()
9991 .px_0p5()
9992 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9993 .font(
9994 theme_settings::ThemeSettings::get_global(cx)
9995 .buffer_font
9996 .clone(),
9997 )
9998 .text_size(TextSize::XSmall.rems(cx))
9999 .child(h_flex().children(ui::render_modifiers(
10000 keystroke.modifiers(),
10001 PlatformStyle::platform(),
10002 Some(modifiers_color),
10003 Some(IconSize::XSmall.rems().into()),
10004 true,
10005 )))
10006 .when(is_platform_style_mac, |parent| {
10007 parent.child(keystroke.key().to_string())
10008 })
10009 .when(!is_platform_style_mac, |parent| {
10010 parent.child(
10011 Key::new(ui::utils::capitalize(keystroke.key()), Some(Color::Default))
10012 .size(Some(IconSize::XSmall.rems().into())),
10013 )
10014 })
10015 .into_any()
10016 }
10017
10018 fn render_edit_prediction_popover_keystroke(
10019 &self,
10020 keystroke: &gpui::KeybindingKeystroke,
10021 color: Color,
10022 cx: &App,
10023 ) -> AnyElement {
10024 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
10025
10026 if keystroke.modifiers().modified() {
10027 h_flex()
10028 .font(
10029 theme_settings::ThemeSettings::get_global(cx)
10030 .buffer_font
10031 .clone(),
10032 )
10033 .when(is_platform_style_mac, |parent| parent.gap_1())
10034 .child(h_flex().children(ui::render_modifiers(
10035 keystroke.modifiers(),
10036 PlatformStyle::platform(),
10037 Some(color),
10038 None,
10039 false,
10040 )))
10041 .into_any()
10042 } else {
10043 Key::new(ui::utils::capitalize(keystroke.key()), Some(color))
10044 .size(Some(IconSize::XSmall.rems().into()))
10045 .into_any_element()
10046 }
10047 }
10048
10049 fn render_edit_prediction_keybind(
10050 &self,
10051 window: &mut Window,
10052 cx: &mut App,
10053 ) -> Option<AnyElement> {
10054 let keybind_display =
10055 self.edit_prediction_keybind_display(EditPredictionKeybindSurface::Inline, window, cx);
10056 let keystroke = keybind_display.displayed_keystroke.as_ref()?;
10057
10058 let modifiers_color = if *keystroke.modifiers() == window.modifiers() {
10059 Color::Accent
10060 } else {
10061 Color::Muted
10062 };
10063
10064 Some(self.render_edit_prediction_inline_keystroke(keystroke, modifiers_color, cx))
10065 }
10066
10067 fn render_edit_prediction_line_popover(
10068 &self,
10069 label: impl Into<SharedString>,
10070 icon: Option<IconName>,
10071 window: &mut Window,
10072 cx: &mut App,
10073 ) -> Stateful<Div> {
10074 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
10075
10076 let keybind = self.render_edit_prediction_keybind(window, cx);
10077 let has_keybind = keybind.is_some();
10078 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10079
10080 h_flex()
10081 .id("ep-line-popover")
10082 .py_0p5()
10083 .pl_1()
10084 .pr(padding_right)
10085 .gap_1()
10086 .rounded_md()
10087 .border_1()
10088 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10089 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
10090 .shadow_xs()
10091 .when(!has_keybind, |el| {
10092 let status_colors = cx.theme().status();
10093
10094 el.bg(status_colors.error_background)
10095 .border_color(status_colors.error.opacity(0.6))
10096 .pl_2()
10097 .child(Icon::new(icons.error).color(Color::Error))
10098 .cursor_default()
10099 .hoverable_tooltip(move |_window, cx| {
10100 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
10101 })
10102 })
10103 .children(keybind)
10104 .child(
10105 Label::new(label)
10106 .size(LabelSize::Small)
10107 .when(!has_keybind, |el| {
10108 el.color(cx.theme().status().error.into()).strikethrough()
10109 }),
10110 )
10111 .when(!has_keybind, |el| {
10112 el.child(
10113 h_flex().ml_1().child(
10114 Icon::new(IconName::Info)
10115 .size(IconSize::Small)
10116 .color(cx.theme().status().error.into()),
10117 ),
10118 )
10119 })
10120 .when_some(icon, |element, icon| {
10121 element.child(
10122 div()
10123 .mt(px(1.5))
10124 .child(Icon::new(icon).size(IconSize::Small)),
10125 )
10126 })
10127 }
10128
10129 fn render_edit_prediction_jump_outside_popover(
10130 &self,
10131 snapshot: &BufferSnapshot,
10132 window: &mut Window,
10133 cx: &mut App,
10134 ) -> Stateful<Div> {
10135 let keybind = self.render_edit_prediction_keybind(window, cx);
10136 let has_keybind = keybind.is_some();
10137 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10138
10139 let file_name = snapshot
10140 .file()
10141 .map(|file| SharedString::new(file.file_name(cx)))
10142 .unwrap_or(SharedString::new_static("untitled"));
10143
10144 h_flex()
10145 .id("ep-jump-outside-popover")
10146 .py_1()
10147 .px_2()
10148 .gap_1()
10149 .rounded_md()
10150 .border_1()
10151 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10152 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
10153 .shadow_xs()
10154 .when(!has_keybind, |el| {
10155 let status_colors = cx.theme().status();
10156
10157 el.bg(status_colors.error_background)
10158 .border_color(status_colors.error.opacity(0.6))
10159 .pl_2()
10160 .child(Icon::new(icons.error).color(Color::Error))
10161 .cursor_default()
10162 .hoverable_tooltip(move |_window, cx| {
10163 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
10164 })
10165 })
10166 .children(keybind)
10167 .child(
10168 Label::new(file_name)
10169 .size(LabelSize::Small)
10170 .buffer_font(cx)
10171 .when(!has_keybind, |el| {
10172 el.color(cx.theme().status().error.into()).strikethrough()
10173 }),
10174 )
10175 .when(!has_keybind, |el| {
10176 el.child(
10177 h_flex().ml_1().child(
10178 Icon::new(IconName::Info)
10179 .size(IconSize::Small)
10180 .color(cx.theme().status().error.into()),
10181 ),
10182 )
10183 })
10184 .child(
10185 div()
10186 .mt(px(1.5))
10187 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
10188 )
10189 }
10190
10191 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
10192 let accent_color = cx.theme().colors().text_accent;
10193 let editor_bg_color = cx.theme().colors().editor_background;
10194 editor_bg_color.blend(accent_color.opacity(0.1))
10195 }
10196
10197 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
10198 let accent_color = cx.theme().colors().text_accent;
10199 let editor_bg_color = cx.theme().colors().editor_background;
10200 editor_bg_color.blend(accent_color.opacity(0.6))
10201 }
10202 fn get_prediction_provider_icons(
10203 provider: &Option<RegisteredEditPredictionDelegate>,
10204 cx: &App,
10205 ) -> edit_prediction_types::EditPredictionIconSet {
10206 match provider {
10207 Some(provider) => provider.provider.icons(cx),
10208 None => edit_prediction_types::EditPredictionIconSet::new(IconName::ZedPredict),
10209 }
10210 }
10211
10212 fn render_edit_prediction_cursor_popover(
10213 &self,
10214 min_width: Pixels,
10215 max_width: Pixels,
10216 cursor_point: Point,
10217 style: &EditorStyle,
10218 window: &mut Window,
10219 cx: &mut Context<Editor>,
10220 ) -> Option<AnyElement> {
10221 let provider = self.edit_prediction_provider.as_ref()?;
10222 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10223
10224 let is_refreshing = provider.provider.is_refreshing(cx);
10225
10226 fn pending_completion_container(icon: IconName) -> Div {
10227 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
10228 }
10229
10230 let completion = match &self.active_edit_prediction {
10231 Some(prediction) => {
10232 if !self.has_visible_completions_menu() {
10233 const RADIUS: Pixels = px(6.);
10234 const BORDER_WIDTH: Pixels = px(1.);
10235 let keybind_display = self.edit_prediction_keybind_display(
10236 EditPredictionKeybindSurface::CursorPopoverCompact,
10237 window,
10238 cx,
10239 );
10240
10241 return Some(
10242 h_flex()
10243 .elevation_2(cx)
10244 .border(BORDER_WIDTH)
10245 .border_color(cx.theme().colors().border)
10246 .when(keybind_display.missing_accept_keystroke, |el| {
10247 el.border_color(cx.theme().status().error)
10248 })
10249 .rounded(RADIUS)
10250 .rounded_tl(px(0.))
10251 .overflow_hidden()
10252 .child(div().px_1p5().child(match &prediction.completion {
10253 EditPrediction::MoveWithin { target, snapshot } => {
10254 use text::ToPoint as _;
10255 if target.text_anchor_in(&snapshot).to_point(snapshot).row
10256 > cursor_point.row
10257 {
10258 Icon::new(icons.down)
10259 } else {
10260 Icon::new(icons.up)
10261 }
10262 }
10263 EditPrediction::MoveOutside { .. } => {
10264 // TODO [zeta2] custom icon for external jump?
10265 Icon::new(icons.base)
10266 }
10267 EditPrediction::Edit { .. } => Icon::new(icons.base),
10268 }))
10269 .child(
10270 h_flex()
10271 .gap_1()
10272 .py_1()
10273 .px_2()
10274 .rounded_r(RADIUS - BORDER_WIDTH)
10275 .border_l_1()
10276 .border_color(cx.theme().colors().border)
10277 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10278 .when(keybind_display.show_hold_label, |el| {
10279 el.child(
10280 Label::new("Hold")
10281 .size(LabelSize::Small)
10282 .when(
10283 keybind_display.missing_accept_keystroke,
10284 |el| el.strikethrough(),
10285 )
10286 .line_height_style(LineHeightStyle::UiLabel),
10287 )
10288 })
10289 .id("edit_prediction_cursor_popover_keybind")
10290 .when(keybind_display.missing_accept_keystroke, |el| {
10291 let status_colors = cx.theme().status();
10292
10293 el.bg(status_colors.error_background)
10294 .border_color(status_colors.error.opacity(0.6))
10295 .child(Icon::new(IconName::Info).color(Color::Error))
10296 .cursor_default()
10297 .hoverable_tooltip(move |_window, cx| {
10298 cx.new(|_| MissingEditPredictionKeybindingTooltip)
10299 .into()
10300 })
10301 })
10302 .when_some(
10303 keybind_display.displayed_keystroke.as_ref(),
10304 |el, compact_keystroke| {
10305 el.child(self.render_edit_prediction_popover_keystroke(
10306 compact_keystroke,
10307 Color::Default,
10308 cx,
10309 ))
10310 },
10311 ),
10312 )
10313 .into_any(),
10314 );
10315 }
10316
10317 self.render_edit_prediction_cursor_popover_preview(
10318 prediction,
10319 cursor_point,
10320 style,
10321 cx,
10322 )?
10323 }
10324
10325 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
10326 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
10327 stale_completion,
10328 cursor_point,
10329 style,
10330 cx,
10331 )?,
10332
10333 None => pending_completion_container(icons.base)
10334 .child(Label::new("...").size(LabelSize::Small)),
10335 },
10336
10337 None => pending_completion_container(icons.base)
10338 .child(Label::new("...").size(LabelSize::Small)),
10339 };
10340
10341 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
10342 completion
10343 .with_animation(
10344 "loading-completion",
10345 Animation::new(Duration::from_secs(2))
10346 .repeat()
10347 .with_easing(pulsating_between(0.4, 0.8)),
10348 |label, delta| label.opacity(delta),
10349 )
10350 .into_any_element()
10351 } else {
10352 completion.into_any_element()
10353 };
10354
10355 let has_completion = self.active_edit_prediction.is_some();
10356 let keybind_display = self.edit_prediction_keybind_display(
10357 EditPredictionKeybindSurface::CursorPopoverExpanded,
10358 window,
10359 cx,
10360 );
10361
10362 Some(
10363 h_flex()
10364 .min_w(min_width)
10365 .max_w(max_width)
10366 .flex_1()
10367 .elevation_2(cx)
10368 .border_color(cx.theme().colors().border)
10369 .child(
10370 div()
10371 .flex_1()
10372 .py_1()
10373 .px_2()
10374 .overflow_hidden()
10375 .child(completion),
10376 )
10377 .when_some(
10378 keybind_display.displayed_keystroke.as_ref(),
10379 |el, keystroke| {
10380 let key_color = if !has_completion {
10381 Color::Muted
10382 } else {
10383 Color::Default
10384 };
10385
10386 if keybind_display.action == EditPredictionKeybindAction::Preview {
10387 el.child(
10388 h_flex()
10389 .h_full()
10390 .border_l_1()
10391 .rounded_r_lg()
10392 .border_color(cx.theme().colors().border)
10393 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10394 .gap_1()
10395 .py_1()
10396 .px_2()
10397 .child(self.render_edit_prediction_popover_keystroke(
10398 keystroke, key_color, cx,
10399 ))
10400 .child(Label::new("Preview").into_any_element())
10401 .opacity(if has_completion { 1.0 } else { 0.4 }),
10402 )
10403 } else {
10404 el.child(
10405 h_flex()
10406 .h_full()
10407 .border_l_1()
10408 .rounded_r_lg()
10409 .border_color(cx.theme().colors().border)
10410 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10411 .gap_1()
10412 .py_1()
10413 .px_2()
10414 .child(self.render_edit_prediction_popover_keystroke(
10415 keystroke, key_color, cx,
10416 ))
10417 .opacity(if has_completion { 1.0 } else { 0.4 }),
10418 )
10419 }
10420 },
10421 )
10422 .into_any(),
10423 )
10424 }
10425
10426 fn render_edit_prediction_cursor_popover_preview(
10427 &self,
10428 completion: &EditPredictionState,
10429 cursor_point: Point,
10430 style: &EditorStyle,
10431 cx: &mut Context<Editor>,
10432 ) -> Option<Div> {
10433 use text::ToPoint as _;
10434
10435 fn render_relative_row_jump(
10436 prefix: impl Into<String>,
10437 current_row: u32,
10438 target_row: u32,
10439 ) -> Div {
10440 let (row_diff, arrow) = if target_row < current_row {
10441 (current_row - target_row, IconName::ArrowUp)
10442 } else {
10443 (target_row - current_row, IconName::ArrowDown)
10444 };
10445
10446 h_flex()
10447 .child(
10448 Label::new(format!("{}{}", prefix.into(), row_diff))
10449 .color(Color::Muted)
10450 .size(LabelSize::Small),
10451 )
10452 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
10453 }
10454
10455 let supports_jump = self
10456 .edit_prediction_provider
10457 .as_ref()
10458 .map(|provider| provider.provider.supports_jump_to_edit())
10459 .unwrap_or(true);
10460
10461 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10462
10463 match &completion.completion {
10464 EditPrediction::MoveWithin {
10465 target, snapshot, ..
10466 } => {
10467 if !supports_jump {
10468 return None;
10469 }
10470 let (target, _) = self.display_snapshot(cx).anchor_to_buffer_anchor(*target)?;
10471
10472 Some(
10473 h_flex()
10474 .px_2()
10475 .gap_2()
10476 .flex_1()
10477 .child(if target.to_point(snapshot).row > cursor_point.row {
10478 Icon::new(icons.down)
10479 } else {
10480 Icon::new(icons.up)
10481 })
10482 .child(Label::new("Jump to Edit")),
10483 )
10484 }
10485 EditPrediction::MoveOutside { snapshot, .. } => {
10486 let file_name = snapshot
10487 .file()
10488 .map(|file| file.file_name(cx))
10489 .unwrap_or("untitled");
10490 Some(
10491 h_flex()
10492 .px_2()
10493 .gap_2()
10494 .flex_1()
10495 .child(Icon::new(icons.base))
10496 .child(Label::new(format!("Jump to {file_name}"))),
10497 )
10498 }
10499 EditPrediction::Edit {
10500 edits,
10501 edit_preview,
10502 snapshot,
10503 ..
10504 } => {
10505 let first_edit_row = self
10506 .display_snapshot(cx)
10507 .anchor_to_buffer_anchor(edits.first()?.0.start)?
10508 .0
10509 .to_point(snapshot)
10510 .row;
10511
10512 let (highlighted_edits, has_more_lines) =
10513 if let Some(edit_preview) = edit_preview.as_ref() {
10514 crate::edit_prediction_edit_text(
10515 snapshot,
10516 edits,
10517 edit_preview,
10518 true,
10519 &self.display_snapshot(cx),
10520 cx,
10521 )
10522 .first_line_preview()
10523 } else {
10524 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
10525 };
10526
10527 let styled_text = gpui::StyledText::new(highlighted_edits.text)
10528 .with_default_highlights(&style.text, highlighted_edits.highlights);
10529
10530 let preview = h_flex()
10531 .gap_1()
10532 .min_w_16()
10533 .child(styled_text)
10534 .when(has_more_lines, |parent| parent.child("…"));
10535
10536 let left = if supports_jump && first_edit_row != cursor_point.row {
10537 render_relative_row_jump("", cursor_point.row, first_edit_row)
10538 .into_any_element()
10539 } else {
10540 Icon::new(icons.base).into_any_element()
10541 };
10542
10543 Some(
10544 h_flex()
10545 .h_full()
10546 .flex_1()
10547 .gap_2()
10548 .pr_1()
10549 .overflow_x_hidden()
10550 .font(
10551 theme_settings::ThemeSettings::get_global(cx)
10552 .buffer_font
10553 .clone(),
10554 )
10555 .child(left)
10556 .child(preview),
10557 )
10558 }
10559 }
10560 }
10561
10562 pub fn render_context_menu(
10563 &mut self,
10564 max_height_in_lines: u32,
10565 window: &mut Window,
10566 cx: &mut Context<Editor>,
10567 ) -> Option<AnyElement> {
10568 let menu = self.context_menu.borrow();
10569 let menu = menu.as_ref()?;
10570 if !menu.visible() {
10571 return None;
10572 };
10573 self.style
10574 .as_ref()
10575 .map(|style| menu.render(style, max_height_in_lines, window, cx))
10576 }
10577
10578 fn render_context_menu_aside(
10579 &mut self,
10580 max_size: Size<Pixels>,
10581 window: &mut Window,
10582 cx: &mut Context<Editor>,
10583 ) -> Option<AnyElement> {
10584 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
10585 if menu.visible() {
10586 menu.render_aside(max_size, window, cx)
10587 } else {
10588 None
10589 }
10590 })
10591 }
10592
10593 fn hide_context_menu(
10594 &mut self,
10595 window: &mut Window,
10596 cx: &mut Context<Self>,
10597 ) -> Option<CodeContextMenu> {
10598 cx.notify();
10599 self.completion_tasks.clear();
10600 let context_menu = self.context_menu.borrow_mut().take();
10601 self.stale_edit_prediction_in_menu.take();
10602 self.update_visible_edit_prediction(window, cx);
10603 if let Some(CodeContextMenu::Completions(_)) = &context_menu
10604 && let Some(completion_provider) = &self.completion_provider
10605 {
10606 completion_provider.selection_changed(None, window, cx);
10607 }
10608 context_menu
10609 }
10610
10611 fn show_snippet_choices(
10612 &mut self,
10613 choices: &Vec<String>,
10614 selection: Range<Anchor>,
10615 cx: &mut Context<Self>,
10616 ) {
10617 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
10618 let Some((buffer_snapshot, range)) =
10619 buffer_snapshot.anchor_range_to_buffer_anchor_range(selection.clone())
10620 else {
10621 return;
10622 };
10623 let Some(buffer) = self.buffer.read(cx).buffer(buffer_snapshot.remote_id()) else {
10624 return;
10625 };
10626
10627 let id = post_inc(&mut self.next_completion_id);
10628 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
10629 let mut context_menu = self.context_menu.borrow_mut();
10630 let old_menu = context_menu.take();
10631 *context_menu = Some(CodeContextMenu::Completions(
10632 CompletionsMenu::new_snippet_choices(
10633 id,
10634 true,
10635 choices,
10636 selection.start,
10637 range,
10638 buffer,
10639 old_menu.map(|menu| menu.primary_scroll_handle()),
10640 snippet_sort_order,
10641 ),
10642 ));
10643 }
10644
10645 pub fn insert_snippet(
10646 &mut self,
10647 insertion_ranges: &[Range<MultiBufferOffset>],
10648 snippet: Snippet,
10649 window: &mut Window,
10650 cx: &mut Context<Self>,
10651 ) -> Result<()> {
10652 struct Tabstop<T> {
10653 is_end_tabstop: bool,
10654 ranges: Vec<Range<T>>,
10655 choices: Option<Vec<String>>,
10656 }
10657
10658 let tabstops = self.buffer.update(cx, |buffer, cx| {
10659 let snippet_text: Arc<str> = snippet.text.clone().into();
10660 let edits = insertion_ranges
10661 .iter()
10662 .cloned()
10663 .map(|range| (range, snippet_text.clone()));
10664 let autoindent_mode = AutoindentMode::Block {
10665 original_indent_columns: Vec::new(),
10666 };
10667 buffer.edit(edits, Some(autoindent_mode), cx);
10668
10669 let snapshot = &*buffer.read(cx);
10670 let snippet = &snippet;
10671 snippet
10672 .tabstops
10673 .iter()
10674 .map(|tabstop| {
10675 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10676 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10677 });
10678 let mut tabstop_ranges = tabstop
10679 .ranges
10680 .iter()
10681 .flat_map(|tabstop_range| {
10682 let mut delta = 0_isize;
10683 insertion_ranges.iter().map(move |insertion_range| {
10684 let insertion_start = insertion_range.start + delta;
10685 delta += snippet.text.len() as isize
10686 - (insertion_range.end - insertion_range.start) as isize;
10687
10688 let start =
10689 (insertion_start + tabstop_range.start).min(snapshot.len());
10690 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10691 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10692 })
10693 })
10694 .collect::<Vec<_>>();
10695 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10696
10697 Tabstop {
10698 is_end_tabstop,
10699 ranges: tabstop_ranges,
10700 choices: tabstop.choices.clone(),
10701 }
10702 })
10703 .collect::<Vec<_>>()
10704 });
10705 if let Some(tabstop) = tabstops.first() {
10706 self.change_selections(Default::default(), window, cx, |s| {
10707 // Reverse order so that the first range is the newest created selection.
10708 // Completions will use it and autoscroll will prioritize it.
10709 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10710 });
10711
10712 if let Some(choices) = &tabstop.choices
10713 && let Some(selection) = tabstop.ranges.first()
10714 {
10715 self.show_snippet_choices(choices, selection.clone(), cx)
10716 }
10717
10718 // If we're already at the last tabstop and it's at the end of the snippet,
10719 // we're done, we don't need to keep the state around.
10720 if !tabstop.is_end_tabstop {
10721 let choices = tabstops
10722 .iter()
10723 .map(|tabstop| tabstop.choices.clone())
10724 .collect();
10725
10726 let ranges = tabstops
10727 .into_iter()
10728 .map(|tabstop| tabstop.ranges)
10729 .collect::<Vec<_>>();
10730
10731 self.snippet_stack.push(SnippetState {
10732 active_index: 0,
10733 ranges,
10734 choices,
10735 });
10736 }
10737
10738 // Check whether the just-entered snippet ends with an auto-closable bracket.
10739 if self.autoclose_regions.is_empty() {
10740 let snapshot = self.buffer.read(cx).snapshot(cx);
10741 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10742 let selection_head = selection.head();
10743 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10744 continue;
10745 };
10746
10747 let mut bracket_pair = None;
10748 let max_lookup_length = scope
10749 .brackets()
10750 .map(|(pair, _)| {
10751 pair.start
10752 .as_str()
10753 .chars()
10754 .count()
10755 .max(pair.end.as_str().chars().count())
10756 })
10757 .max();
10758 if let Some(max_lookup_length) = max_lookup_length {
10759 let next_text = snapshot
10760 .chars_at(selection_head)
10761 .take(max_lookup_length)
10762 .collect::<String>();
10763 let prev_text = snapshot
10764 .reversed_chars_at(selection_head)
10765 .take(max_lookup_length)
10766 .collect::<String>();
10767
10768 for (pair, enabled) in scope.brackets() {
10769 if enabled
10770 && pair.close
10771 && prev_text.starts_with(pair.start.as_str())
10772 && next_text.starts_with(pair.end.as_str())
10773 {
10774 bracket_pair = Some(pair.clone());
10775 break;
10776 }
10777 }
10778 }
10779
10780 if let Some(pair) = bracket_pair {
10781 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10782 let autoclose_enabled =
10783 self.use_autoclose && snapshot_settings.use_autoclose;
10784 if autoclose_enabled {
10785 let start = snapshot.anchor_after(selection_head);
10786 let end = snapshot.anchor_after(selection_head);
10787 self.autoclose_regions.push(AutocloseRegion {
10788 selection_id: selection.id,
10789 range: start..end,
10790 pair,
10791 });
10792 }
10793 }
10794 }
10795 }
10796 }
10797 Ok(())
10798 }
10799
10800 pub fn move_to_next_snippet_tabstop(
10801 &mut self,
10802 window: &mut Window,
10803 cx: &mut Context<Self>,
10804 ) -> bool {
10805 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10806 }
10807
10808 pub fn move_to_prev_snippet_tabstop(
10809 &mut self,
10810 window: &mut Window,
10811 cx: &mut Context<Self>,
10812 ) -> bool {
10813 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10814 }
10815
10816 pub fn move_to_snippet_tabstop(
10817 &mut self,
10818 bias: Bias,
10819 window: &mut Window,
10820 cx: &mut Context<Self>,
10821 ) -> bool {
10822 if let Some(mut snippet) = self.snippet_stack.pop() {
10823 match bias {
10824 Bias::Left => {
10825 if snippet.active_index > 0 {
10826 snippet.active_index -= 1;
10827 } else {
10828 self.snippet_stack.push(snippet);
10829 return false;
10830 }
10831 }
10832 Bias::Right => {
10833 if snippet.active_index + 1 < snippet.ranges.len() {
10834 snippet.active_index += 1;
10835 } else {
10836 self.snippet_stack.push(snippet);
10837 return false;
10838 }
10839 }
10840 }
10841 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10842 self.change_selections(Default::default(), window, cx, |s| {
10843 // Reverse order so that the first range is the newest created selection.
10844 // Completions will use it and autoscroll will prioritize it.
10845 s.select_ranges(current_ranges.iter().rev().cloned())
10846 });
10847
10848 if let Some(choices) = &snippet.choices[snippet.active_index]
10849 && let Some(selection) = current_ranges.first()
10850 {
10851 self.show_snippet_choices(choices, selection.clone(), cx);
10852 }
10853
10854 // If snippet state is not at the last tabstop, push it back on the stack
10855 if snippet.active_index + 1 < snippet.ranges.len() {
10856 self.snippet_stack.push(snippet);
10857 }
10858 return true;
10859 }
10860 }
10861
10862 false
10863 }
10864
10865 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10866 self.transact(window, cx, |this, window, cx| {
10867 this.select_all(&SelectAll, window, cx);
10868 this.insert("", window, cx);
10869 });
10870 }
10871
10872 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10873 if self.read_only(cx) {
10874 return;
10875 }
10876 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10877 self.transact(window, cx, |this, window, cx| {
10878 this.select_autoclose_pair(window, cx);
10879
10880 let linked_edits = this.linked_edits_for_selections(Arc::from(""), cx);
10881
10882 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10883 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10884 for selection in &mut selections {
10885 if selection.is_empty() {
10886 let old_head = selection.head();
10887 let mut new_head =
10888 movement::left(&display_map, old_head.to_display_point(&display_map))
10889 .to_point(&display_map);
10890 if let Some((buffer, line_buffer_range)) = display_map
10891 .buffer_snapshot()
10892 .buffer_line_for_row(MultiBufferRow(old_head.row))
10893 {
10894 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10895 let indent_len = match indent_size.kind {
10896 IndentKind::Space => {
10897 buffer.settings_at(line_buffer_range.start, cx).tab_size
10898 }
10899 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10900 };
10901 if old_head.column <= indent_size.len && old_head.column > 0 {
10902 let indent_len = indent_len.get();
10903 new_head = cmp::min(
10904 new_head,
10905 MultiBufferPoint::new(
10906 old_head.row,
10907 ((old_head.column - 1) / indent_len) * indent_len,
10908 ),
10909 );
10910 }
10911 }
10912
10913 selection.set_head(new_head, SelectionGoal::None);
10914 }
10915 }
10916
10917 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10918 this.insert("", window, cx);
10919 linked_edits.apply_with_left_expansion(cx);
10920 this.refresh_edit_prediction(true, false, window, cx);
10921 refresh_linked_ranges(this, window, cx);
10922 });
10923 }
10924
10925 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10926 if self.read_only(cx) {
10927 return;
10928 }
10929 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10930 self.transact(window, cx, |this, window, cx| {
10931 this.change_selections(Default::default(), window, cx, |s| {
10932 s.move_with(&mut |map, selection| {
10933 if selection.is_empty() {
10934 let cursor = movement::right(map, selection.head());
10935 selection.end = cursor;
10936 selection.reversed = true;
10937 selection.goal = SelectionGoal::None;
10938 }
10939 })
10940 });
10941 let linked_edits = this.linked_edits_for_selections(Arc::from(""), cx);
10942 this.insert("", window, cx);
10943 linked_edits.apply(cx);
10944 this.refresh_edit_prediction(true, false, window, cx);
10945 refresh_linked_ranges(this, window, cx);
10946 });
10947 }
10948
10949 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10950 if self.mode.is_single_line() {
10951 cx.propagate();
10952 return;
10953 }
10954
10955 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10956 if self.move_to_prev_snippet_tabstop(window, cx) {
10957 return;
10958 }
10959 self.outdent(&Outdent, window, cx);
10960 }
10961
10962 pub fn next_snippet_tabstop(
10963 &mut self,
10964 _: &NextSnippetTabstop,
10965 window: &mut Window,
10966 cx: &mut Context<Self>,
10967 ) {
10968 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10969 cx.propagate();
10970 return;
10971 }
10972
10973 if self.move_to_next_snippet_tabstop(window, cx) {
10974 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10975 return;
10976 }
10977 cx.propagate();
10978 }
10979
10980 pub fn previous_snippet_tabstop(
10981 &mut self,
10982 _: &PreviousSnippetTabstop,
10983 window: &mut Window,
10984 cx: &mut Context<Self>,
10985 ) {
10986 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10987 cx.propagate();
10988 return;
10989 }
10990
10991 if self.move_to_prev_snippet_tabstop(window, cx) {
10992 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10993 return;
10994 }
10995 cx.propagate();
10996 }
10997
10998 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10999 if self.mode.is_single_line() {
11000 cx.propagate();
11001 return;
11002 }
11003
11004 if self.move_to_next_snippet_tabstop(window, cx) {
11005 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11006 return;
11007 }
11008 if self.read_only(cx) {
11009 return;
11010 }
11011 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11012 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
11013 let buffer = self.buffer.read(cx);
11014 let snapshot = buffer.snapshot(cx);
11015 let rows_iter = selections.iter().map(|s| s.head().row);
11016 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
11017
11018 let has_some_cursor_in_whitespace = selections
11019 .iter()
11020 .filter(|selection| selection.is_empty())
11021 .any(|selection| {
11022 let cursor = selection.head();
11023 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
11024 cursor.column < current_indent.len
11025 });
11026
11027 let mut edits = Vec::new();
11028 let mut prev_edited_row = 0;
11029 let mut row_delta = 0;
11030 for selection in &mut selections {
11031 if selection.start.row != prev_edited_row {
11032 row_delta = 0;
11033 }
11034 prev_edited_row = selection.end.row;
11035
11036 // If cursor is after a list prefix, make selection non-empty to trigger line indent
11037 if selection.is_empty() {
11038 let cursor = selection.head();
11039 let settings = buffer.language_settings_at(cursor, cx);
11040 if settings.indent_list_on_tab {
11041 if let Some(language) = snapshot.language_scope_at(Point::new(cursor.row, 0)) {
11042 if is_list_prefix_row(MultiBufferRow(cursor.row), &snapshot, &language) {
11043 row_delta = Self::indent_selection(
11044 buffer, &snapshot, selection, &mut edits, row_delta, cx,
11045 );
11046 continue;
11047 }
11048 }
11049 }
11050 }
11051
11052 // If the selection is non-empty, then increase the indentation of the selected lines.
11053 if !selection.is_empty() {
11054 row_delta =
11055 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
11056 continue;
11057 }
11058
11059 let cursor = selection.head();
11060 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
11061 if let Some(suggested_indent) =
11062 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
11063 {
11064 // Don't do anything if already at suggested indent
11065 // and there is any other cursor which is not
11066 if has_some_cursor_in_whitespace
11067 && cursor.column == current_indent.len
11068 && current_indent.len == suggested_indent.len
11069 {
11070 continue;
11071 }
11072
11073 // Adjust line and move cursor to suggested indent
11074 // if cursor is not at suggested indent
11075 if cursor.column < suggested_indent.len
11076 && cursor.column <= current_indent.len
11077 && current_indent.len <= suggested_indent.len
11078 {
11079 selection.start = Point::new(cursor.row, suggested_indent.len);
11080 selection.end = selection.start;
11081 if row_delta == 0 {
11082 edits.extend(Buffer::edit_for_indent_size_adjustment(
11083 cursor.row,
11084 current_indent,
11085 suggested_indent,
11086 ));
11087 row_delta = suggested_indent.len - current_indent.len;
11088 }
11089 continue;
11090 }
11091
11092 // If current indent is more than suggested indent
11093 // only move cursor to current indent and skip indent
11094 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
11095 selection.start = Point::new(cursor.row, current_indent.len);
11096 selection.end = selection.start;
11097 continue;
11098 }
11099 }
11100
11101 // Otherwise, insert a hard or soft tab.
11102 let settings = buffer.language_settings_at(cursor, cx);
11103 let tab_size = if settings.hard_tabs {
11104 IndentSize::tab()
11105 } else {
11106 let tab_size = settings.tab_size.get();
11107 let indent_remainder = snapshot
11108 .text_for_range(Point::new(cursor.row, 0)..cursor)
11109 .flat_map(str::chars)
11110 .fold(row_delta % tab_size, |counter: u32, c| {
11111 if c == '\t' {
11112 0
11113 } else {
11114 (counter + 1) % tab_size
11115 }
11116 });
11117
11118 let chars_to_next_tab_stop = tab_size - indent_remainder;
11119 IndentSize::spaces(chars_to_next_tab_stop)
11120 };
11121 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
11122 selection.end = selection.start;
11123 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
11124 row_delta += tab_size.len;
11125 }
11126
11127 self.transact(window, cx, |this, window, cx| {
11128 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
11129 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11130 this.refresh_edit_prediction(true, false, window, cx);
11131 });
11132 }
11133
11134 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
11135 if self.read_only(cx) {
11136 return;
11137 }
11138 if self.mode.is_single_line() {
11139 cx.propagate();
11140 return;
11141 }
11142
11143 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11144 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
11145 let mut prev_edited_row = 0;
11146 let mut row_delta = 0;
11147 let mut edits = Vec::new();
11148 let buffer = self.buffer.read(cx);
11149 let snapshot = buffer.snapshot(cx);
11150 for selection in &mut selections {
11151 if selection.start.row != prev_edited_row {
11152 row_delta = 0;
11153 }
11154 prev_edited_row = selection.end.row;
11155
11156 row_delta =
11157 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
11158 }
11159
11160 self.transact(window, cx, |this, window, cx| {
11161 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
11162 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11163 });
11164 }
11165
11166 fn indent_selection(
11167 buffer: &MultiBuffer,
11168 snapshot: &MultiBufferSnapshot,
11169 selection: &mut Selection<Point>,
11170 edits: &mut Vec<(Range<Point>, String)>,
11171 delta_for_start_row: u32,
11172 cx: &App,
11173 ) -> u32 {
11174 let settings = buffer.language_settings_at(selection.start, cx);
11175 let tab_size = settings.tab_size.get();
11176 let indent_kind = if settings.hard_tabs {
11177 IndentKind::Tab
11178 } else {
11179 IndentKind::Space
11180 };
11181 let mut start_row = selection.start.row;
11182 let mut end_row = selection.end.row + 1;
11183
11184 // If a selection ends at the beginning of a line, don't indent
11185 // that last line.
11186 if selection.end.column == 0 && selection.end.row > selection.start.row {
11187 end_row -= 1;
11188 }
11189
11190 // Avoid re-indenting a row that has already been indented by a
11191 // previous selection, but still update this selection's column
11192 // to reflect that indentation.
11193 if delta_for_start_row > 0 {
11194 start_row += 1;
11195 selection.start.column += delta_for_start_row;
11196 if selection.end.row == selection.start.row {
11197 selection.end.column += delta_for_start_row;
11198 }
11199 }
11200
11201 let mut delta_for_end_row = 0;
11202 let has_multiple_rows = start_row + 1 != end_row;
11203 for row in start_row..end_row {
11204 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
11205 let indent_delta = match (current_indent.kind, indent_kind) {
11206 (IndentKind::Space, IndentKind::Space) => {
11207 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
11208 IndentSize::spaces(columns_to_next_tab_stop)
11209 }
11210 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
11211 (_, IndentKind::Tab) => IndentSize::tab(),
11212 };
11213
11214 let start = if has_multiple_rows || current_indent.len < selection.start.column {
11215 0
11216 } else {
11217 selection.start.column
11218 };
11219 let row_start = Point::new(row, start);
11220 edits.push((
11221 row_start..row_start,
11222 indent_delta.chars().collect::<String>(),
11223 ));
11224
11225 // Update this selection's endpoints to reflect the indentation.
11226 if row == selection.start.row {
11227 selection.start.column += indent_delta.len;
11228 }
11229 if row == selection.end.row {
11230 selection.end.column += indent_delta.len;
11231 delta_for_end_row = indent_delta.len;
11232 }
11233 }
11234
11235 if selection.start.row == selection.end.row {
11236 delta_for_start_row + delta_for_end_row
11237 } else {
11238 delta_for_end_row
11239 }
11240 }
11241
11242 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
11243 if self.read_only(cx) {
11244 return;
11245 }
11246 if self.mode.is_single_line() {
11247 cx.propagate();
11248 return;
11249 }
11250
11251 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11252 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11253 let selections = self.selections.all::<Point>(&display_map);
11254 let mut deletion_ranges = Vec::new();
11255 let mut last_outdent = None;
11256 {
11257 let buffer = self.buffer.read(cx);
11258 let snapshot = buffer.snapshot(cx);
11259 for selection in &selections {
11260 let settings = buffer.language_settings_at(selection.start, cx);
11261 let tab_size = settings.tab_size.get();
11262 let mut rows = selection.spanned_rows(false, &display_map);
11263
11264 // Avoid re-outdenting a row that has already been outdented by a
11265 // previous selection.
11266 if let Some(last_row) = last_outdent
11267 && last_row == rows.start
11268 {
11269 rows.start = rows.start.next_row();
11270 }
11271 let has_multiple_rows = rows.len() > 1;
11272 for row in rows.iter_rows() {
11273 let indent_size = snapshot.indent_size_for_line(row);
11274 if indent_size.len > 0 {
11275 let deletion_len = match indent_size.kind {
11276 IndentKind::Space => {
11277 let columns_to_prev_tab_stop = indent_size.len % tab_size;
11278 if columns_to_prev_tab_stop == 0 {
11279 tab_size
11280 } else {
11281 columns_to_prev_tab_stop
11282 }
11283 }
11284 IndentKind::Tab => 1,
11285 };
11286 let start = if has_multiple_rows
11287 || deletion_len > selection.start.column
11288 || indent_size.len < selection.start.column
11289 {
11290 0
11291 } else {
11292 selection.start.column - deletion_len
11293 };
11294 deletion_ranges.push(
11295 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
11296 );
11297 last_outdent = Some(row);
11298 }
11299 }
11300 }
11301 }
11302
11303 self.transact(window, cx, |this, window, cx| {
11304 this.buffer.update(cx, |buffer, cx| {
11305 let empty_str: Arc<str> = Arc::default();
11306 buffer.edit(
11307 deletion_ranges
11308 .into_iter()
11309 .map(|range| (range, empty_str.clone())),
11310 None,
11311 cx,
11312 );
11313 });
11314 let selections = this
11315 .selections
11316 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11317 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11318 });
11319 }
11320
11321 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
11322 if self.read_only(cx) {
11323 return;
11324 }
11325 if self.mode.is_single_line() {
11326 cx.propagate();
11327 return;
11328 }
11329
11330 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11331 let selections = self
11332 .selections
11333 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11334 .into_iter()
11335 .map(|s| s.range());
11336
11337 self.transact(window, cx, |this, window, cx| {
11338 this.buffer.update(cx, |buffer, cx| {
11339 buffer.autoindent_ranges(selections, cx);
11340 });
11341 let selections = this
11342 .selections
11343 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11344 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11345 });
11346 }
11347
11348 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
11349 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11350 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11351 let selections = self.selections.all::<Point>(&display_map);
11352
11353 let mut new_cursors = Vec::new();
11354 let mut edit_ranges = Vec::new();
11355 let mut selections = selections.iter().peekable();
11356 while let Some(selection) = selections.next() {
11357 let mut rows = selection.spanned_rows(false, &display_map);
11358
11359 // Accumulate contiguous regions of rows that we want to delete.
11360 while let Some(next_selection) = selections.peek() {
11361 let next_rows = next_selection.spanned_rows(false, &display_map);
11362 if next_rows.start <= rows.end {
11363 rows.end = next_rows.end;
11364 selections.next().unwrap();
11365 } else {
11366 break;
11367 }
11368 }
11369
11370 let buffer = display_map.buffer_snapshot();
11371 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
11372 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
11373 // If there's a line after the range, delete the \n from the end of the row range
11374 (
11375 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
11376 rows.end,
11377 )
11378 } else {
11379 // If there isn't a line after the range, delete the \n from the line before the
11380 // start of the row range
11381 edit_start = edit_start.saturating_sub_usize(1);
11382 (buffer.len(), rows.start.previous_row())
11383 };
11384
11385 let text_layout_details = self.text_layout_details(window, cx);
11386 let x = display_map.x_for_display_point(
11387 selection.head().to_display_point(&display_map),
11388 &text_layout_details,
11389 );
11390 let row = Point::new(target_row.0, 0)
11391 .to_display_point(&display_map)
11392 .row();
11393 let column = display_map.display_column_for_x(row, x, &text_layout_details);
11394
11395 new_cursors.push((
11396 selection.id,
11397 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
11398 SelectionGoal::None,
11399 ));
11400 edit_ranges.push(edit_start..edit_end);
11401 }
11402
11403 self.transact(window, cx, |this, window, cx| {
11404 let buffer = this.buffer.update(cx, |buffer, cx| {
11405 let empty_str: Arc<str> = Arc::default();
11406 buffer.edit(
11407 edit_ranges
11408 .into_iter()
11409 .map(|range| (range, empty_str.clone())),
11410 None,
11411 cx,
11412 );
11413 buffer.snapshot(cx)
11414 });
11415 let new_selections = new_cursors
11416 .into_iter()
11417 .map(|(id, cursor, goal)| {
11418 let cursor = cursor.to_point(&buffer);
11419 Selection {
11420 id,
11421 start: cursor,
11422 end: cursor,
11423 reversed: false,
11424 goal,
11425 }
11426 })
11427 .collect();
11428
11429 this.change_selections(Default::default(), window, cx, |s| {
11430 s.select(new_selections);
11431 });
11432 });
11433 }
11434
11435 pub fn join_lines_impl(
11436 &mut self,
11437 insert_whitespace: bool,
11438 window: &mut Window,
11439 cx: &mut Context<Self>,
11440 ) {
11441 if self.read_only(cx) {
11442 return;
11443 }
11444 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
11445 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
11446 let start = MultiBufferRow(selection.start.row);
11447 // Treat single line selections as if they include the next line. Otherwise this action
11448 // would do nothing for single line selections individual cursors.
11449 let end = if selection.start.row == selection.end.row {
11450 MultiBufferRow(selection.start.row + 1)
11451 } else if selection.end.column == 0 {
11452 // If the selection ends at the start of a line, it's logically at the end of the
11453 // previous line (plus its newline).
11454 // Don't include the end line unless there's only one line selected.
11455 if selection.start.row + 1 == selection.end.row {
11456 MultiBufferRow(selection.end.row)
11457 } else {
11458 MultiBufferRow(selection.end.row - 1)
11459 }
11460 } else {
11461 MultiBufferRow(selection.end.row)
11462 };
11463
11464 if let Some(last_row_range) = row_ranges.last_mut()
11465 && start <= last_row_range.end
11466 {
11467 last_row_range.end = end;
11468 continue;
11469 }
11470 row_ranges.push(start..end);
11471 }
11472
11473 let snapshot = self.buffer.read(cx).snapshot(cx);
11474 let mut cursor_positions = Vec::new();
11475 for row_range in &row_ranges {
11476 let anchor = snapshot.anchor_before(Point::new(
11477 row_range.end.previous_row().0,
11478 snapshot.line_len(row_range.end.previous_row()),
11479 ));
11480 cursor_positions.push(anchor..anchor);
11481 }
11482
11483 self.transact(window, cx, |this, window, cx| {
11484 for row_range in row_ranges.into_iter().rev() {
11485 for row in row_range.iter_rows().rev() {
11486 let end_of_line = Point::new(row.0, snapshot.line_len(row));
11487 let next_line_row = row.next_row();
11488 let indent = snapshot.indent_size_for_line(next_line_row);
11489 let mut join_start_column = indent.len;
11490
11491 if let Some(language_scope) =
11492 snapshot.language_scope_at(Point::new(next_line_row.0, indent.len))
11493 {
11494 let line_end =
11495 Point::new(next_line_row.0, snapshot.line_len(next_line_row));
11496 let line_text_after_indent = snapshot
11497 .text_for_range(Point::new(next_line_row.0, indent.len)..line_end)
11498 .collect::<String>();
11499
11500 if !line_text_after_indent.is_empty() {
11501 let block_prefix = language_scope
11502 .block_comment()
11503 .map(|c| c.prefix.as_ref())
11504 .filter(|p| !p.is_empty());
11505 let doc_prefix = language_scope
11506 .documentation_comment()
11507 .map(|c| c.prefix.as_ref())
11508 .filter(|p| !p.is_empty());
11509 let all_prefixes = language_scope
11510 .line_comment_prefixes()
11511 .iter()
11512 .map(|p| p.as_ref())
11513 .chain(block_prefix)
11514 .chain(doc_prefix)
11515 .chain(language_scope.unordered_list().iter().map(|p| p.as_ref()));
11516
11517 let mut longest_prefix_len = None;
11518 for prefix in all_prefixes {
11519 let trimmed = prefix.trim_end();
11520 if line_text_after_indent.starts_with(trimmed) {
11521 let candidate_len =
11522 if line_text_after_indent.starts_with(prefix) {
11523 prefix.len()
11524 } else {
11525 trimmed.len()
11526 };
11527 if longest_prefix_len.map_or(true, |len| candidate_len > len) {
11528 longest_prefix_len = Some(candidate_len);
11529 }
11530 }
11531 }
11532
11533 if let Some(prefix_len) = longest_prefix_len {
11534 join_start_column =
11535 join_start_column.saturating_add(prefix_len as u32);
11536 }
11537 }
11538 }
11539
11540 let start_of_next_line = Point::new(next_line_row.0, join_start_column);
11541
11542 let replace = if snapshot.line_len(next_line_row) > join_start_column
11543 && insert_whitespace
11544 {
11545 " "
11546 } else {
11547 ""
11548 };
11549
11550 this.buffer.update(cx, |buffer, cx| {
11551 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
11552 });
11553 }
11554 }
11555
11556 this.change_selections(Default::default(), window, cx, |s| {
11557 s.select_anchor_ranges(cursor_positions)
11558 });
11559 });
11560 }
11561
11562 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
11563 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11564 self.join_lines_impl(true, window, cx);
11565 }
11566
11567 pub fn sort_lines_case_sensitive(
11568 &mut self,
11569 _: &SortLinesCaseSensitive,
11570 window: &mut Window,
11571 cx: &mut Context<Self>,
11572 ) {
11573 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
11574 }
11575
11576 pub fn sort_lines_by_length(
11577 &mut self,
11578 _: &SortLinesByLength,
11579 window: &mut Window,
11580 cx: &mut Context<Self>,
11581 ) {
11582 self.manipulate_immutable_lines(window, cx, |lines| {
11583 lines.sort_by_key(|&line| line.chars().count())
11584 })
11585 }
11586
11587 pub fn sort_lines_case_insensitive(
11588 &mut self,
11589 _: &SortLinesCaseInsensitive,
11590 window: &mut Window,
11591 cx: &mut Context<Self>,
11592 ) {
11593 self.manipulate_immutable_lines(window, cx, |lines| {
11594 lines.sort_by_key(|line| line.to_lowercase())
11595 })
11596 }
11597
11598 pub fn unique_lines_case_insensitive(
11599 &mut self,
11600 _: &UniqueLinesCaseInsensitive,
11601 window: &mut Window,
11602 cx: &mut Context<Self>,
11603 ) {
11604 self.manipulate_immutable_lines(window, cx, |lines| {
11605 let mut seen = HashSet::default();
11606 lines.retain(|line| seen.insert(line.to_lowercase()));
11607 })
11608 }
11609
11610 pub fn unique_lines_case_sensitive(
11611 &mut self,
11612 _: &UniqueLinesCaseSensitive,
11613 window: &mut Window,
11614 cx: &mut Context<Self>,
11615 ) {
11616 self.manipulate_immutable_lines(window, cx, |lines| {
11617 let mut seen = HashSet::default();
11618 lines.retain(|line| seen.insert(*line));
11619 })
11620 }
11621
11622 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
11623 let snapshot = self.buffer.read(cx).snapshot(cx);
11624 for selection in self.selections.disjoint_anchors_arc().iter() {
11625 if snapshot
11626 .language_at(selection.start)
11627 .and_then(|lang| lang.config().wrap_characters.as_ref())
11628 .is_some()
11629 {
11630 return true;
11631 }
11632 }
11633 false
11634 }
11635
11636 fn wrap_selections_in_tag(
11637 &mut self,
11638 _: &WrapSelectionsInTag,
11639 window: &mut Window,
11640 cx: &mut Context<Self>,
11641 ) {
11642 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11643
11644 let snapshot = self.buffer.read(cx).snapshot(cx);
11645
11646 let mut edits = Vec::new();
11647 let mut boundaries = Vec::new();
11648
11649 for selection in self
11650 .selections
11651 .all_adjusted(&self.display_snapshot(cx))
11652 .iter()
11653 {
11654 let Some(wrap_config) = snapshot
11655 .language_at(selection.start)
11656 .and_then(|lang| lang.config().wrap_characters.clone())
11657 else {
11658 continue;
11659 };
11660
11661 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
11662 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
11663
11664 let start_before = snapshot.anchor_before(selection.start);
11665 let end_after = snapshot.anchor_after(selection.end);
11666
11667 edits.push((start_before..start_before, open_tag));
11668 edits.push((end_after..end_after, close_tag));
11669
11670 boundaries.push((
11671 start_before,
11672 end_after,
11673 wrap_config.start_prefix.len(),
11674 wrap_config.end_suffix.len(),
11675 ));
11676 }
11677
11678 if edits.is_empty() {
11679 return;
11680 }
11681
11682 self.transact(window, cx, |this, window, cx| {
11683 let buffer = this.buffer.update(cx, |buffer, cx| {
11684 buffer.edit(edits, None, cx);
11685 buffer.snapshot(cx)
11686 });
11687
11688 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
11689 for (start_before, end_after, start_prefix_len, end_suffix_len) in
11690 boundaries.into_iter()
11691 {
11692 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
11693 let close_offset = end_after
11694 .to_offset(&buffer)
11695 .saturating_sub_usize(end_suffix_len);
11696 new_selections.push(open_offset..open_offset);
11697 new_selections.push(close_offset..close_offset);
11698 }
11699
11700 this.change_selections(Default::default(), window, cx, |s| {
11701 s.select_ranges(new_selections);
11702 });
11703
11704 this.request_autoscroll(Autoscroll::fit(), cx);
11705 });
11706 }
11707
11708 pub fn toggle_read_only(
11709 &mut self,
11710 _: &workspace::ToggleReadOnlyFile,
11711 _: &mut Window,
11712 cx: &mut Context<Self>,
11713 ) {
11714 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
11715 buffer.update(cx, |buffer, cx| {
11716 buffer.set_capability(
11717 match buffer.capability() {
11718 Capability::ReadWrite => Capability::Read,
11719 Capability::Read => Capability::ReadWrite,
11720 Capability::ReadOnly => Capability::ReadOnly,
11721 },
11722 cx,
11723 );
11724 })
11725 }
11726 }
11727
11728 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11729 let Some(project) = self.project.clone() else {
11730 return;
11731 };
11732 let task = self.reload(project, window, cx);
11733 self.detach_and_notify_err(task, window, cx);
11734 }
11735
11736 pub fn restore_file(
11737 &mut self,
11738 _: &::git::RestoreFile,
11739 window: &mut Window,
11740 cx: &mut Context<Self>,
11741 ) {
11742 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11743 let mut buffer_ids = HashSet::default();
11744 let snapshot = self.buffer().read(cx).snapshot(cx);
11745 for selection in self
11746 .selections
11747 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11748 {
11749 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11750 }
11751
11752 let ranges = buffer_ids
11753 .into_iter()
11754 .flat_map(|buffer_id| snapshot.range_for_buffer(buffer_id))
11755 .collect::<Vec<_>>();
11756
11757 self.restore_hunks_in_ranges(ranges, window, cx);
11758 }
11759
11760 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11761 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11762 let selections = self
11763 .selections
11764 .all(&self.display_snapshot(cx))
11765 .into_iter()
11766 .map(|s| s.range())
11767 .collect();
11768 self.restore_hunks_in_ranges(selections, window, cx);
11769 }
11770
11771 /// Restores the diff hunks in the editor's selections and moves the cursor
11772 /// to the next diff hunk. Wraps around to the beginning of the buffer if
11773 /// not all diff hunks are expanded.
11774 pub fn restore_and_next(
11775 &mut self,
11776 _: &::git::RestoreAndNext,
11777 window: &mut Window,
11778 cx: &mut Context<Self>,
11779 ) {
11780 let selections = self
11781 .selections
11782 .all(&self.display_snapshot(cx))
11783 .into_iter()
11784 .map(|selection| selection.range())
11785 .collect();
11786
11787 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11788 self.restore_hunks_in_ranges(selections, window, cx);
11789
11790 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
11791 let wrap_around = !all_diff_hunks_expanded;
11792 let snapshot = self.snapshot(window, cx);
11793 let position = self
11794 .selections
11795 .newest::<Point>(&snapshot.display_snapshot)
11796 .head();
11797
11798 self.go_to_hunk_before_or_after_position(
11799 &snapshot,
11800 position,
11801 Direction::Next,
11802 wrap_around,
11803 window,
11804 cx,
11805 );
11806 }
11807
11808 pub fn restore_hunks_in_ranges(
11809 &mut self,
11810 ranges: Vec<Range<Point>>,
11811 window: &mut Window,
11812 cx: &mut Context<Editor>,
11813 ) {
11814 if self.delegate_stage_and_restore {
11815 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11816 if !hunks.is_empty() {
11817 cx.emit(EditorEvent::RestoreRequested { hunks });
11818 }
11819 return;
11820 }
11821 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11822 self.transact(window, cx, |editor, window, cx| {
11823 editor.restore_diff_hunks(hunks, cx);
11824 let selections = editor
11825 .selections
11826 .all::<MultiBufferOffset>(&editor.display_snapshot(cx));
11827 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11828 s.select(selections);
11829 });
11830 });
11831 }
11832
11833 pub(crate) fn restore_diff_hunks(&self, hunks: Vec<MultiBufferDiffHunk>, cx: &mut App) {
11834 let mut revert_changes = HashMap::default();
11835 let chunk_by = hunks.into_iter().chunk_by(|hunk| hunk.buffer_id);
11836 for (buffer_id, hunks) in &chunk_by {
11837 let hunks = hunks.collect::<Vec<_>>();
11838 for hunk in &hunks {
11839 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11840 }
11841 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11842 }
11843 if !revert_changes.is_empty() {
11844 self.buffer().update(cx, |multi_buffer, cx| {
11845 for (buffer_id, changes) in revert_changes {
11846 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
11847 buffer.update(cx, |buffer, cx| {
11848 buffer.edit(
11849 changes
11850 .into_iter()
11851 .map(|(range, text)| (range, text.to_string())),
11852 None,
11853 cx,
11854 );
11855 });
11856 }
11857 }
11858 });
11859 }
11860 }
11861
11862 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11863 if let Some(status) = self
11864 .addons
11865 .iter()
11866 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11867 {
11868 return Some(status);
11869 }
11870 self.project
11871 .as_ref()?
11872 .read(cx)
11873 .status_for_buffer_id(buffer_id, cx)
11874 }
11875
11876 pub fn open_active_item_in_terminal(
11877 &mut self,
11878 _: &OpenInTerminal,
11879 window: &mut Window,
11880 cx: &mut Context<Self>,
11881 ) {
11882 if let Some(working_directory) = self.active_buffer(cx).and_then(|buffer| {
11883 let project_path = buffer.read(cx).project_path(cx)?;
11884 let project = self.project()?.read(cx);
11885 let entry = project.entry_for_path(&project_path, cx)?;
11886 let parent = match &entry.canonical_path {
11887 Some(canonical_path) => canonical_path.to_path_buf(),
11888 None => project.absolute_path(&project_path, cx)?,
11889 }
11890 .parent()?
11891 .to_path_buf();
11892 Some(parent)
11893 }) {
11894 window.dispatch_action(
11895 OpenTerminal {
11896 working_directory,
11897 local: false,
11898 }
11899 .boxed_clone(),
11900 cx,
11901 );
11902 }
11903 }
11904
11905 fn set_breakpoint_context_menu(
11906 &mut self,
11907 display_row: DisplayRow,
11908 position: Option<Anchor>,
11909 clicked_point: gpui::Point<Pixels>,
11910 window: &mut Window,
11911 cx: &mut Context<Self>,
11912 ) {
11913 let source = self
11914 .buffer
11915 .read(cx)
11916 .snapshot(cx)
11917 .anchor_before(Point::new(display_row.0, 0u32));
11918
11919 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11920
11921 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11922 self,
11923 source,
11924 clicked_point,
11925 context_menu,
11926 window,
11927 cx,
11928 );
11929 }
11930
11931 fn add_edit_breakpoint_block(
11932 &mut self,
11933 anchor: Anchor,
11934 breakpoint: &Breakpoint,
11935 edit_action: BreakpointPromptEditAction,
11936 window: &mut Window,
11937 cx: &mut Context<Self>,
11938 ) {
11939 let weak_editor = cx.weak_entity();
11940 let bp_prompt = cx.new(|cx| {
11941 BreakpointPromptEditor::new(
11942 weak_editor,
11943 anchor,
11944 breakpoint.clone(),
11945 edit_action,
11946 window,
11947 cx,
11948 )
11949 });
11950
11951 let height = bp_prompt.update(cx, |this, cx| {
11952 this.prompt
11953 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11954 });
11955 let cloned_prompt = bp_prompt.clone();
11956 let blocks = vec![BlockProperties {
11957 style: BlockStyle::Sticky,
11958 placement: BlockPlacement::Above(anchor),
11959 height: Some(height),
11960 render: Arc::new(move |cx| {
11961 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11962 cloned_prompt.clone().into_any_element()
11963 }),
11964 priority: 0,
11965 }];
11966
11967 let focus_handle = bp_prompt.focus_handle(cx);
11968 window.focus(&focus_handle, cx);
11969
11970 let block_ids = self.insert_blocks(blocks, None, cx);
11971 bp_prompt.update(cx, |prompt, _| {
11972 prompt.add_block_ids(block_ids);
11973 });
11974 }
11975
11976 pub(crate) fn breakpoint_at_row(
11977 &self,
11978 row: u32,
11979 window: &mut Window,
11980 cx: &mut Context<Self>,
11981 ) -> Option<(Anchor, Breakpoint)> {
11982 let snapshot = self.snapshot(window, cx);
11983 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11984
11985 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11986 }
11987
11988 pub(crate) fn breakpoint_at_anchor(
11989 &self,
11990 breakpoint_position: Anchor,
11991 snapshot: &EditorSnapshot,
11992 cx: &mut Context<Self>,
11993 ) -> Option<(Anchor, Breakpoint)> {
11994 let (breakpoint_position, _) = snapshot
11995 .buffer_snapshot()
11996 .anchor_to_buffer_anchor(breakpoint_position)?;
11997 let buffer = self.buffer.read(cx).buffer(breakpoint_position.buffer_id)?;
11998
11999 let buffer_snapshot = buffer.read(cx).snapshot();
12000
12001 let row = buffer_snapshot
12002 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position)
12003 .row;
12004
12005 let line_len = buffer_snapshot.line_len(row);
12006 let anchor_end = buffer_snapshot.anchor_after(Point::new(row, line_len));
12007
12008 self.breakpoint_store
12009 .as_ref()?
12010 .read_with(cx, |breakpoint_store, cx| {
12011 breakpoint_store
12012 .breakpoints(
12013 &buffer,
12014 Some(breakpoint_position..anchor_end),
12015 &buffer_snapshot,
12016 cx,
12017 )
12018 .next()
12019 .and_then(|(bp, _)| {
12020 let breakpoint_row = buffer_snapshot
12021 .summary_for_anchor::<text::PointUtf16>(&bp.position)
12022 .row;
12023
12024 if breakpoint_row == row {
12025 snapshot
12026 .buffer_snapshot()
12027 .anchor_in_excerpt(bp.position)
12028 .map(|position| (position, bp.bp.clone()))
12029 } else {
12030 None
12031 }
12032 })
12033 })
12034 }
12035
12036 pub fn edit_log_breakpoint(
12037 &mut self,
12038 _: &EditLogBreakpoint,
12039 window: &mut Window,
12040 cx: &mut Context<Self>,
12041 ) {
12042 if self.breakpoint_store.is_none() {
12043 return;
12044 }
12045
12046 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
12047 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
12048 message: None,
12049 state: BreakpointState::Enabled,
12050 condition: None,
12051 hit_condition: None,
12052 });
12053
12054 self.add_edit_breakpoint_block(
12055 anchor,
12056 &breakpoint,
12057 BreakpointPromptEditAction::Log,
12058 window,
12059 cx,
12060 );
12061 }
12062 }
12063
12064 fn breakpoints_at_cursors(
12065 &self,
12066 window: &mut Window,
12067 cx: &mut Context<Self>,
12068 ) -> Vec<(Anchor, Option<Breakpoint>)> {
12069 let snapshot = self.snapshot(window, cx);
12070 let cursors = self
12071 .selections
12072 .disjoint_anchors_arc()
12073 .iter()
12074 .map(|selection| {
12075 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
12076
12077 let breakpoint_position = self
12078 .breakpoint_at_row(cursor_position.row, window, cx)
12079 .map(|bp| bp.0)
12080 .unwrap_or_else(|| {
12081 snapshot
12082 .display_snapshot
12083 .buffer_snapshot()
12084 .anchor_after(Point::new(cursor_position.row, 0))
12085 });
12086
12087 let breakpoint = self
12088 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
12089 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
12090
12091 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
12092 })
12093 // 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.
12094 .collect::<HashMap<Anchor, _>>();
12095
12096 cursors.into_iter().collect()
12097 }
12098
12099 pub fn enable_breakpoint(
12100 &mut self,
12101 _: &crate::actions::EnableBreakpoint,
12102 window: &mut Window,
12103 cx: &mut Context<Self>,
12104 ) {
12105 if self.breakpoint_store.is_none() {
12106 return;
12107 }
12108
12109 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
12110 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
12111 continue;
12112 };
12113 self.edit_breakpoint_at_anchor(
12114 anchor,
12115 breakpoint,
12116 BreakpointEditAction::InvertState,
12117 cx,
12118 );
12119 }
12120 }
12121
12122 pub fn align_selections(
12123 &mut self,
12124 _: &crate::actions::AlignSelections,
12125 window: &mut Window,
12126 cx: &mut Context<Self>,
12127 ) {
12128 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12129
12130 let display_snapshot = self.display_snapshot(cx);
12131
12132 struct CursorData {
12133 anchor: Anchor,
12134 point: Point,
12135 }
12136 let cursor_data: Vec<CursorData> = self
12137 .selections
12138 .disjoint_anchors()
12139 .iter()
12140 .map(|selection| {
12141 let anchor = if selection.reversed {
12142 selection.head()
12143 } else {
12144 selection.tail()
12145 };
12146 CursorData {
12147 anchor: anchor,
12148 point: anchor.to_point(&display_snapshot.buffer_snapshot()),
12149 }
12150 })
12151 .collect();
12152
12153 let rows_anchors_count: Vec<usize> = cursor_data
12154 .iter()
12155 .map(|cursor| cursor.point.row)
12156 .chunk_by(|&row| row)
12157 .into_iter()
12158 .map(|(_, group)| group.count())
12159 .collect();
12160 let max_columns = rows_anchors_count.iter().max().copied().unwrap_or(0);
12161 let mut rows_column_offset = vec![0; rows_anchors_count.len()];
12162 let mut edits = Vec::new();
12163
12164 for column_idx in 0..max_columns {
12165 let mut cursor_index = 0;
12166
12167 // Calculate target_column => position that the selections will go
12168 let mut target_column = 0;
12169 for (row_idx, cursor_count) in rows_anchors_count.iter().enumerate() {
12170 // Skip rows that don't have this column
12171 if column_idx >= *cursor_count {
12172 cursor_index += cursor_count;
12173 continue;
12174 }
12175
12176 let point = &cursor_data[cursor_index + column_idx].point;
12177 let adjusted_column = point.column + rows_column_offset[row_idx];
12178 if adjusted_column > target_column {
12179 target_column = adjusted_column;
12180 }
12181 cursor_index += cursor_count;
12182 }
12183
12184 // Collect edits for this column
12185 cursor_index = 0;
12186 for (row_idx, cursor_count) in rows_anchors_count.iter().enumerate() {
12187 // Skip rows that don't have this column
12188 if column_idx >= *cursor_count {
12189 cursor_index += *cursor_count;
12190 continue;
12191 }
12192
12193 let point = &cursor_data[cursor_index + column_idx].point;
12194 let spaces_needed = target_column - point.column - rows_column_offset[row_idx];
12195 if spaces_needed > 0 {
12196 let anchor = cursor_data[cursor_index + column_idx]
12197 .anchor
12198 .bias_left(&display_snapshot);
12199 edits.push((anchor..anchor, " ".repeat(spaces_needed as usize)));
12200 }
12201 rows_column_offset[row_idx] += spaces_needed;
12202
12203 cursor_index += *cursor_count;
12204 }
12205 }
12206
12207 if !edits.is_empty() {
12208 self.transact(window, cx, |editor, _window, cx| {
12209 editor.edit(edits, cx);
12210 });
12211 }
12212 }
12213
12214 pub fn disable_breakpoint(
12215 &mut self,
12216 _: &crate::actions::DisableBreakpoint,
12217 window: &mut Window,
12218 cx: &mut Context<Self>,
12219 ) {
12220 if self.breakpoint_store.is_none() {
12221 return;
12222 }
12223
12224 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
12225 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
12226 continue;
12227 };
12228 self.edit_breakpoint_at_anchor(
12229 anchor,
12230 breakpoint,
12231 BreakpointEditAction::InvertState,
12232 cx,
12233 );
12234 }
12235 }
12236
12237 pub fn toggle_breakpoint(
12238 &mut self,
12239 _: &crate::actions::ToggleBreakpoint,
12240 window: &mut Window,
12241 cx: &mut Context<Self>,
12242 ) {
12243 if self.breakpoint_store.is_none() {
12244 return;
12245 }
12246
12247 let snapshot = self.snapshot(window, cx);
12248 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
12249 if self.gutter_breakpoint_indicator.0.is_some() {
12250 let display_row = anchor
12251 .to_point(snapshot.buffer_snapshot())
12252 .to_display_point(&snapshot.display_snapshot)
12253 .row();
12254 self.update_breakpoint_collision_on_toggle(
12255 display_row,
12256 &BreakpointEditAction::Toggle,
12257 );
12258 }
12259
12260 if let Some(breakpoint) = breakpoint {
12261 self.edit_breakpoint_at_anchor(
12262 anchor,
12263 breakpoint,
12264 BreakpointEditAction::Toggle,
12265 cx,
12266 );
12267 } else {
12268 self.edit_breakpoint_at_anchor(
12269 anchor,
12270 Breakpoint::new_standard(),
12271 BreakpointEditAction::Toggle,
12272 cx,
12273 );
12274 }
12275 }
12276 }
12277
12278 fn update_breakpoint_collision_on_toggle(
12279 &mut self,
12280 display_row: DisplayRow,
12281 edit_action: &BreakpointEditAction,
12282 ) {
12283 if let Some(ref mut breakpoint_indicator) = self.gutter_breakpoint_indicator.0 {
12284 if breakpoint_indicator.display_row == display_row
12285 && matches!(edit_action, BreakpointEditAction::Toggle)
12286 {
12287 breakpoint_indicator.collides_with_existing_breakpoint =
12288 !breakpoint_indicator.collides_with_existing_breakpoint;
12289 }
12290 }
12291 }
12292
12293 pub fn edit_breakpoint_at_anchor(
12294 &mut self,
12295 breakpoint_position: Anchor,
12296 breakpoint: Breakpoint,
12297 edit_action: BreakpointEditAction,
12298 cx: &mut Context<Self>,
12299 ) {
12300 let Some(breakpoint_store) = &self.breakpoint_store else {
12301 return;
12302 };
12303 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
12304 let Some((position, _)) = buffer_snapshot.anchor_to_buffer_anchor(breakpoint_position)
12305 else {
12306 return;
12307 };
12308 let Some(buffer) = self.buffer.read(cx).buffer(position.buffer_id) else {
12309 return;
12310 };
12311
12312 breakpoint_store.update(cx, |breakpoint_store, cx| {
12313 breakpoint_store.toggle_breakpoint(
12314 buffer,
12315 BreakpointWithPosition {
12316 position,
12317 bp: breakpoint,
12318 },
12319 edit_action,
12320 cx,
12321 );
12322 });
12323
12324 cx.notify();
12325 }
12326
12327 #[cfg(any(test, feature = "test-support"))]
12328 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
12329 self.breakpoint_store.clone()
12330 }
12331
12332 pub fn prepare_restore_change(
12333 &self,
12334 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
12335 hunk: &MultiBufferDiffHunk,
12336 cx: &mut App,
12337 ) -> Option<()> {
12338 if hunk.is_created_file() {
12339 return None;
12340 }
12341 let multi_buffer = self.buffer.read(cx);
12342 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
12343 let diff_snapshot = multi_buffer_snapshot.diff_for_buffer_id(hunk.buffer_id)?;
12344 let original_text = diff_snapshot
12345 .base_text()
12346 .as_rope()
12347 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
12348 let buffer = multi_buffer.buffer(hunk.buffer_id)?;
12349 let buffer = buffer.read(cx);
12350 let buffer_snapshot = buffer.snapshot();
12351 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
12352 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
12353 probe
12354 .0
12355 .start
12356 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
12357 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
12358 }) {
12359 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
12360 Some(())
12361 } else {
12362 None
12363 }
12364 }
12365
12366 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
12367 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
12368 }
12369
12370 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
12371 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
12372 }
12373
12374 pub fn rotate_selections_forward(
12375 &mut self,
12376 _: &RotateSelectionsForward,
12377 window: &mut Window,
12378 cx: &mut Context<Self>,
12379 ) {
12380 self.rotate_selections(window, cx, false)
12381 }
12382
12383 pub fn rotate_selections_backward(
12384 &mut self,
12385 _: &RotateSelectionsBackward,
12386 window: &mut Window,
12387 cx: &mut Context<Self>,
12388 ) {
12389 self.rotate_selections(window, cx, true)
12390 }
12391
12392 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
12393 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12394 let display_snapshot = self.display_snapshot(cx);
12395 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
12396
12397 if selections.len() < 2 {
12398 return;
12399 }
12400
12401 let (edits, new_selections) = {
12402 let buffer = self.buffer.read(cx).read(cx);
12403 let has_selections = selections.iter().any(|s| !s.is_empty());
12404 if has_selections {
12405 let mut selected_texts: Vec<String> = selections
12406 .iter()
12407 .map(|selection| {
12408 buffer
12409 .text_for_range(selection.start..selection.end)
12410 .collect()
12411 })
12412 .collect();
12413
12414 if reverse {
12415 selected_texts.rotate_left(1);
12416 } else {
12417 selected_texts.rotate_right(1);
12418 }
12419
12420 let mut offset_delta: i64 = 0;
12421 let mut new_selections = Vec::new();
12422 let edits: Vec<_> = selections
12423 .iter()
12424 .zip(selected_texts.iter())
12425 .map(|(selection, new_text)| {
12426 let old_len = (selection.end.0 - selection.start.0) as i64;
12427 let new_len = new_text.len() as i64;
12428 let adjusted_start =
12429 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
12430 let adjusted_end =
12431 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
12432
12433 new_selections.push(Selection {
12434 id: selection.id,
12435 start: adjusted_start,
12436 end: adjusted_end,
12437 reversed: selection.reversed,
12438 goal: selection.goal,
12439 });
12440
12441 offset_delta += new_len - old_len;
12442 (selection.start..selection.end, new_text.clone())
12443 })
12444 .collect();
12445 (edits, new_selections)
12446 } else {
12447 let mut all_rows: Vec<u32> = selections
12448 .iter()
12449 .map(|selection| buffer.offset_to_point(selection.start).row)
12450 .collect();
12451 all_rows.sort_unstable();
12452 all_rows.dedup();
12453
12454 if all_rows.len() < 2 {
12455 return;
12456 }
12457
12458 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
12459 .iter()
12460 .map(|&row| {
12461 let start = Point::new(row, 0);
12462 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12463 buffer.point_to_offset(start)..buffer.point_to_offset(end)
12464 })
12465 .collect();
12466
12467 let mut line_texts: Vec<String> = line_ranges
12468 .iter()
12469 .map(|range| buffer.text_for_range(range.clone()).collect())
12470 .collect();
12471
12472 if reverse {
12473 line_texts.rotate_left(1);
12474 } else {
12475 line_texts.rotate_right(1);
12476 }
12477
12478 let edits = line_ranges
12479 .iter()
12480 .zip(line_texts.iter())
12481 .map(|(range, new_text)| (range.clone(), new_text.clone()))
12482 .collect();
12483
12484 let num_rows = all_rows.len();
12485 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
12486 .iter()
12487 .enumerate()
12488 .map(|(i, &row)| (row, i))
12489 .collect();
12490
12491 // Compute new line start offsets after rotation (handles CRLF)
12492 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
12493 let first_line_start = line_ranges[0].start.0;
12494 let mut new_line_starts: Vec<usize> = vec![first_line_start];
12495 for text in line_texts.iter().take(num_rows - 1) {
12496 let prev_start = *new_line_starts.last().unwrap();
12497 new_line_starts.push(prev_start + text.len() + newline_len);
12498 }
12499
12500 let new_selections = selections
12501 .iter()
12502 .map(|selection| {
12503 let point = buffer.offset_to_point(selection.start);
12504 let old_index = row_to_index[&point.row];
12505 let new_index = if reverse {
12506 (old_index + num_rows - 1) % num_rows
12507 } else {
12508 (old_index + 1) % num_rows
12509 };
12510 let new_offset =
12511 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
12512 Selection {
12513 id: selection.id,
12514 start: new_offset,
12515 end: new_offset,
12516 reversed: selection.reversed,
12517 goal: selection.goal,
12518 }
12519 })
12520 .collect();
12521
12522 (edits, new_selections)
12523 }
12524 };
12525
12526 self.transact(window, cx, |this, window, cx| {
12527 this.buffer.update(cx, |buffer, cx| {
12528 buffer.edit(edits, None, cx);
12529 });
12530 this.change_selections(Default::default(), window, cx, |s| {
12531 s.select(new_selections);
12532 });
12533 });
12534 }
12535
12536 fn manipulate_lines<M>(
12537 &mut self,
12538 window: &mut Window,
12539 cx: &mut Context<Self>,
12540 mut manipulate: M,
12541 ) where
12542 M: FnMut(&str) -> LineManipulationResult,
12543 {
12544 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12545
12546 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12547 let buffer = self.buffer.read(cx).snapshot(cx);
12548
12549 let mut edits = Vec::new();
12550
12551 let selections = self.selections.all::<Point>(&display_map);
12552 let mut selections = selections.iter().peekable();
12553 let mut contiguous_row_selections = Vec::new();
12554 let mut new_selections = Vec::new();
12555 let mut added_lines = 0;
12556 let mut removed_lines = 0;
12557
12558 while let Some(selection) = selections.next() {
12559 let (start_row, end_row) = consume_contiguous_rows(
12560 &mut contiguous_row_selections,
12561 selection,
12562 &display_map,
12563 &mut selections,
12564 );
12565
12566 let start_point = Point::new(start_row.0, 0);
12567 let end_point = Point::new(
12568 end_row.previous_row().0,
12569 buffer.line_len(end_row.previous_row()),
12570 );
12571 let text = buffer
12572 .text_for_range(start_point..end_point)
12573 .collect::<String>();
12574
12575 let LineManipulationResult {
12576 new_text,
12577 line_count_before,
12578 line_count_after,
12579 } = manipulate(&text);
12580
12581 edits.push((start_point..end_point, new_text));
12582
12583 // Selections must change based on added and removed line count
12584 let start_row =
12585 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
12586 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
12587 new_selections.push(Selection {
12588 id: selection.id,
12589 start: start_row,
12590 end: end_row,
12591 goal: SelectionGoal::None,
12592 reversed: selection.reversed,
12593 });
12594
12595 if line_count_after > line_count_before {
12596 added_lines += line_count_after - line_count_before;
12597 } else if line_count_before > line_count_after {
12598 removed_lines += line_count_before - line_count_after;
12599 }
12600 }
12601
12602 self.transact(window, cx, |this, window, cx| {
12603 let buffer = this.buffer.update(cx, |buffer, cx| {
12604 buffer.edit(edits, None, cx);
12605 buffer.snapshot(cx)
12606 });
12607
12608 // Recalculate offsets on newly edited buffer
12609 let new_selections = new_selections
12610 .iter()
12611 .map(|s| {
12612 let start_point = Point::new(s.start.0, 0);
12613 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
12614 Selection {
12615 id: s.id,
12616 start: buffer.point_to_offset(start_point),
12617 end: buffer.point_to_offset(end_point),
12618 goal: s.goal,
12619 reversed: s.reversed,
12620 }
12621 })
12622 .collect();
12623
12624 this.change_selections(Default::default(), window, cx, |s| {
12625 s.select(new_selections);
12626 });
12627
12628 this.request_autoscroll(Autoscroll::fit(), cx);
12629 });
12630 }
12631
12632 fn manipulate_immutable_lines<Fn>(
12633 &mut self,
12634 window: &mut Window,
12635 cx: &mut Context<Self>,
12636 mut callback: Fn,
12637 ) where
12638 Fn: FnMut(&mut Vec<&str>),
12639 {
12640 self.manipulate_lines(window, cx, |text| {
12641 let mut lines: Vec<&str> = text.split('\n').collect();
12642 let line_count_before = lines.len();
12643
12644 callback(&mut lines);
12645
12646 LineManipulationResult {
12647 new_text: lines.join("\n"),
12648 line_count_before,
12649 line_count_after: lines.len(),
12650 }
12651 });
12652 }
12653
12654 fn manipulate_mutable_lines<Fn>(
12655 &mut self,
12656 window: &mut Window,
12657 cx: &mut Context<Self>,
12658 mut callback: Fn,
12659 ) where
12660 Fn: FnMut(&mut Vec<Cow<'_, str>>),
12661 {
12662 self.manipulate_lines(window, cx, |text| {
12663 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
12664 let line_count_before = lines.len();
12665
12666 callback(&mut lines);
12667
12668 LineManipulationResult {
12669 new_text: lines.join("\n"),
12670 line_count_before,
12671 line_count_after: lines.len(),
12672 }
12673 });
12674 }
12675
12676 pub fn convert_indentation_to_spaces(
12677 &mut self,
12678 _: &ConvertIndentationToSpaces,
12679 window: &mut Window,
12680 cx: &mut Context<Self>,
12681 ) {
12682 let settings = self.buffer.read(cx).language_settings(cx);
12683 let tab_size = settings.tab_size.get() as usize;
12684
12685 self.manipulate_mutable_lines(window, cx, |lines| {
12686 // Allocates a reasonably sized scratch buffer once for the whole loop
12687 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12688 // Avoids recomputing spaces that could be inserted many times
12689 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12690 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12691 .collect();
12692
12693 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12694 let mut chars = line.as_ref().chars();
12695 let mut col = 0;
12696 let mut changed = false;
12697
12698 for ch in chars.by_ref() {
12699 match ch {
12700 ' ' => {
12701 reindented_line.push(' ');
12702 col += 1;
12703 }
12704 '\t' => {
12705 // \t are converted to spaces depending on the current column
12706 let spaces_len = tab_size - (col % tab_size);
12707 reindented_line.extend(&space_cache[spaces_len - 1]);
12708 col += spaces_len;
12709 changed = true;
12710 }
12711 _ => {
12712 // If we dont append before break, the character is consumed
12713 reindented_line.push(ch);
12714 break;
12715 }
12716 }
12717 }
12718
12719 if !changed {
12720 reindented_line.clear();
12721 continue;
12722 }
12723 // Append the rest of the line and replace old reference with new one
12724 reindented_line.extend(chars);
12725 *line = Cow::Owned(reindented_line.clone());
12726 reindented_line.clear();
12727 }
12728 });
12729 }
12730
12731 pub fn convert_indentation_to_tabs(
12732 &mut self,
12733 _: &ConvertIndentationToTabs,
12734 window: &mut Window,
12735 cx: &mut Context<Self>,
12736 ) {
12737 let settings = self.buffer.read(cx).language_settings(cx);
12738 let tab_size = settings.tab_size.get() as usize;
12739
12740 self.manipulate_mutable_lines(window, cx, |lines| {
12741 // Allocates a reasonably sized buffer once for the whole loop
12742 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12743 // Avoids recomputing spaces that could be inserted many times
12744 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12745 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12746 .collect();
12747
12748 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12749 let mut chars = line.chars();
12750 let mut spaces_count = 0;
12751 let mut first_non_indent_char = None;
12752 let mut changed = false;
12753
12754 for ch in chars.by_ref() {
12755 match ch {
12756 ' ' => {
12757 // Keep track of spaces. Append \t when we reach tab_size
12758 spaces_count += 1;
12759 changed = true;
12760 if spaces_count == tab_size {
12761 reindented_line.push('\t');
12762 spaces_count = 0;
12763 }
12764 }
12765 '\t' => {
12766 reindented_line.push('\t');
12767 spaces_count = 0;
12768 }
12769 _ => {
12770 // Dont append it yet, we might have remaining spaces
12771 first_non_indent_char = Some(ch);
12772 break;
12773 }
12774 }
12775 }
12776
12777 if !changed {
12778 reindented_line.clear();
12779 continue;
12780 }
12781 // Remaining spaces that didn't make a full tab stop
12782 if spaces_count > 0 {
12783 reindented_line.extend(&space_cache[spaces_count - 1]);
12784 }
12785 // If we consume an extra character that was not indentation, add it back
12786 if let Some(extra_char) = first_non_indent_char {
12787 reindented_line.push(extra_char);
12788 }
12789 // Append the rest of the line and replace old reference with new one
12790 reindented_line.extend(chars);
12791 *line = Cow::Owned(reindented_line.clone());
12792 reindented_line.clear();
12793 }
12794 });
12795 }
12796
12797 pub fn convert_to_upper_case(
12798 &mut self,
12799 _: &ConvertToUpperCase,
12800 window: &mut Window,
12801 cx: &mut Context<Self>,
12802 ) {
12803 self.manipulate_text(window, cx, |text| text.to_uppercase())
12804 }
12805
12806 pub fn convert_to_lower_case(
12807 &mut self,
12808 _: &ConvertToLowerCase,
12809 window: &mut Window,
12810 cx: &mut Context<Self>,
12811 ) {
12812 self.manipulate_text(window, cx, |text| text.to_lowercase())
12813 }
12814
12815 pub fn convert_to_title_case(
12816 &mut self,
12817 _: &ConvertToTitleCase,
12818 window: &mut Window,
12819 cx: &mut Context<Self>,
12820 ) {
12821 self.manipulate_text(window, cx, |text| {
12822 Self::convert_text_case(text, Case::Title)
12823 })
12824 }
12825
12826 pub fn convert_to_snake_case(
12827 &mut self,
12828 _: &ConvertToSnakeCase,
12829 window: &mut Window,
12830 cx: &mut Context<Self>,
12831 ) {
12832 self.manipulate_text(window, cx, |text| {
12833 Self::convert_text_case(text, Case::Snake)
12834 })
12835 }
12836
12837 pub fn convert_to_kebab_case(
12838 &mut self,
12839 _: &ConvertToKebabCase,
12840 window: &mut Window,
12841 cx: &mut Context<Self>,
12842 ) {
12843 self.manipulate_text(window, cx, |text| {
12844 Self::convert_text_case(text, Case::Kebab)
12845 })
12846 }
12847
12848 pub fn convert_to_upper_camel_case(
12849 &mut self,
12850 _: &ConvertToUpperCamelCase,
12851 window: &mut Window,
12852 cx: &mut Context<Self>,
12853 ) {
12854 self.manipulate_text(window, cx, |text| {
12855 Self::convert_text_case(text, Case::UpperCamel)
12856 })
12857 }
12858
12859 pub fn convert_to_lower_camel_case(
12860 &mut self,
12861 _: &ConvertToLowerCamelCase,
12862 window: &mut Window,
12863 cx: &mut Context<Self>,
12864 ) {
12865 self.manipulate_text(window, cx, |text| {
12866 Self::convert_text_case(text, Case::Camel)
12867 })
12868 }
12869
12870 pub fn convert_to_opposite_case(
12871 &mut self,
12872 _: &ConvertToOppositeCase,
12873 window: &mut Window,
12874 cx: &mut Context<Self>,
12875 ) {
12876 self.manipulate_text(window, cx, |text| {
12877 text.chars()
12878 .fold(String::with_capacity(text.len()), |mut t, c| {
12879 if c.is_uppercase() {
12880 t.extend(c.to_lowercase());
12881 } else {
12882 t.extend(c.to_uppercase());
12883 }
12884 t
12885 })
12886 })
12887 }
12888
12889 pub fn convert_to_sentence_case(
12890 &mut self,
12891 _: &ConvertToSentenceCase,
12892 window: &mut Window,
12893 cx: &mut Context<Self>,
12894 ) {
12895 self.manipulate_text(window, cx, |text| {
12896 Self::convert_text_case(text, Case::Sentence)
12897 })
12898 }
12899
12900 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
12901 self.manipulate_text(window, cx, |text| {
12902 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
12903 if has_upper_case_characters {
12904 text.to_lowercase()
12905 } else {
12906 text.to_uppercase()
12907 }
12908 })
12909 }
12910
12911 pub fn convert_to_rot13(
12912 &mut self,
12913 _: &ConvertToRot13,
12914 window: &mut Window,
12915 cx: &mut Context<Self>,
12916 ) {
12917 self.manipulate_text(window, cx, |text| {
12918 text.chars()
12919 .map(|c| match c {
12920 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
12921 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
12922 _ => c,
12923 })
12924 .collect()
12925 })
12926 }
12927
12928 fn convert_text_case(text: &str, case: Case) -> String {
12929 text.lines()
12930 .map(|line| {
12931 let trimmed_start = line.trim_start();
12932 let leading = &line[..line.len() - trimmed_start.len()];
12933 let trimmed = trimmed_start.trim_end();
12934 let trailing = &trimmed_start[trimmed.len()..];
12935 format!("{}{}{}", leading, trimmed.to_case(case), trailing)
12936 })
12937 .join("\n")
12938 }
12939
12940 pub fn convert_to_rot47(
12941 &mut self,
12942 _: &ConvertToRot47,
12943 window: &mut Window,
12944 cx: &mut Context<Self>,
12945 ) {
12946 self.manipulate_text(window, cx, |text| {
12947 text.chars()
12948 .map(|c| {
12949 let code_point = c as u32;
12950 if code_point >= 33 && code_point <= 126 {
12951 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
12952 }
12953 c
12954 })
12955 .collect()
12956 })
12957 }
12958
12959 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
12960 where
12961 Fn: FnMut(&str) -> String,
12962 {
12963 let buffer = self.buffer.read(cx).snapshot(cx);
12964
12965 let mut new_selections = Vec::new();
12966 let mut edits = Vec::new();
12967 let mut selection_adjustment = 0isize;
12968
12969 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
12970 let selection_is_empty = selection.is_empty();
12971
12972 let (start, end) = if selection_is_empty {
12973 let (word_range, _) = buffer.surrounding_word(selection.start, None);
12974 (word_range.start, word_range.end)
12975 } else {
12976 (
12977 buffer.point_to_offset(selection.start),
12978 buffer.point_to_offset(selection.end),
12979 )
12980 };
12981
12982 let text = buffer.text_for_range(start..end).collect::<String>();
12983 let old_length = text.len() as isize;
12984 let text = callback(&text);
12985
12986 new_selections.push(Selection {
12987 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
12988 end: MultiBufferOffset(
12989 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
12990 ),
12991 goal: SelectionGoal::None,
12992 id: selection.id,
12993 reversed: selection.reversed,
12994 });
12995
12996 selection_adjustment += old_length - text.len() as isize;
12997
12998 edits.push((start..end, text));
12999 }
13000
13001 self.transact(window, cx, |this, window, cx| {
13002 this.buffer.update(cx, |buffer, cx| {
13003 buffer.edit(edits, None, cx);
13004 });
13005
13006 this.change_selections(Default::default(), window, cx, |s| {
13007 s.select(new_selections);
13008 });
13009
13010 this.request_autoscroll(Autoscroll::fit(), cx);
13011 });
13012 }
13013
13014 pub fn move_selection_on_drop(
13015 &mut self,
13016 selection: &Selection<Anchor>,
13017 target: DisplayPoint,
13018 is_cut: bool,
13019 window: &mut Window,
13020 cx: &mut Context<Self>,
13021 ) {
13022 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13023 let buffer = display_map.buffer_snapshot();
13024 let mut edits = Vec::new();
13025 let insert_point = display_map
13026 .clip_point(target, Bias::Left)
13027 .to_point(&display_map);
13028 let text = buffer
13029 .text_for_range(selection.start..selection.end)
13030 .collect::<String>();
13031 if is_cut {
13032 edits.push(((selection.start..selection.end), String::new()));
13033 }
13034 let insert_anchor = buffer.anchor_before(insert_point);
13035 edits.push(((insert_anchor..insert_anchor), text));
13036 let last_edit_start = insert_anchor.bias_left(buffer);
13037 let last_edit_end = insert_anchor.bias_right(buffer);
13038 self.transact(window, cx, |this, window, cx| {
13039 this.buffer.update(cx, |buffer, cx| {
13040 buffer.edit(edits, None, cx);
13041 });
13042 this.change_selections(Default::default(), window, cx, |s| {
13043 s.select_anchor_ranges([last_edit_start..last_edit_end]);
13044 });
13045 });
13046 }
13047
13048 pub fn clear_selection_drag_state(&mut self) {
13049 self.selection_drag_state = SelectionDragState::None;
13050 }
13051
13052 pub fn duplicate(
13053 &mut self,
13054 upwards: bool,
13055 whole_lines: bool,
13056 window: &mut Window,
13057 cx: &mut Context<Self>,
13058 ) {
13059 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13060
13061 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13062 let buffer = display_map.buffer_snapshot();
13063 let selections = self.selections.all::<Point>(&display_map);
13064
13065 let mut edits = Vec::new();
13066 let mut selections_iter = selections.iter().peekable();
13067 while let Some(selection) = selections_iter.next() {
13068 let mut rows = selection.spanned_rows(false, &display_map);
13069 // duplicate line-wise
13070 if whole_lines || selection.start == selection.end {
13071 // Avoid duplicating the same lines twice.
13072 while let Some(next_selection) = selections_iter.peek() {
13073 let next_rows = next_selection.spanned_rows(false, &display_map);
13074 if next_rows.start < rows.end {
13075 rows.end = next_rows.end;
13076 selections_iter.next().unwrap();
13077 } else {
13078 break;
13079 }
13080 }
13081
13082 // Copy the text from the selected row region and splice it either at the start
13083 // or end of the region.
13084 let start = Point::new(rows.start.0, 0);
13085 let end = Point::new(
13086 rows.end.previous_row().0,
13087 buffer.line_len(rows.end.previous_row()),
13088 );
13089
13090 let mut text = buffer.text_for_range(start..end).collect::<String>();
13091
13092 let insert_location = if upwards {
13093 // When duplicating upward, we need to insert before the current line.
13094 // If we're on the last line and it doesn't end with a newline,
13095 // we need to add a newline before the duplicated content.
13096 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
13097 && buffer.max_point().column > 0
13098 && !text.ends_with('\n');
13099
13100 if needs_leading_newline {
13101 text.insert(0, '\n');
13102 end
13103 } else {
13104 text.push('\n');
13105 Point::new(rows.start.0, 0)
13106 }
13107 } else {
13108 text.push('\n');
13109 start
13110 };
13111 edits.push((insert_location..insert_location, text));
13112 } else {
13113 // duplicate character-wise
13114 let start = selection.start;
13115 let end = selection.end;
13116 let text = buffer.text_for_range(start..end).collect::<String>();
13117 edits.push((selection.end..selection.end, text));
13118 }
13119 }
13120
13121 self.transact(window, cx, |this, window, cx| {
13122 this.buffer.update(cx, |buffer, cx| {
13123 buffer.edit(edits, None, cx);
13124 });
13125
13126 // When duplicating upward with whole lines, move the cursor to the duplicated line
13127 if upwards && whole_lines {
13128 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
13129
13130 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13131 let mut new_ranges = Vec::new();
13132 let selections = s.all::<Point>(&display_map);
13133 let mut selections_iter = selections.iter().peekable();
13134
13135 while let Some(first_selection) = selections_iter.next() {
13136 // Group contiguous selections together to find the total row span
13137 let mut group_selections = vec![first_selection];
13138 let mut rows = first_selection.spanned_rows(false, &display_map);
13139
13140 while let Some(next_selection) = selections_iter.peek() {
13141 let next_rows = next_selection.spanned_rows(false, &display_map);
13142 if next_rows.start < rows.end {
13143 rows.end = next_rows.end;
13144 group_selections.push(selections_iter.next().unwrap());
13145 } else {
13146 break;
13147 }
13148 }
13149
13150 let row_count = rows.end.0 - rows.start.0;
13151
13152 // Move all selections in this group up by the total number of duplicated rows
13153 for selection in group_selections {
13154 let new_start = Point::new(
13155 selection.start.row.saturating_sub(row_count),
13156 selection.start.column,
13157 );
13158
13159 let new_end = Point::new(
13160 selection.end.row.saturating_sub(row_count),
13161 selection.end.column,
13162 );
13163
13164 new_ranges.push(new_start..new_end);
13165 }
13166 }
13167
13168 s.select_ranges(new_ranges);
13169 });
13170 }
13171
13172 this.request_autoscroll(Autoscroll::fit(), cx);
13173 });
13174 }
13175
13176 pub fn duplicate_line_up(
13177 &mut self,
13178 _: &DuplicateLineUp,
13179 window: &mut Window,
13180 cx: &mut Context<Self>,
13181 ) {
13182 self.duplicate(true, true, window, cx);
13183 }
13184
13185 pub fn duplicate_line_down(
13186 &mut self,
13187 _: &DuplicateLineDown,
13188 window: &mut Window,
13189 cx: &mut Context<Self>,
13190 ) {
13191 self.duplicate(false, true, window, cx);
13192 }
13193
13194 pub fn duplicate_selection(
13195 &mut self,
13196 _: &DuplicateSelection,
13197 window: &mut Window,
13198 cx: &mut Context<Self>,
13199 ) {
13200 self.duplicate(false, false, window, cx);
13201 }
13202
13203 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
13204 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13205 if self.mode.is_single_line() {
13206 cx.propagate();
13207 return;
13208 }
13209
13210 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13211 let buffer = self.buffer.read(cx).snapshot(cx);
13212
13213 let mut edits = Vec::new();
13214 let mut unfold_ranges = Vec::new();
13215 let mut refold_creases = Vec::new();
13216
13217 let selections = self.selections.all::<Point>(&display_map);
13218 let mut selections = selections.iter().peekable();
13219 let mut contiguous_row_selections = Vec::new();
13220 let mut new_selections = Vec::new();
13221
13222 while let Some(selection) = selections.next() {
13223 // Find all the selections that span a contiguous row range
13224 let (start_row, end_row) = consume_contiguous_rows(
13225 &mut contiguous_row_selections,
13226 selection,
13227 &display_map,
13228 &mut selections,
13229 );
13230
13231 // Move the text spanned by the row range to be before the line preceding the row range
13232 if start_row.0 > 0 {
13233 let range_to_move = Point::new(
13234 start_row.previous_row().0,
13235 buffer.line_len(start_row.previous_row()),
13236 )
13237 ..Point::new(
13238 end_row.previous_row().0,
13239 buffer.line_len(end_row.previous_row()),
13240 );
13241 let insertion_point = display_map
13242 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
13243 .0;
13244
13245 // Don't move lines across excerpts
13246 if buffer
13247 .excerpt_containing(insertion_point..range_to_move.end)
13248 .is_some()
13249 {
13250 let text = buffer
13251 .text_for_range(range_to_move.clone())
13252 .flat_map(|s| s.chars())
13253 .skip(1)
13254 .chain(['\n'])
13255 .collect::<String>();
13256
13257 edits.push((
13258 buffer.anchor_after(range_to_move.start)
13259 ..buffer.anchor_before(range_to_move.end),
13260 String::new(),
13261 ));
13262 let insertion_anchor = buffer.anchor_after(insertion_point);
13263 edits.push((insertion_anchor..insertion_anchor, text));
13264
13265 let row_delta = range_to_move.start.row - insertion_point.row + 1;
13266
13267 // Move selections up
13268 new_selections.extend(contiguous_row_selections.drain(..).map(
13269 |mut selection| {
13270 selection.start.row -= row_delta;
13271 selection.end.row -= row_delta;
13272 selection
13273 },
13274 ));
13275
13276 // Move folds up
13277 unfold_ranges.push(range_to_move.clone());
13278 for fold in display_map.folds_in_range(
13279 buffer.anchor_before(range_to_move.start)
13280 ..buffer.anchor_after(range_to_move.end),
13281 ) {
13282 let mut start = fold.range.start.to_point(&buffer);
13283 let mut end = fold.range.end.to_point(&buffer);
13284 start.row -= row_delta;
13285 end.row -= row_delta;
13286 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
13287 }
13288 }
13289 }
13290
13291 // If we didn't move line(s), preserve the existing selections
13292 new_selections.append(&mut contiguous_row_selections);
13293 }
13294
13295 self.transact(window, cx, |this, window, cx| {
13296 this.unfold_ranges(&unfold_ranges, true, true, cx);
13297 this.buffer.update(cx, |buffer, cx| {
13298 for (range, text) in edits {
13299 buffer.edit([(range, text)], None, cx);
13300 }
13301 });
13302 this.fold_creases(refold_creases, true, window, cx);
13303 this.change_selections(Default::default(), window, cx, |s| {
13304 s.select(new_selections);
13305 })
13306 });
13307 }
13308
13309 pub fn move_line_down(
13310 &mut self,
13311 _: &MoveLineDown,
13312 window: &mut Window,
13313 cx: &mut Context<Self>,
13314 ) {
13315 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13316 if self.mode.is_single_line() {
13317 cx.propagate();
13318 return;
13319 }
13320
13321 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13322 let buffer = self.buffer.read(cx).snapshot(cx);
13323
13324 let mut edits = Vec::new();
13325 let mut unfold_ranges = Vec::new();
13326 let mut refold_creases = Vec::new();
13327
13328 let selections = self.selections.all::<Point>(&display_map);
13329 let mut selections = selections.iter().peekable();
13330 let mut contiguous_row_selections = Vec::new();
13331 let mut new_selections = Vec::new();
13332
13333 while let Some(selection) = selections.next() {
13334 // Find all the selections that span a contiguous row range
13335 let (start_row, end_row) = consume_contiguous_rows(
13336 &mut contiguous_row_selections,
13337 selection,
13338 &display_map,
13339 &mut selections,
13340 );
13341
13342 // Move the text spanned by the row range to be after the last line of the row range
13343 if end_row.0 <= buffer.max_point().row {
13344 let range_to_move =
13345 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
13346 let insertion_point = display_map
13347 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
13348 .0;
13349
13350 // Don't move lines across excerpt boundaries
13351 if buffer
13352 .excerpt_containing(range_to_move.start..insertion_point)
13353 .is_some()
13354 {
13355 let mut text = String::from("\n");
13356 text.extend(buffer.text_for_range(range_to_move.clone()));
13357 text.pop(); // Drop trailing newline
13358 edits.push((
13359 buffer.anchor_after(range_to_move.start)
13360 ..buffer.anchor_before(range_to_move.end),
13361 String::new(),
13362 ));
13363 let insertion_anchor = buffer.anchor_after(insertion_point);
13364 edits.push((insertion_anchor..insertion_anchor, text));
13365
13366 let row_delta = insertion_point.row - range_to_move.end.row + 1;
13367
13368 // Move selections down
13369 new_selections.extend(contiguous_row_selections.drain(..).map(
13370 |mut selection| {
13371 selection.start.row += row_delta;
13372 selection.end.row += row_delta;
13373 selection
13374 },
13375 ));
13376
13377 // Move folds down
13378 unfold_ranges.push(range_to_move.clone());
13379 for fold in display_map.folds_in_range(
13380 buffer.anchor_before(range_to_move.start)
13381 ..buffer.anchor_after(range_to_move.end),
13382 ) {
13383 let mut start = fold.range.start.to_point(&buffer);
13384 let mut end = fold.range.end.to_point(&buffer);
13385 start.row += row_delta;
13386 end.row += row_delta;
13387 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
13388 }
13389 }
13390 }
13391
13392 // If we didn't move line(s), preserve the existing selections
13393 new_selections.append(&mut contiguous_row_selections);
13394 }
13395
13396 self.transact(window, cx, |this, window, cx| {
13397 this.unfold_ranges(&unfold_ranges, true, true, cx);
13398 this.buffer.update(cx, |buffer, cx| {
13399 for (range, text) in edits {
13400 buffer.edit([(range, text)], None, cx);
13401 }
13402 });
13403 this.fold_creases(refold_creases, true, window, cx);
13404 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
13405 });
13406 }
13407
13408 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
13409 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13410 let text_layout_details = &self.text_layout_details(window, cx);
13411 self.transact(window, cx, |this, window, cx| {
13412 let edits = this.change_selections(Default::default(), window, cx, |s| {
13413 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
13414 s.move_with(&mut |display_map, selection| {
13415 if !selection.is_empty() {
13416 return;
13417 }
13418
13419 let mut head = selection.head();
13420 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
13421 if head.column() == display_map.line_len(head.row()) {
13422 transpose_offset = display_map
13423 .buffer_snapshot()
13424 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13425 }
13426
13427 if transpose_offset == MultiBufferOffset(0) {
13428 return;
13429 }
13430
13431 *head.column_mut() += 1;
13432 head = display_map.clip_point(head, Bias::Right);
13433 let goal = SelectionGoal::HorizontalPosition(
13434 display_map
13435 .x_for_display_point(head, text_layout_details)
13436 .into(),
13437 );
13438 selection.collapse_to(head, goal);
13439
13440 let transpose_start = display_map
13441 .buffer_snapshot()
13442 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13443 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
13444 let transpose_end = display_map
13445 .buffer_snapshot()
13446 .clip_offset(transpose_offset + 1usize, Bias::Right);
13447 if let Some(ch) = display_map
13448 .buffer_snapshot()
13449 .chars_at(transpose_start)
13450 .next()
13451 {
13452 edits.push((transpose_start..transpose_offset, String::new()));
13453 edits.push((transpose_end..transpose_end, ch.to_string()));
13454 }
13455 }
13456 });
13457 edits
13458 });
13459 this.buffer
13460 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13461 let selections = this
13462 .selections
13463 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13464 this.change_selections(Default::default(), window, cx, |s| {
13465 s.select(selections);
13466 });
13467 });
13468 }
13469
13470 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
13471 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13472 if self.mode.is_single_line() {
13473 cx.propagate();
13474 return;
13475 }
13476
13477 self.rewrap_impl(RewrapOptions::default(), cx)
13478 }
13479
13480 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
13481 let buffer = self.buffer.read(cx).snapshot(cx);
13482 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13483
13484 #[derive(Clone, Debug, PartialEq)]
13485 enum CommentFormat {
13486 /// single line comment, with prefix for line
13487 Line(String),
13488 /// single line within a block comment, with prefix for line
13489 BlockLine(String),
13490 /// a single line of a block comment that includes the initial delimiter
13491 BlockCommentWithStart(BlockCommentConfig),
13492 /// a single line of a block comment that includes the ending delimiter
13493 BlockCommentWithEnd(BlockCommentConfig),
13494 }
13495
13496 // Split selections to respect paragraph, indent, and comment prefix boundaries.
13497 let wrap_ranges = selections.into_iter().flat_map(|selection| {
13498 let language_settings = buffer.language_settings_at(selection.head(), cx);
13499 let language_scope = buffer.language_scope_at(selection.head());
13500
13501 let indent_and_prefix_for_row =
13502 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
13503 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
13504 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
13505 &language_scope
13506 {
13507 let indent_end = Point::new(row, indent.len);
13508 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13509 let line_text_after_indent = buffer
13510 .text_for_range(indent_end..line_end)
13511 .collect::<String>();
13512
13513 let is_within_comment_override = buffer
13514 .language_scope_at(indent_end)
13515 .is_some_and(|scope| scope.override_name() == Some("comment"));
13516 let comment_delimiters = if is_within_comment_override {
13517 // we are within a comment syntax node, but we don't
13518 // yet know what kind of comment: block, doc or line
13519 match (
13520 language_scope.documentation_comment(),
13521 language_scope.block_comment(),
13522 ) {
13523 (Some(config), _) | (_, Some(config))
13524 if buffer.contains_str_at(indent_end, &config.start) =>
13525 {
13526 Some(CommentFormat::BlockCommentWithStart(config.clone()))
13527 }
13528 (Some(config), _) | (_, Some(config))
13529 if line_text_after_indent.ends_with(config.end.as_ref()) =>
13530 {
13531 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
13532 }
13533 (Some(config), _) | (_, Some(config))
13534 if buffer.contains_str_at(indent_end, &config.prefix) =>
13535 {
13536 Some(CommentFormat::BlockLine(config.prefix.to_string()))
13537 }
13538 (_, _) => language_scope
13539 .line_comment_prefixes()
13540 .iter()
13541 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13542 .map(|prefix| CommentFormat::Line(prefix.to_string())),
13543 }
13544 } else {
13545 // we not in an overridden comment node, but we may
13546 // be within a non-overridden line comment node
13547 language_scope
13548 .line_comment_prefixes()
13549 .iter()
13550 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13551 .map(|prefix| CommentFormat::Line(prefix.to_string()))
13552 };
13553
13554 let rewrap_prefix = language_scope
13555 .rewrap_prefixes()
13556 .iter()
13557 .find_map(|prefix_regex| {
13558 prefix_regex.find(&line_text_after_indent).map(|mat| {
13559 if mat.start() == 0 {
13560 Some(mat.as_str().to_string())
13561 } else {
13562 None
13563 }
13564 })
13565 })
13566 .flatten();
13567 (comment_delimiters, rewrap_prefix)
13568 } else {
13569 (None, None)
13570 };
13571 (indent, comment_prefix, rewrap_prefix)
13572 };
13573
13574 let mut start_row = selection.start.row;
13575 let mut end_row = selection.end.row;
13576
13577 if selection.is_empty() {
13578 let cursor_row = selection.start.row;
13579
13580 let (mut indent_size, comment_prefix, _) = indent_and_prefix_for_row(cursor_row);
13581 let line_prefix = match &comment_prefix {
13582 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
13583 Some(prefix.as_str())
13584 }
13585 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
13586 prefix, ..
13587 })) => Some(prefix.as_ref()),
13588 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13589 start: _,
13590 end: _,
13591 prefix,
13592 tab_size,
13593 })) => {
13594 indent_size.len += tab_size;
13595 Some(prefix.as_ref())
13596 }
13597 None => None,
13598 };
13599 let indent_prefix = indent_size.chars().collect::<String>();
13600 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
13601
13602 'expand_upwards: while start_row > 0 {
13603 let prev_row = start_row - 1;
13604 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
13605 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
13606 && !buffer.is_line_blank(MultiBufferRow(prev_row))
13607 {
13608 start_row = prev_row;
13609 } else {
13610 break 'expand_upwards;
13611 }
13612 }
13613
13614 'expand_downwards: while end_row < buffer.max_point().row {
13615 let next_row = end_row + 1;
13616 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
13617 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
13618 && !buffer.is_line_blank(MultiBufferRow(next_row))
13619 {
13620 end_row = next_row;
13621 } else {
13622 break 'expand_downwards;
13623 }
13624 }
13625 }
13626
13627 let mut non_blank_rows_iter = (start_row..=end_row)
13628 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
13629 .peekable();
13630
13631 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
13632 row
13633 } else {
13634 return Vec::new();
13635 };
13636
13637 let mut ranges = Vec::new();
13638
13639 let mut current_range_start = first_row;
13640 let mut prev_row = first_row;
13641 let (
13642 mut current_range_indent,
13643 mut current_range_comment_delimiters,
13644 mut current_range_rewrap_prefix,
13645 ) = indent_and_prefix_for_row(first_row);
13646
13647 for row in non_blank_rows_iter.skip(1) {
13648 let has_paragraph_break = row > prev_row + 1;
13649
13650 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
13651 indent_and_prefix_for_row(row);
13652
13653 let has_indent_change = row_indent != current_range_indent;
13654 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
13655
13656 let has_boundary_change = has_comment_change
13657 || row_rewrap_prefix.is_some()
13658 || (has_indent_change && current_range_comment_delimiters.is_some());
13659
13660 if has_paragraph_break || has_boundary_change {
13661 ranges.push((
13662 language_settings.clone(),
13663 Point::new(current_range_start, 0)
13664 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13665 current_range_indent,
13666 current_range_comment_delimiters.clone(),
13667 current_range_rewrap_prefix.clone(),
13668 ));
13669 current_range_start = row;
13670 current_range_indent = row_indent;
13671 current_range_comment_delimiters = row_comment_delimiters;
13672 current_range_rewrap_prefix = row_rewrap_prefix;
13673 }
13674 prev_row = row;
13675 }
13676
13677 ranges.push((
13678 language_settings.clone(),
13679 Point::new(current_range_start, 0)
13680 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13681 current_range_indent,
13682 current_range_comment_delimiters,
13683 current_range_rewrap_prefix,
13684 ));
13685
13686 ranges
13687 });
13688
13689 let mut edits = Vec::new();
13690 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
13691
13692 for (language_settings, wrap_range, mut indent_size, comment_prefix, rewrap_prefix) in
13693 wrap_ranges
13694 {
13695 let start_row = wrap_range.start.row;
13696 let end_row = wrap_range.end.row;
13697
13698 // Skip selections that overlap with a range that has already been rewrapped.
13699 let selection_range = start_row..end_row;
13700 if rewrapped_row_ranges
13701 .iter()
13702 .any(|range| range.overlaps(&selection_range))
13703 {
13704 continue;
13705 }
13706
13707 let tab_size = language_settings.tab_size;
13708
13709 let (line_prefix, inside_comment) = match &comment_prefix {
13710 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
13711 (Some(prefix.as_str()), true)
13712 }
13713 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
13714 (Some(prefix.as_ref()), true)
13715 }
13716 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13717 start: _,
13718 end: _,
13719 prefix,
13720 tab_size,
13721 })) => {
13722 indent_size.len += tab_size;
13723 (Some(prefix.as_ref()), true)
13724 }
13725 None => (None, false),
13726 };
13727 let indent_prefix = indent_size.chars().collect::<String>();
13728 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
13729
13730 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
13731 RewrapBehavior::InComments => inside_comment,
13732 RewrapBehavior::InSelections => !wrap_range.is_empty(),
13733 RewrapBehavior::Anywhere => true,
13734 };
13735
13736 let should_rewrap = options.override_language_settings
13737 || allow_rewrap_based_on_language
13738 || self.hard_wrap.is_some();
13739 if !should_rewrap {
13740 continue;
13741 }
13742
13743 let start = Point::new(start_row, 0);
13744 let start_offset = ToOffset::to_offset(&start, &buffer);
13745 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
13746 let selection_text = buffer.text_for_range(start..end).collect::<String>();
13747 let mut first_line_delimiter = None;
13748 let mut last_line_delimiter = None;
13749 let Some(lines_without_prefixes) = selection_text
13750 .lines()
13751 .enumerate()
13752 .map(|(ix, line)| {
13753 let line_trimmed = line.trim_start();
13754 if rewrap_prefix.is_some() && ix > 0 {
13755 Ok(line_trimmed)
13756 } else if let Some(
13757 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13758 start,
13759 prefix,
13760 end,
13761 tab_size,
13762 })
13763 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
13764 start,
13765 prefix,
13766 end,
13767 tab_size,
13768 }),
13769 ) = &comment_prefix
13770 {
13771 let line_trimmed = line_trimmed
13772 .strip_prefix(start.as_ref())
13773 .map(|s| {
13774 let mut indent_size = indent_size;
13775 indent_size.len -= tab_size;
13776 let indent_prefix: String = indent_size.chars().collect();
13777 first_line_delimiter = Some((indent_prefix, start));
13778 s.trim_start()
13779 })
13780 .unwrap_or(line_trimmed);
13781 let line_trimmed = line_trimmed
13782 .strip_suffix(end.as_ref())
13783 .map(|s| {
13784 last_line_delimiter = Some(end);
13785 s.trim_end()
13786 })
13787 .unwrap_or(line_trimmed);
13788 let line_trimmed = line_trimmed
13789 .strip_prefix(prefix.as_ref())
13790 .unwrap_or(line_trimmed);
13791 Ok(line_trimmed)
13792 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
13793 line_trimmed.strip_prefix(prefix).with_context(|| {
13794 format!("line did not start with prefix {prefix:?}: {line:?}")
13795 })
13796 } else {
13797 line_trimmed
13798 .strip_prefix(&line_prefix.trim_start())
13799 .with_context(|| {
13800 format!("line did not start with prefix {line_prefix:?}: {line:?}")
13801 })
13802 }
13803 })
13804 .collect::<Result<Vec<_>, _>>()
13805 .log_err()
13806 else {
13807 continue;
13808 };
13809
13810 let wrap_column = options.line_length.or(self.hard_wrap).unwrap_or_else(|| {
13811 buffer
13812 .language_settings_at(Point::new(start_row, 0), cx)
13813 .preferred_line_length as usize
13814 });
13815
13816 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
13817 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
13818 } else {
13819 line_prefix.clone()
13820 };
13821
13822 let wrapped_text = {
13823 let mut wrapped_text = wrap_with_prefix(
13824 line_prefix,
13825 subsequent_lines_prefix,
13826 lines_without_prefixes.join("\n"),
13827 wrap_column,
13828 tab_size,
13829 options.preserve_existing_whitespace,
13830 );
13831
13832 if let Some((indent, delimiter)) = first_line_delimiter {
13833 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
13834 }
13835 if let Some(last_line) = last_line_delimiter {
13836 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
13837 }
13838
13839 wrapped_text
13840 };
13841
13842 // TODO: should always use char-based diff while still supporting cursor behavior that
13843 // matches vim.
13844 let mut diff_options = DiffOptions::default();
13845 if options.override_language_settings {
13846 diff_options.max_word_diff_len = 0;
13847 diff_options.max_word_diff_line_count = 0;
13848 } else {
13849 diff_options.max_word_diff_len = usize::MAX;
13850 diff_options.max_word_diff_line_count = usize::MAX;
13851 }
13852
13853 for (old_range, new_text) in
13854 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
13855 {
13856 let edit_start = buffer.anchor_after(start_offset + old_range.start);
13857 let edit_end = buffer.anchor_after(start_offset + old_range.end);
13858 edits.push((edit_start..edit_end, new_text));
13859 }
13860
13861 rewrapped_row_ranges.push(start_row..=end_row);
13862 }
13863
13864 self.buffer
13865 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13866 }
13867
13868 pub fn cut_common(
13869 &mut self,
13870 cut_no_selection_line: bool,
13871 window: &mut Window,
13872 cx: &mut Context<Self>,
13873 ) -> ClipboardItem {
13874 let mut text = String::new();
13875 let buffer = self.buffer.read(cx).snapshot(cx);
13876 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13877 let mut clipboard_selections = Vec::with_capacity(selections.len());
13878 {
13879 let max_point = buffer.max_point();
13880 let mut is_first = true;
13881 let mut prev_selection_was_entire_line = false;
13882 for selection in &mut selections {
13883 let is_entire_line =
13884 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
13885 if is_entire_line {
13886 selection.start = Point::new(selection.start.row, 0);
13887 if !selection.is_empty() && selection.end.column == 0 {
13888 selection.end = cmp::min(max_point, selection.end);
13889 } else {
13890 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
13891 }
13892 selection.goal = SelectionGoal::None;
13893 }
13894 if is_first {
13895 is_first = false;
13896 } else if !prev_selection_was_entire_line {
13897 text += "\n";
13898 }
13899 prev_selection_was_entire_line = is_entire_line;
13900 let mut len = 0;
13901 for chunk in buffer.text_for_range(selection.start..selection.end) {
13902 text.push_str(chunk);
13903 len += chunk.len();
13904 }
13905
13906 clipboard_selections.push(ClipboardSelection::for_buffer(
13907 len,
13908 is_entire_line,
13909 selection.range(),
13910 &buffer,
13911 self.project.as_ref(),
13912 cx,
13913 ));
13914 }
13915 }
13916
13917 self.transact(window, cx, |this, window, cx| {
13918 this.change_selections(Default::default(), window, cx, |s| {
13919 s.select(selections);
13920 });
13921 this.insert("", window, cx);
13922 });
13923 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
13924 }
13925
13926 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
13927 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13928 let item = self.cut_common(true, window, cx);
13929 cx.write_to_clipboard(item);
13930 }
13931
13932 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
13933 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13934 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13935 s.move_with(&mut |snapshot, sel| {
13936 if sel.is_empty() {
13937 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
13938 }
13939 if sel.is_empty() {
13940 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13941 }
13942 });
13943 });
13944 let item = self.cut_common(false, window, cx);
13945 cx.set_global(KillRing(item))
13946 }
13947
13948 pub fn kill_ring_yank(
13949 &mut self,
13950 _: &KillRingYank,
13951 window: &mut Window,
13952 cx: &mut Context<Self>,
13953 ) {
13954 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13955 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
13956 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
13957 (kill_ring.text().to_string(), kill_ring.metadata_json())
13958 } else {
13959 return;
13960 }
13961 } else {
13962 return;
13963 };
13964 self.do_paste(&text, metadata, false, window, cx);
13965 }
13966
13967 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
13968 self.do_copy(true, cx);
13969 }
13970
13971 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
13972 self.do_copy(false, cx);
13973 }
13974
13975 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
13976 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13977 let buffer = self.buffer.read(cx).read(cx);
13978 let mut text = String::new();
13979 let mut clipboard_selections = Vec::with_capacity(selections.len());
13980
13981 let max_point = buffer.max_point();
13982 let mut is_first = true;
13983 for selection in &selections {
13984 let mut start = selection.start;
13985 let mut end = selection.end;
13986 let is_entire_line = selection.is_empty() || self.selections.line_mode();
13987 let mut add_trailing_newline = false;
13988 if is_entire_line {
13989 start = Point::new(start.row, 0);
13990 let next_line_start = Point::new(end.row + 1, 0);
13991 if next_line_start <= max_point {
13992 end = next_line_start;
13993 } else {
13994 // We're on the last line without a trailing newline.
13995 // Copy to the end of the line and add a newline afterwards.
13996 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
13997 add_trailing_newline = true;
13998 }
13999 }
14000
14001 let mut trimmed_selections = Vec::new();
14002 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
14003 let row = MultiBufferRow(start.row);
14004 let first_indent = buffer.indent_size_for_line(row);
14005 if first_indent.len == 0 || start.column > first_indent.len {
14006 trimmed_selections.push(start..end);
14007 } else {
14008 trimmed_selections.push(
14009 Point::new(row.0, first_indent.len)
14010 ..Point::new(row.0, buffer.line_len(row)),
14011 );
14012 for row in start.row + 1..=end.row {
14013 let mut line_len = buffer.line_len(MultiBufferRow(row));
14014 if row == end.row {
14015 line_len = end.column;
14016 }
14017 if line_len == 0 {
14018 trimmed_selections.push(Point::new(row, 0)..Point::new(row, line_len));
14019 continue;
14020 }
14021 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
14022 if row_indent_size.len >= first_indent.len {
14023 trimmed_selections
14024 .push(Point::new(row, first_indent.len)..Point::new(row, line_len));
14025 } else {
14026 trimmed_selections.clear();
14027 trimmed_selections.push(start..end);
14028 break;
14029 }
14030 }
14031 }
14032 } else {
14033 trimmed_selections.push(start..end);
14034 }
14035
14036 let is_multiline_trim = trimmed_selections.len() > 1;
14037 let mut selection_len: usize = 0;
14038 let prev_selection_was_entire_line = is_entire_line && !is_multiline_trim;
14039
14040 for trimmed_range in trimmed_selections {
14041 if is_first {
14042 is_first = false;
14043 } else if is_multiline_trim || !prev_selection_was_entire_line {
14044 text.push('\n');
14045 if is_multiline_trim {
14046 selection_len += 1;
14047 }
14048 }
14049 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
14050 text.push_str(chunk);
14051 selection_len += chunk.len();
14052 }
14053 if add_trailing_newline {
14054 text.push('\n');
14055 selection_len += 1;
14056 }
14057 }
14058
14059 clipboard_selections.push(ClipboardSelection::for_buffer(
14060 selection_len,
14061 is_entire_line,
14062 start..end,
14063 &buffer,
14064 self.project.as_ref(),
14065 cx,
14066 ));
14067 }
14068
14069 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
14070 text,
14071 clipboard_selections,
14072 ));
14073 }
14074
14075 pub fn do_paste(
14076 &mut self,
14077 text: &String,
14078 clipboard_selections: Option<Vec<ClipboardSelection>>,
14079 handle_entire_lines: bool,
14080 window: &mut Window,
14081 cx: &mut Context<Self>,
14082 ) {
14083 if self.read_only(cx) {
14084 return;
14085 }
14086
14087 self.finalize_last_transaction(cx);
14088
14089 let clipboard_text = Cow::Borrowed(text.as_str());
14090
14091 self.transact(window, cx, |this, window, cx| {
14092 let had_active_edit_prediction = this.has_active_edit_prediction();
14093 let display_map = this.display_snapshot(cx);
14094 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
14095 let cursor_offset = this
14096 .selections
14097 .last::<MultiBufferOffset>(&display_map)
14098 .head();
14099
14100 if let Some(mut clipboard_selections) = clipboard_selections {
14101 let all_selections_were_entire_line =
14102 clipboard_selections.iter().all(|s| s.is_entire_line);
14103 let first_selection_indent_column =
14104 clipboard_selections.first().map(|s| s.first_line_indent);
14105 if clipboard_selections.len() != old_selections.len() {
14106 clipboard_selections.drain(..);
14107 }
14108 let mut auto_indent_on_paste = true;
14109
14110 this.buffer.update(cx, |buffer, cx| {
14111 let snapshot = buffer.read(cx);
14112 auto_indent_on_paste = snapshot
14113 .language_settings_at(cursor_offset, cx)
14114 .auto_indent_on_paste;
14115
14116 let mut start_offset = 0;
14117 let mut edits = Vec::new();
14118 let mut original_indent_columns = Vec::new();
14119 for (ix, selection) in old_selections.iter().enumerate() {
14120 let to_insert;
14121 let entire_line;
14122 let original_indent_column;
14123 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
14124 let end_offset = start_offset + clipboard_selection.len;
14125 to_insert = &clipboard_text[start_offset..end_offset];
14126 entire_line = clipboard_selection.is_entire_line;
14127 start_offset = if entire_line {
14128 end_offset
14129 } else {
14130 end_offset + 1
14131 };
14132 original_indent_column = Some(clipboard_selection.first_line_indent);
14133 } else {
14134 to_insert = &*clipboard_text;
14135 entire_line = all_selections_were_entire_line;
14136 original_indent_column = first_selection_indent_column
14137 }
14138
14139 let (range, to_insert) =
14140 if selection.is_empty() && handle_entire_lines && entire_line {
14141 // If the corresponding selection was empty when this slice of the
14142 // clipboard text was written, then the entire line containing the
14143 // selection was copied. If this selection is also currently empty,
14144 // then paste the line before the current line of the buffer.
14145 let column = selection.start.to_point(&snapshot).column as usize;
14146 let line_start = selection.start - column;
14147 (line_start..line_start, Cow::Borrowed(to_insert))
14148 } else {
14149 let language = snapshot.language_at(selection.head());
14150 let range = selection.range();
14151 if let Some(language) = language
14152 && language.name() == "Markdown"
14153 {
14154 edit_for_markdown_paste(
14155 &snapshot,
14156 range,
14157 to_insert,
14158 url::Url::parse(to_insert).ok(),
14159 )
14160 } else {
14161 (range, Cow::Borrowed(to_insert))
14162 }
14163 };
14164
14165 edits.push((range, to_insert));
14166 original_indent_columns.push(original_indent_column);
14167 }
14168 drop(snapshot);
14169
14170 buffer.edit(
14171 edits,
14172 if auto_indent_on_paste {
14173 Some(AutoindentMode::Block {
14174 original_indent_columns,
14175 })
14176 } else {
14177 None
14178 },
14179 cx,
14180 );
14181 });
14182
14183 let selections = this
14184 .selections
14185 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
14186 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14187 } else {
14188 let url = url::Url::parse(&clipboard_text).ok();
14189
14190 let auto_indent_mode = if !clipboard_text.is_empty() {
14191 Some(AutoindentMode::Block {
14192 original_indent_columns: Vec::new(),
14193 })
14194 } else {
14195 None
14196 };
14197
14198 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
14199 let snapshot = buffer.snapshot(cx);
14200
14201 let anchors = old_selections
14202 .iter()
14203 .map(|s| {
14204 let anchor = snapshot.anchor_after(s.head());
14205 s.map(|_| anchor)
14206 })
14207 .collect::<Vec<_>>();
14208
14209 let mut edits = Vec::new();
14210
14211 // When pasting text without metadata (e.g. copied from an
14212 // external editor using multiple cursors) and the number of
14213 // lines matches the number of selections, distribute one
14214 // line per cursor instead of pasting the whole text at each.
14215 let lines: Vec<&str> = clipboard_text.split('\n').collect();
14216 let distribute_lines =
14217 old_selections.len() > 1 && lines.len() == old_selections.len();
14218
14219 for (ix, selection) in old_selections.iter().enumerate() {
14220 let language = snapshot.language_at(selection.head());
14221 let range = selection.range();
14222
14223 let text_for_cursor: &str = if distribute_lines {
14224 lines[ix]
14225 } else {
14226 &clipboard_text
14227 };
14228
14229 let (edit_range, edit_text) = if let Some(language) = language
14230 && language.name() == "Markdown"
14231 {
14232 edit_for_markdown_paste(&snapshot, range, text_for_cursor, url.clone())
14233 } else {
14234 (range, Cow::Borrowed(text_for_cursor))
14235 };
14236
14237 edits.push((edit_range, edit_text));
14238 }
14239
14240 drop(snapshot);
14241 buffer.edit(edits, auto_indent_mode, cx);
14242
14243 anchors
14244 });
14245
14246 this.change_selections(Default::default(), window, cx, |s| {
14247 s.select_anchors(selection_anchors);
14248 });
14249 }
14250
14251 // 🤔 | .. | show_in_menu |
14252 // | .. | true true
14253 // | had_edit_prediction | false true
14254
14255 let trigger_in_words =
14256 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
14257
14258 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
14259 });
14260 }
14261
14262 pub fn diff_clipboard_with_selection(
14263 &mut self,
14264 _: &DiffClipboardWithSelection,
14265 window: &mut Window,
14266 cx: &mut Context<Self>,
14267 ) {
14268 let selections = self
14269 .selections
14270 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
14271
14272 if selections.is_empty() {
14273 log::warn!("There should always be at least one selection in Zed. This is a bug.");
14274 return;
14275 };
14276
14277 let clipboard_text = cx.read_from_clipboard().and_then(|item| {
14278 item.entries().iter().find_map(|entry| match entry {
14279 ClipboardEntry::String(text) => Some(text.text().to_string()),
14280 _ => None,
14281 })
14282 });
14283
14284 let Some(clipboard_text) = clipboard_text else {
14285 log::warn!("Clipboard doesn't contain text.");
14286 return;
14287 };
14288
14289 window.dispatch_action(
14290 Box::new(DiffClipboardWithSelectionData {
14291 clipboard_text,
14292 editor: cx.entity(),
14293 }),
14294 cx,
14295 );
14296 }
14297
14298 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
14299 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14300 if let Some(item) = cx.read_from_clipboard() {
14301 let clipboard_string = item.entries().iter().find_map(|entry| match entry {
14302 ClipboardEntry::String(s) => Some(s),
14303 _ => None,
14304 });
14305 match clipboard_string {
14306 Some(clipboard_string) => self.do_paste(
14307 clipboard_string.text(),
14308 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
14309 true,
14310 window,
14311 cx,
14312 ),
14313 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
14314 }
14315 }
14316 }
14317
14318 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
14319 if self.read_only(cx) {
14320 return;
14321 }
14322
14323 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14324
14325 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
14326 if let Some((selections, _)) =
14327 self.selection_history.transaction(transaction_id).cloned()
14328 {
14329 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14330 s.select_anchors(selections.to_vec());
14331 });
14332 } else {
14333 log::error!(
14334 "No entry in selection_history found for undo. \
14335 This may correspond to a bug where undo does not update the selection. \
14336 If this is occurring, please add details to \
14337 https://github.com/zed-industries/zed/issues/22692"
14338 );
14339 }
14340 self.request_autoscroll(Autoscroll::fit(), cx);
14341 self.unmark_text(window, cx);
14342 self.refresh_edit_prediction(true, false, window, cx);
14343 cx.emit(EditorEvent::Edited { transaction_id });
14344 cx.emit(EditorEvent::TransactionUndone { transaction_id });
14345 }
14346 }
14347
14348 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
14349 if self.read_only(cx) {
14350 return;
14351 }
14352
14353 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14354
14355 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
14356 if let Some((_, Some(selections))) =
14357 self.selection_history.transaction(transaction_id).cloned()
14358 {
14359 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14360 s.select_anchors(selections.to_vec());
14361 });
14362 } else {
14363 log::error!(
14364 "No entry in selection_history found for redo. \
14365 This may correspond to a bug where undo does not update the selection. \
14366 If this is occurring, please add details to \
14367 https://github.com/zed-industries/zed/issues/22692"
14368 );
14369 }
14370 self.request_autoscroll(Autoscroll::fit(), cx);
14371 self.unmark_text(window, cx);
14372 self.refresh_edit_prediction(true, false, window, cx);
14373 cx.emit(EditorEvent::Edited { transaction_id });
14374 }
14375 }
14376
14377 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
14378 self.buffer
14379 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
14380 }
14381
14382 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
14383 self.buffer
14384 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
14385 }
14386
14387 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
14388 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14389 self.change_selections(Default::default(), window, cx, |s| {
14390 s.move_with(&mut |map, selection| {
14391 let cursor = if selection.is_empty() {
14392 movement::left(map, selection.start)
14393 } else {
14394 selection.start
14395 };
14396 selection.collapse_to(cursor, SelectionGoal::None);
14397 });
14398 })
14399 }
14400
14401 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
14402 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14403 self.change_selections(Default::default(), window, cx, |s| {
14404 s.move_heads_with(&mut |map, head, _| (movement::left(map, head), SelectionGoal::None));
14405 })
14406 }
14407
14408 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
14409 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14410 self.change_selections(Default::default(), window, cx, |s| {
14411 s.move_with(&mut |map, selection| {
14412 let cursor = if selection.is_empty() {
14413 movement::right(map, selection.end)
14414 } else {
14415 selection.end
14416 };
14417 selection.collapse_to(cursor, SelectionGoal::None)
14418 });
14419 })
14420 }
14421
14422 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
14423 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14424 self.change_selections(Default::default(), window, cx, |s| {
14425 s.move_heads_with(&mut |map, head, _| {
14426 (movement::right(map, head), SelectionGoal::None)
14427 });
14428 });
14429 }
14430
14431 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
14432 if self.take_rename(true, window, cx).is_some() {
14433 return;
14434 }
14435
14436 if self.mode.is_single_line() {
14437 cx.propagate();
14438 return;
14439 }
14440
14441 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14442
14443 let text_layout_details = &self.text_layout_details(window, cx);
14444 let selection_count = self.selections.count();
14445 let first_selection = self.selections.first_anchor();
14446
14447 self.change_selections(Default::default(), window, cx, |s| {
14448 s.move_with(&mut |map, selection| {
14449 if !selection.is_empty() {
14450 selection.goal = SelectionGoal::None;
14451 }
14452 let (cursor, goal) = movement::up(
14453 map,
14454 selection.start,
14455 selection.goal,
14456 false,
14457 text_layout_details,
14458 );
14459 selection.collapse_to(cursor, goal);
14460 });
14461 });
14462
14463 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14464 {
14465 cx.propagate();
14466 }
14467 }
14468
14469 pub fn move_up_by_lines(
14470 &mut self,
14471 action: &MoveUpByLines,
14472 window: &mut Window,
14473 cx: &mut Context<Self>,
14474 ) {
14475 if self.take_rename(true, window, cx).is_some() {
14476 return;
14477 }
14478
14479 if self.mode.is_single_line() {
14480 cx.propagate();
14481 return;
14482 }
14483
14484 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14485
14486 let text_layout_details = &self.text_layout_details(window, cx);
14487
14488 self.change_selections(Default::default(), window, cx, |s| {
14489 s.move_with(&mut |map, selection| {
14490 if !selection.is_empty() {
14491 selection.goal = SelectionGoal::None;
14492 }
14493 let (cursor, goal) = movement::up_by_rows(
14494 map,
14495 selection.start,
14496 action.lines,
14497 selection.goal,
14498 false,
14499 text_layout_details,
14500 );
14501 selection.collapse_to(cursor, goal);
14502 });
14503 })
14504 }
14505
14506 pub fn move_down_by_lines(
14507 &mut self,
14508 action: &MoveDownByLines,
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.mode.is_single_line() {
14517 cx.propagate();
14518 return;
14519 }
14520
14521 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14522
14523 let text_layout_details = &self.text_layout_details(window, cx);
14524
14525 self.change_selections(Default::default(), window, cx, |s| {
14526 s.move_with(&mut |map, selection| {
14527 if !selection.is_empty() {
14528 selection.goal = SelectionGoal::None;
14529 }
14530 let (cursor, goal) = movement::down_by_rows(
14531 map,
14532 selection.start,
14533 action.lines,
14534 selection.goal,
14535 false,
14536 text_layout_details,
14537 );
14538 selection.collapse_to(cursor, goal);
14539 });
14540 })
14541 }
14542
14543 pub fn select_down_by_lines(
14544 &mut self,
14545 action: &SelectDownByLines,
14546 window: &mut Window,
14547 cx: &mut Context<Self>,
14548 ) {
14549 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14550 let text_layout_details = &self.text_layout_details(window, cx);
14551 self.change_selections(Default::default(), window, cx, |s| {
14552 s.move_heads_with(&mut |map, head, goal| {
14553 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
14554 })
14555 })
14556 }
14557
14558 pub fn select_up_by_lines(
14559 &mut self,
14560 action: &SelectUpByLines,
14561 window: &mut Window,
14562 cx: &mut Context<Self>,
14563 ) {
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_by_rows(map, head, action.lines, goal, false, text_layout_details)
14569 })
14570 })
14571 }
14572
14573 pub fn select_page_up(
14574 &mut self,
14575 _: &SelectPageUp,
14576 window: &mut Window,
14577 cx: &mut Context<Self>,
14578 ) {
14579 let Some(row_count) = self.visible_row_count() else {
14580 return;
14581 };
14582
14583 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14584
14585 let text_layout_details = &self.text_layout_details(window, cx);
14586
14587 self.change_selections(Default::default(), window, cx, |s| {
14588 s.move_heads_with(&mut |map, head, goal| {
14589 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
14590 })
14591 })
14592 }
14593
14594 pub fn move_page_up(
14595 &mut self,
14596 action: &MovePageUp,
14597 window: &mut Window,
14598 cx: &mut Context<Self>,
14599 ) {
14600 if self.take_rename(true, window, cx).is_some() {
14601 return;
14602 }
14603
14604 if self
14605 .context_menu
14606 .borrow_mut()
14607 .as_mut()
14608 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
14609 .unwrap_or(false)
14610 {
14611 return;
14612 }
14613
14614 if matches!(self.mode, EditorMode::SingleLine) {
14615 cx.propagate();
14616 return;
14617 }
14618
14619 let Some(row_count) = self.visible_row_count() else {
14620 return;
14621 };
14622
14623 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14624
14625 let effects = if action.center_cursor {
14626 SelectionEffects::scroll(Autoscroll::center())
14627 } else {
14628 SelectionEffects::default()
14629 };
14630
14631 let text_layout_details = &self.text_layout_details(window, cx);
14632
14633 self.change_selections(effects, window, cx, |s| {
14634 s.move_with(&mut |map, selection| {
14635 if !selection.is_empty() {
14636 selection.goal = SelectionGoal::None;
14637 }
14638 let (cursor, goal) = movement::up_by_rows(
14639 map,
14640 selection.end,
14641 row_count,
14642 selection.goal,
14643 false,
14644 text_layout_details,
14645 );
14646 selection.collapse_to(cursor, goal);
14647 });
14648 });
14649 }
14650
14651 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
14652 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14653 let text_layout_details = &self.text_layout_details(window, cx);
14654 self.change_selections(Default::default(), window, cx, |s| {
14655 s.move_heads_with(&mut |map, head, goal| {
14656 movement::up(map, head, goal, false, text_layout_details)
14657 })
14658 })
14659 }
14660
14661 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
14662 self.take_rename(true, window, cx);
14663
14664 if self.mode.is_single_line() {
14665 cx.propagate();
14666 return;
14667 }
14668
14669 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14670
14671 let text_layout_details = &self.text_layout_details(window, cx);
14672 let selection_count = self.selections.count();
14673 let first_selection = self.selections.first_anchor();
14674
14675 self.change_selections(Default::default(), window, cx, |s| {
14676 s.move_with(&mut |map, selection| {
14677 if !selection.is_empty() {
14678 selection.goal = SelectionGoal::None;
14679 }
14680 let (cursor, goal) = movement::down(
14681 map,
14682 selection.end,
14683 selection.goal,
14684 false,
14685 text_layout_details,
14686 );
14687 selection.collapse_to(cursor, goal);
14688 });
14689 });
14690
14691 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14692 {
14693 cx.propagate();
14694 }
14695 }
14696
14697 pub fn select_page_down(
14698 &mut self,
14699 _: &SelectPageDown,
14700 window: &mut Window,
14701 cx: &mut Context<Self>,
14702 ) {
14703 let Some(row_count) = self.visible_row_count() else {
14704 return;
14705 };
14706
14707 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14708
14709 let text_layout_details = &self.text_layout_details(window, cx);
14710
14711 self.change_selections(Default::default(), window, cx, |s| {
14712 s.move_heads_with(&mut |map, head, goal| {
14713 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
14714 })
14715 })
14716 }
14717
14718 pub fn move_page_down(
14719 &mut self,
14720 action: &MovePageDown,
14721 window: &mut Window,
14722 cx: &mut Context<Self>,
14723 ) {
14724 if self.take_rename(true, window, cx).is_some() {
14725 return;
14726 }
14727
14728 if self
14729 .context_menu
14730 .borrow_mut()
14731 .as_mut()
14732 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
14733 .unwrap_or(false)
14734 {
14735 return;
14736 }
14737
14738 if matches!(self.mode, EditorMode::SingleLine) {
14739 cx.propagate();
14740 return;
14741 }
14742
14743 let Some(row_count) = self.visible_row_count() else {
14744 return;
14745 };
14746
14747 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14748
14749 let effects = if action.center_cursor {
14750 SelectionEffects::scroll(Autoscroll::center())
14751 } else {
14752 SelectionEffects::default()
14753 };
14754
14755 let text_layout_details = &self.text_layout_details(window, cx);
14756 self.change_selections(effects, window, cx, |s| {
14757 s.move_with(&mut |map, selection| {
14758 if !selection.is_empty() {
14759 selection.goal = SelectionGoal::None;
14760 }
14761 let (cursor, goal) = movement::down_by_rows(
14762 map,
14763 selection.end,
14764 row_count,
14765 selection.goal,
14766 false,
14767 text_layout_details,
14768 );
14769 selection.collapse_to(cursor, goal);
14770 });
14771 });
14772 }
14773
14774 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
14775 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14776 let text_layout_details = &self.text_layout_details(window, cx);
14777 self.change_selections(Default::default(), window, cx, |s| {
14778 s.move_heads_with(&mut |map, head, goal| {
14779 movement::down(map, head, goal, false, text_layout_details)
14780 })
14781 });
14782 }
14783
14784 pub fn context_menu_first(
14785 &mut self,
14786 _: &ContextMenuFirst,
14787 window: &mut Window,
14788 cx: &mut Context<Self>,
14789 ) {
14790 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14791 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
14792 }
14793 }
14794
14795 pub fn context_menu_prev(
14796 &mut self,
14797 _: &ContextMenuPrevious,
14798 window: &mut Window,
14799 cx: &mut Context<Self>,
14800 ) {
14801 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14802 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
14803 }
14804 }
14805
14806 pub fn context_menu_next(
14807 &mut self,
14808 _: &ContextMenuNext,
14809 window: &mut Window,
14810 cx: &mut Context<Self>,
14811 ) {
14812 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14813 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
14814 }
14815 }
14816
14817 pub fn context_menu_last(
14818 &mut self,
14819 _: &ContextMenuLast,
14820 window: &mut Window,
14821 cx: &mut Context<Self>,
14822 ) {
14823 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14824 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
14825 }
14826 }
14827
14828 pub fn signature_help_prev(
14829 &mut self,
14830 _: &SignatureHelpPrevious,
14831 _: &mut Window,
14832 cx: &mut Context<Self>,
14833 ) {
14834 if let Some(popover) = self.signature_help_state.popover_mut() {
14835 if popover.current_signature == 0 {
14836 popover.current_signature = popover.signatures.len() - 1;
14837 } else {
14838 popover.current_signature -= 1;
14839 }
14840 cx.notify();
14841 }
14842 }
14843
14844 pub fn signature_help_next(
14845 &mut self,
14846 _: &SignatureHelpNext,
14847 _: &mut Window,
14848 cx: &mut Context<Self>,
14849 ) {
14850 if let Some(popover) = self.signature_help_state.popover_mut() {
14851 if popover.current_signature + 1 == popover.signatures.len() {
14852 popover.current_signature = 0;
14853 } else {
14854 popover.current_signature += 1;
14855 }
14856 cx.notify();
14857 }
14858 }
14859
14860 pub fn move_to_previous_word_start(
14861 &mut self,
14862 _: &MoveToPreviousWordStart,
14863 window: &mut Window,
14864 cx: &mut Context<Self>,
14865 ) {
14866 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14867 self.change_selections(Default::default(), window, cx, |s| {
14868 s.move_cursors_with(&mut |map, head, _| {
14869 (
14870 movement::previous_word_start(map, head),
14871 SelectionGoal::None,
14872 )
14873 });
14874 })
14875 }
14876
14877 pub fn move_to_previous_subword_start(
14878 &mut self,
14879 _: &MoveToPreviousSubwordStart,
14880 window: &mut Window,
14881 cx: &mut Context<Self>,
14882 ) {
14883 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14884 self.change_selections(Default::default(), window, cx, |s| {
14885 s.move_cursors_with(&mut |map, head, _| {
14886 (
14887 movement::previous_subword_start(map, head),
14888 SelectionGoal::None,
14889 )
14890 });
14891 })
14892 }
14893
14894 pub fn select_to_previous_word_start(
14895 &mut self,
14896 _: &SelectToPreviousWordStart,
14897 window: &mut Window,
14898 cx: &mut Context<Self>,
14899 ) {
14900 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14901 self.change_selections(Default::default(), window, cx, |s| {
14902 s.move_heads_with(&mut |map, head, _| {
14903 (
14904 movement::previous_word_start(map, head),
14905 SelectionGoal::None,
14906 )
14907 });
14908 })
14909 }
14910
14911 pub fn select_to_previous_subword_start(
14912 &mut self,
14913 _: &SelectToPreviousSubwordStart,
14914 window: &mut Window,
14915 cx: &mut Context<Self>,
14916 ) {
14917 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14918 self.change_selections(Default::default(), window, cx, |s| {
14919 s.move_heads_with(&mut |map, head, _| {
14920 (
14921 movement::previous_subword_start(map, head),
14922 SelectionGoal::None,
14923 )
14924 });
14925 })
14926 }
14927
14928 pub fn delete_to_previous_word_start(
14929 &mut self,
14930 action: &DeleteToPreviousWordStart,
14931 window: &mut Window,
14932 cx: &mut Context<Self>,
14933 ) {
14934 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14935 self.transact(window, cx, |this, window, cx| {
14936 this.select_autoclose_pair(window, cx);
14937 this.change_selections(Default::default(), window, cx, |s| {
14938 s.move_with(&mut |map, selection| {
14939 if selection.is_empty() {
14940 let mut cursor = if action.ignore_newlines {
14941 movement::previous_word_start(map, selection.head())
14942 } else {
14943 movement::previous_word_start_or_newline(map, selection.head())
14944 };
14945 cursor = movement::adjust_greedy_deletion(
14946 map,
14947 selection.head(),
14948 cursor,
14949 action.ignore_brackets,
14950 );
14951 selection.set_head(cursor, SelectionGoal::None);
14952 }
14953 });
14954 });
14955 this.insert("", window, cx);
14956 });
14957 }
14958
14959 pub fn delete_to_previous_subword_start(
14960 &mut self,
14961 action: &DeleteToPreviousSubwordStart,
14962 window: &mut Window,
14963 cx: &mut Context<Self>,
14964 ) {
14965 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14966 self.transact(window, cx, |this, window, cx| {
14967 this.select_autoclose_pair(window, cx);
14968 this.change_selections(Default::default(), window, cx, |s| {
14969 s.move_with(&mut |map, selection| {
14970 if selection.is_empty() {
14971 let mut cursor = if action.ignore_newlines {
14972 movement::previous_subword_start(map, selection.head())
14973 } else {
14974 movement::previous_subword_start_or_newline(map, selection.head())
14975 };
14976 cursor = movement::adjust_greedy_deletion(
14977 map,
14978 selection.head(),
14979 cursor,
14980 action.ignore_brackets,
14981 );
14982 selection.set_head(cursor, SelectionGoal::None);
14983 }
14984 });
14985 });
14986 this.insert("", window, cx);
14987 });
14988 }
14989
14990 pub fn move_to_next_word_end(
14991 &mut self,
14992 _: &MoveToNextWordEnd,
14993 window: &mut Window,
14994 cx: &mut Context<Self>,
14995 ) {
14996 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14997 self.change_selections(Default::default(), window, cx, |s| {
14998 s.move_cursors_with(&mut |map, head, _| {
14999 (movement::next_word_end(map, head), SelectionGoal::None)
15000 });
15001 })
15002 }
15003
15004 pub fn move_to_next_subword_end(
15005 &mut self,
15006 _: &MoveToNextSubwordEnd,
15007 window: &mut Window,
15008 cx: &mut Context<Self>,
15009 ) {
15010 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15011 self.change_selections(Default::default(), window, cx, |s| {
15012 s.move_cursors_with(&mut |map, head, _| {
15013 (movement::next_subword_end(map, head), SelectionGoal::None)
15014 });
15015 })
15016 }
15017
15018 pub fn select_to_next_word_end(
15019 &mut self,
15020 _: &SelectToNextWordEnd,
15021 window: &mut Window,
15022 cx: &mut Context<Self>,
15023 ) {
15024 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15025 self.change_selections(Default::default(), window, cx, |s| {
15026 s.move_heads_with(&mut |map, head, _| {
15027 (movement::next_word_end(map, head), SelectionGoal::None)
15028 });
15029 })
15030 }
15031
15032 pub fn select_to_next_subword_end(
15033 &mut self,
15034 _: &SelectToNextSubwordEnd,
15035 window: &mut Window,
15036 cx: &mut Context<Self>,
15037 ) {
15038 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15039 self.change_selections(Default::default(), window, cx, |s| {
15040 s.move_heads_with(&mut |map, head, _| {
15041 (movement::next_subword_end(map, head), SelectionGoal::None)
15042 });
15043 })
15044 }
15045
15046 pub fn delete_to_next_word_end(
15047 &mut self,
15048 action: &DeleteToNextWordEnd,
15049 window: &mut Window,
15050 cx: &mut Context<Self>,
15051 ) {
15052 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15053 self.transact(window, cx, |this, window, cx| {
15054 this.change_selections(Default::default(), window, cx, |s| {
15055 s.move_with(&mut |map, selection| {
15056 if selection.is_empty() {
15057 let mut cursor = if action.ignore_newlines {
15058 movement::next_word_end(map, selection.head())
15059 } else {
15060 movement::next_word_end_or_newline(map, selection.head())
15061 };
15062 cursor = movement::adjust_greedy_deletion(
15063 map,
15064 selection.head(),
15065 cursor,
15066 action.ignore_brackets,
15067 );
15068 selection.set_head(cursor, SelectionGoal::None);
15069 }
15070 });
15071 });
15072 this.insert("", window, cx);
15073 });
15074 }
15075
15076 pub fn delete_to_next_subword_end(
15077 &mut self,
15078 action: &DeleteToNextSubwordEnd,
15079 window: &mut Window,
15080 cx: &mut Context<Self>,
15081 ) {
15082 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15083 self.transact(window, cx, |this, window, cx| {
15084 this.change_selections(Default::default(), window, cx, |s| {
15085 s.move_with(&mut |map, selection| {
15086 if selection.is_empty() {
15087 let mut cursor = if action.ignore_newlines {
15088 movement::next_subword_end(map, selection.head())
15089 } else {
15090 movement::next_subword_end_or_newline(map, selection.head())
15091 };
15092 cursor = movement::adjust_greedy_deletion(
15093 map,
15094 selection.head(),
15095 cursor,
15096 action.ignore_brackets,
15097 );
15098 selection.set_head(cursor, SelectionGoal::None);
15099 }
15100 });
15101 });
15102 this.insert("", window, cx);
15103 });
15104 }
15105
15106 pub fn move_to_beginning_of_line(
15107 &mut self,
15108 action: &MoveToBeginningOfLine,
15109 window: &mut Window,
15110 cx: &mut Context<Self>,
15111 ) {
15112 let stop_at_indent = action.stop_at_indent && !self.mode.is_single_line();
15113 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15114 self.change_selections(Default::default(), window, cx, |s| {
15115 s.move_cursors_with(&mut |map, head, _| {
15116 (
15117 movement::indented_line_beginning(
15118 map,
15119 head,
15120 action.stop_at_soft_wraps,
15121 stop_at_indent,
15122 ),
15123 SelectionGoal::None,
15124 )
15125 });
15126 })
15127 }
15128
15129 pub fn select_to_beginning_of_line(
15130 &mut self,
15131 action: &SelectToBeginningOfLine,
15132 window: &mut Window,
15133 cx: &mut Context<Self>,
15134 ) {
15135 let stop_at_indent = action.stop_at_indent && !self.mode.is_single_line();
15136 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15137 self.change_selections(Default::default(), window, cx, |s| {
15138 s.move_heads_with(&mut |map, head, _| {
15139 (
15140 movement::indented_line_beginning(
15141 map,
15142 head,
15143 action.stop_at_soft_wraps,
15144 stop_at_indent,
15145 ),
15146 SelectionGoal::None,
15147 )
15148 });
15149 });
15150 }
15151
15152 pub fn delete_to_beginning_of_line(
15153 &mut self,
15154 action: &DeleteToBeginningOfLine,
15155 window: &mut Window,
15156 cx: &mut Context<Self>,
15157 ) {
15158 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15159 self.transact(window, cx, |this, window, cx| {
15160 this.change_selections(Default::default(), window, cx, |s| {
15161 s.move_with(&mut |_, selection| {
15162 selection.reversed = true;
15163 });
15164 });
15165
15166 this.select_to_beginning_of_line(
15167 &SelectToBeginningOfLine {
15168 stop_at_soft_wraps: false,
15169 stop_at_indent: action.stop_at_indent,
15170 },
15171 window,
15172 cx,
15173 );
15174 this.backspace(&Backspace, window, cx);
15175 });
15176 }
15177
15178 pub fn move_to_end_of_line(
15179 &mut self,
15180 action: &MoveToEndOfLine,
15181 window: &mut Window,
15182 cx: &mut Context<Self>,
15183 ) {
15184 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15185 self.change_selections(Default::default(), window, cx, |s| {
15186 s.move_cursors_with(&mut |map, head, _| {
15187 (
15188 movement::line_end(map, head, action.stop_at_soft_wraps),
15189 SelectionGoal::None,
15190 )
15191 });
15192 })
15193 }
15194
15195 pub fn select_to_end_of_line(
15196 &mut self,
15197 action: &SelectToEndOfLine,
15198 window: &mut Window,
15199 cx: &mut Context<Self>,
15200 ) {
15201 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15202 self.change_selections(Default::default(), window, cx, |s| {
15203 s.move_heads_with(&mut |map, head, _| {
15204 (
15205 movement::line_end(map, head, action.stop_at_soft_wraps),
15206 SelectionGoal::None,
15207 )
15208 });
15209 })
15210 }
15211
15212 pub fn delete_to_end_of_line(
15213 &mut self,
15214 _: &DeleteToEndOfLine,
15215 window: &mut Window,
15216 cx: &mut Context<Self>,
15217 ) {
15218 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15219 self.transact(window, cx, |this, window, cx| {
15220 this.select_to_end_of_line(
15221 &SelectToEndOfLine {
15222 stop_at_soft_wraps: false,
15223 },
15224 window,
15225 cx,
15226 );
15227 this.delete(&Delete, window, cx);
15228 });
15229 }
15230
15231 pub fn cut_to_end_of_line(
15232 &mut self,
15233 action: &CutToEndOfLine,
15234 window: &mut Window,
15235 cx: &mut Context<Self>,
15236 ) {
15237 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15238 self.transact(window, cx, |this, window, cx| {
15239 this.select_to_end_of_line(
15240 &SelectToEndOfLine {
15241 stop_at_soft_wraps: false,
15242 },
15243 window,
15244 cx,
15245 );
15246 if !action.stop_at_newlines {
15247 this.change_selections(Default::default(), window, cx, |s| {
15248 s.move_with(&mut |_, sel| {
15249 if sel.is_empty() {
15250 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
15251 }
15252 });
15253 });
15254 }
15255 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15256 let item = this.cut_common(false, window, cx);
15257 cx.write_to_clipboard(item);
15258 });
15259 }
15260
15261 pub fn move_to_start_of_paragraph(
15262 &mut self,
15263 _: &MoveToStartOfParagraph,
15264 window: &mut Window,
15265 cx: &mut Context<Self>,
15266 ) {
15267 if matches!(self.mode, EditorMode::SingleLine) {
15268 cx.propagate();
15269 return;
15270 }
15271 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15272 self.change_selections(Default::default(), window, cx, |s| {
15273 s.move_with(&mut |map, selection| {
15274 selection.collapse_to(
15275 movement::start_of_paragraph(map, selection.head(), 1),
15276 SelectionGoal::None,
15277 )
15278 });
15279 })
15280 }
15281
15282 pub fn move_to_end_of_paragraph(
15283 &mut self,
15284 _: &MoveToEndOfParagraph,
15285 window: &mut Window,
15286 cx: &mut Context<Self>,
15287 ) {
15288 if matches!(self.mode, EditorMode::SingleLine) {
15289 cx.propagate();
15290 return;
15291 }
15292 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15293 self.change_selections(Default::default(), window, cx, |s| {
15294 s.move_with(&mut |map, selection| {
15295 selection.collapse_to(
15296 movement::end_of_paragraph(map, selection.head(), 1),
15297 SelectionGoal::None,
15298 )
15299 });
15300 })
15301 }
15302
15303 pub fn select_to_start_of_paragraph(
15304 &mut self,
15305 _: &SelectToStartOfParagraph,
15306 window: &mut Window,
15307 cx: &mut Context<Self>,
15308 ) {
15309 if matches!(self.mode, EditorMode::SingleLine) {
15310 cx.propagate();
15311 return;
15312 }
15313 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15314 self.change_selections(Default::default(), window, cx, |s| {
15315 s.move_heads_with(&mut |map, head, _| {
15316 (
15317 movement::start_of_paragraph(map, head, 1),
15318 SelectionGoal::None,
15319 )
15320 });
15321 })
15322 }
15323
15324 pub fn select_to_end_of_paragraph(
15325 &mut self,
15326 _: &SelectToEndOfParagraph,
15327 window: &mut Window,
15328 cx: &mut Context<Self>,
15329 ) {
15330 if matches!(self.mode, EditorMode::SingleLine) {
15331 cx.propagate();
15332 return;
15333 }
15334 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15335 self.change_selections(Default::default(), window, cx, |s| {
15336 s.move_heads_with(&mut |map, head, _| {
15337 (
15338 movement::end_of_paragraph(map, head, 1),
15339 SelectionGoal::None,
15340 )
15341 });
15342 })
15343 }
15344
15345 pub fn move_to_start_of_excerpt(
15346 &mut self,
15347 _: &MoveToStartOfExcerpt,
15348 window: &mut Window,
15349 cx: &mut Context<Self>,
15350 ) {
15351 if matches!(self.mode, EditorMode::SingleLine) {
15352 cx.propagate();
15353 return;
15354 }
15355 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15356 self.change_selections(Default::default(), window, cx, |s| {
15357 s.move_with(&mut |map, selection| {
15358 selection.collapse_to(
15359 movement::start_of_excerpt(
15360 map,
15361 selection.head(),
15362 workspace::searchable::Direction::Prev,
15363 ),
15364 SelectionGoal::None,
15365 )
15366 });
15367 })
15368 }
15369
15370 pub fn move_to_start_of_next_excerpt(
15371 &mut self,
15372 _: &MoveToStartOfNextExcerpt,
15373 window: &mut Window,
15374 cx: &mut Context<Self>,
15375 ) {
15376 if matches!(self.mode, EditorMode::SingleLine) {
15377 cx.propagate();
15378 return;
15379 }
15380
15381 self.change_selections(Default::default(), window, cx, |s| {
15382 s.move_with(&mut |map, selection| {
15383 selection.collapse_to(
15384 movement::start_of_excerpt(
15385 map,
15386 selection.head(),
15387 workspace::searchable::Direction::Next,
15388 ),
15389 SelectionGoal::None,
15390 )
15391 });
15392 })
15393 }
15394
15395 pub fn move_to_end_of_excerpt(
15396 &mut self,
15397 _: &MoveToEndOfExcerpt,
15398 window: &mut Window,
15399 cx: &mut Context<Self>,
15400 ) {
15401 if matches!(self.mode, EditorMode::SingleLine) {
15402 cx.propagate();
15403 return;
15404 }
15405 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15406 self.change_selections(Default::default(), window, cx, |s| {
15407 s.move_with(&mut |map, selection| {
15408 selection.collapse_to(
15409 movement::end_of_excerpt(
15410 map,
15411 selection.head(),
15412 workspace::searchable::Direction::Next,
15413 ),
15414 SelectionGoal::None,
15415 )
15416 });
15417 })
15418 }
15419
15420 pub fn move_to_end_of_previous_excerpt(
15421 &mut self,
15422 _: &MoveToEndOfPreviousExcerpt,
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_with(&mut |map, selection| {
15433 selection.collapse_to(
15434 movement::end_of_excerpt(
15435 map,
15436 selection.head(),
15437 workspace::searchable::Direction::Prev,
15438 ),
15439 SelectionGoal::None,
15440 )
15441 });
15442 })
15443 }
15444
15445 pub fn select_to_start_of_excerpt(
15446 &mut self,
15447 _: &SelectToStartOfExcerpt,
15448 window: &mut Window,
15449 cx: &mut Context<Self>,
15450 ) {
15451 if matches!(self.mode, EditorMode::SingleLine) {
15452 cx.propagate();
15453 return;
15454 }
15455 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15456 self.change_selections(Default::default(), window, cx, |s| {
15457 s.move_heads_with(&mut |map, head, _| {
15458 (
15459 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15460 SelectionGoal::None,
15461 )
15462 });
15463 })
15464 }
15465
15466 pub fn select_to_start_of_next_excerpt(
15467 &mut self,
15468 _: &SelectToStartOfNextExcerpt,
15469 window: &mut Window,
15470 cx: &mut Context<Self>,
15471 ) {
15472 if matches!(self.mode, EditorMode::SingleLine) {
15473 cx.propagate();
15474 return;
15475 }
15476 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15477 self.change_selections(Default::default(), window, cx, |s| {
15478 s.move_heads_with(&mut |map, head, _| {
15479 (
15480 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
15481 SelectionGoal::None,
15482 )
15483 });
15484 })
15485 }
15486
15487 pub fn select_to_end_of_excerpt(
15488 &mut self,
15489 _: &SelectToEndOfExcerpt,
15490 window: &mut Window,
15491 cx: &mut Context<Self>,
15492 ) {
15493 if matches!(self.mode, EditorMode::SingleLine) {
15494 cx.propagate();
15495 return;
15496 }
15497 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15498 self.change_selections(Default::default(), window, cx, |s| {
15499 s.move_heads_with(&mut |map, head, _| {
15500 (
15501 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
15502 SelectionGoal::None,
15503 )
15504 });
15505 })
15506 }
15507
15508 pub fn select_to_end_of_previous_excerpt(
15509 &mut self,
15510 _: &SelectToEndOfPreviousExcerpt,
15511 window: &mut Window,
15512 cx: &mut Context<Self>,
15513 ) {
15514 if matches!(self.mode, EditorMode::SingleLine) {
15515 cx.propagate();
15516 return;
15517 }
15518 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15519 self.change_selections(Default::default(), window, cx, |s| {
15520 s.move_heads_with(&mut |map, head, _| {
15521 (
15522 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15523 SelectionGoal::None,
15524 )
15525 });
15526 })
15527 }
15528
15529 pub fn move_to_beginning(
15530 &mut self,
15531 _: &MoveToBeginning,
15532 window: &mut Window,
15533 cx: &mut Context<Self>,
15534 ) {
15535 if matches!(self.mode, EditorMode::SingleLine) {
15536 cx.propagate();
15537 return;
15538 }
15539 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15540 self.change_selections(Default::default(), window, cx, |s| {
15541 s.select_ranges(vec![Anchor::Min..Anchor::Min]);
15542 });
15543 }
15544
15545 pub fn select_to_beginning(
15546 &mut self,
15547 _: &SelectToBeginning,
15548 window: &mut Window,
15549 cx: &mut Context<Self>,
15550 ) {
15551 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
15552 selection.set_head(Point::zero(), SelectionGoal::None);
15553 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15554 self.change_selections(Default::default(), window, cx, |s| {
15555 s.select(vec![selection]);
15556 });
15557 }
15558
15559 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
15560 if matches!(self.mode, EditorMode::SingleLine) {
15561 cx.propagate();
15562 return;
15563 }
15564 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15565 let cursor = self.buffer.read(cx).read(cx).len();
15566 self.change_selections(Default::default(), window, cx, |s| {
15567 s.select_ranges(vec![cursor..cursor])
15568 });
15569 }
15570
15571 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
15572 self.nav_history = nav_history;
15573 }
15574
15575 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
15576 self.nav_history.as_ref()
15577 }
15578
15579 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
15580 self.push_to_nav_history(
15581 self.selections.newest_anchor().head(),
15582 None,
15583 false,
15584 true,
15585 cx,
15586 );
15587 }
15588
15589 fn navigation_data(&self, cursor_anchor: Anchor, cx: &mut Context<Self>) -> NavigationData {
15590 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15591 let buffer = self.buffer.read(cx).read(cx);
15592 let cursor_position = cursor_anchor.to_point(&buffer);
15593 let scroll_anchor = self.scroll_manager.native_anchor(&display_snapshot, cx);
15594 let scroll_top_row = scroll_anchor.top_row(&buffer);
15595 drop(buffer);
15596
15597 NavigationData {
15598 cursor_anchor,
15599 cursor_position,
15600 scroll_anchor,
15601 scroll_top_row,
15602 }
15603 }
15604
15605 fn navigation_entry(
15606 &self,
15607 cursor_anchor: Anchor,
15608 cx: &mut Context<Self>,
15609 ) -> Option<NavigationEntry> {
15610 let Some(history) = self.nav_history.clone() else {
15611 return None;
15612 };
15613 let data = self.navigation_data(cursor_anchor, cx);
15614 Some(history.navigation_entry(Some(Arc::new(data) as Arc<dyn Any + Send + Sync>)))
15615 }
15616
15617 fn push_to_nav_history(
15618 &mut self,
15619 cursor_anchor: Anchor,
15620 new_position: Option<Point>,
15621 is_deactivate: bool,
15622 always: bool,
15623 cx: &mut Context<Self>,
15624 ) {
15625 let data = self.navigation_data(cursor_anchor, cx);
15626 if let Some(nav_history) = self.nav_history.as_mut() {
15627 if let Some(new_position) = new_position {
15628 let row_delta = (new_position.row as i64 - data.cursor_position.row as i64).abs();
15629 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
15630 return;
15631 }
15632 }
15633
15634 let cursor_row = data.cursor_position.row;
15635 nav_history.push(Some(data), Some(cursor_row), cx);
15636 cx.emit(EditorEvent::PushedToNavHistory {
15637 anchor: cursor_anchor,
15638 is_deactivate,
15639 })
15640 }
15641 }
15642
15643 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
15644 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15645 let buffer = self.buffer.read(cx).snapshot(cx);
15646 let mut selection = self
15647 .selections
15648 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
15649 selection.set_head(buffer.len(), SelectionGoal::None);
15650 self.change_selections(Default::default(), window, cx, |s| {
15651 s.select(vec![selection]);
15652 });
15653 }
15654
15655 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
15656 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15657 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15658 s.select_ranges(vec![Anchor::Min..Anchor::Max]);
15659 });
15660 }
15661
15662 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
15663 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15664 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15665 let mut selections = self.selections.all::<Point>(&display_map);
15666 let max_point = display_map.buffer_snapshot().max_point();
15667 for selection in &mut selections {
15668 let rows = selection.spanned_rows(true, &display_map);
15669 selection.start = Point::new(rows.start.0, 0);
15670 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
15671 selection.reversed = false;
15672 }
15673 self.change_selections(Default::default(), window, cx, |s| {
15674 s.select(selections);
15675 });
15676 }
15677
15678 pub fn split_selection_into_lines(
15679 &mut self,
15680 action: &SplitSelectionIntoLines,
15681 window: &mut Window,
15682 cx: &mut Context<Self>,
15683 ) {
15684 let selections = self
15685 .selections
15686 .all::<Point>(&self.display_snapshot(cx))
15687 .into_iter()
15688 .map(|selection| selection.start..selection.end)
15689 .collect::<Vec<_>>();
15690 self.unfold_ranges(&selections, true, false, cx);
15691
15692 let mut new_selection_ranges = Vec::new();
15693 {
15694 let buffer = self.buffer.read(cx).read(cx);
15695 for selection in selections {
15696 for row in selection.start.row..selection.end.row {
15697 let line_start = Point::new(row, 0);
15698 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
15699
15700 if action.keep_selections {
15701 // Keep the selection range for each line
15702 let selection_start = if row == selection.start.row {
15703 selection.start
15704 } else {
15705 line_start
15706 };
15707 new_selection_ranges.push(selection_start..line_end);
15708 } else {
15709 // Collapse to cursor at end of line
15710 new_selection_ranges.push(line_end..line_end);
15711 }
15712 }
15713
15714 let is_multiline_selection = selection.start.row != selection.end.row;
15715 // Don't insert last one if it's a multi-line selection ending at the start of a line,
15716 // so this action feels more ergonomic when paired with other selection operations
15717 let should_skip_last = is_multiline_selection && selection.end.column == 0;
15718 if !should_skip_last {
15719 if action.keep_selections {
15720 if is_multiline_selection {
15721 let line_start = Point::new(selection.end.row, 0);
15722 new_selection_ranges.push(line_start..selection.end);
15723 } else {
15724 new_selection_ranges.push(selection.start..selection.end);
15725 }
15726 } else {
15727 new_selection_ranges.push(selection.end..selection.end);
15728 }
15729 }
15730 }
15731 }
15732 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15733 s.select_ranges(new_selection_ranges);
15734 });
15735 }
15736
15737 pub fn add_selection_above(
15738 &mut self,
15739 action: &AddSelectionAbove,
15740 window: &mut Window,
15741 cx: &mut Context<Self>,
15742 ) {
15743 self.add_selection(true, action.skip_soft_wrap, window, cx);
15744 }
15745
15746 pub fn add_selection_below(
15747 &mut self,
15748 action: &AddSelectionBelow,
15749 window: &mut Window,
15750 cx: &mut Context<Self>,
15751 ) {
15752 self.add_selection(false, action.skip_soft_wrap, window, cx);
15753 }
15754
15755 fn add_selection(
15756 &mut self,
15757 above: bool,
15758 skip_soft_wrap: bool,
15759 window: &mut Window,
15760 cx: &mut Context<Self>,
15761 ) {
15762 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15763
15764 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15765 let all_selections = self.selections.all::<Point>(&display_map);
15766 let text_layout_details = self.text_layout_details(window, cx);
15767
15768 let (mut columnar_selections, new_selections_to_columnarize) = {
15769 if let Some(state) = self.add_selections_state.as_ref() {
15770 let columnar_selection_ids: HashSet<_> = state
15771 .groups
15772 .iter()
15773 .flat_map(|group| group.stack.iter())
15774 .copied()
15775 .collect();
15776
15777 all_selections
15778 .into_iter()
15779 .partition(|s| columnar_selection_ids.contains(&s.id))
15780 } else {
15781 (Vec::new(), all_selections)
15782 }
15783 };
15784
15785 let mut state = self
15786 .add_selections_state
15787 .take()
15788 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
15789
15790 for selection in new_selections_to_columnarize {
15791 let range = selection.display_range(&display_map).sorted();
15792 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
15793 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
15794 let positions = start_x.min(end_x)..start_x.max(end_x);
15795 let mut stack = Vec::new();
15796 for row in range.start.row().0..=range.end.row().0 {
15797 if let Some(selection) = self.selections.build_columnar_selection(
15798 &display_map,
15799 DisplayRow(row),
15800 &positions,
15801 selection.reversed,
15802 &text_layout_details,
15803 ) {
15804 stack.push(selection.id);
15805 columnar_selections.push(selection);
15806 }
15807 }
15808 if !stack.is_empty() {
15809 if above {
15810 stack.reverse();
15811 }
15812 state.groups.push(AddSelectionsGroup { above, stack });
15813 }
15814 }
15815
15816 let mut final_selections = Vec::new();
15817 let end_row = if above {
15818 DisplayRow(0)
15819 } else {
15820 display_map.max_point().row()
15821 };
15822
15823 // When `skip_soft_wrap` is true, we use UTF-16 columns instead of pixel
15824 // positions to place new selections, so we need to keep track of the
15825 // column range of the oldest selection in each group, because
15826 // intermediate selections may have been clamped to shorter lines.
15827 let mut goal_columns_by_selection_id = if skip_soft_wrap {
15828 let mut map = HashMap::default();
15829 for group in state.groups.iter() {
15830 if let Some(oldest_id) = group.stack.first() {
15831 if let Some(oldest_selection) =
15832 columnar_selections.iter().find(|s| s.id == *oldest_id)
15833 {
15834 let snapshot = display_map.buffer_snapshot();
15835 let start_col =
15836 snapshot.point_to_point_utf16(oldest_selection.start).column;
15837 let end_col = snapshot.point_to_point_utf16(oldest_selection.end).column;
15838 let goal_columns = start_col.min(end_col)..start_col.max(end_col);
15839 for id in &group.stack {
15840 map.insert(*id, goal_columns.clone());
15841 }
15842 }
15843 }
15844 }
15845 map
15846 } else {
15847 HashMap::default()
15848 };
15849
15850 let mut last_added_item_per_group = HashMap::default();
15851 for group in state.groups.iter_mut() {
15852 if let Some(last_id) = group.stack.last() {
15853 last_added_item_per_group.insert(*last_id, group);
15854 }
15855 }
15856
15857 for selection in columnar_selections {
15858 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
15859 if above == group.above {
15860 let range = selection.display_range(&display_map).sorted();
15861 debug_assert_eq!(range.start.row(), range.end.row());
15862 let row = range.start.row();
15863 let positions =
15864 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
15865 Pixels::from(start)..Pixels::from(end)
15866 } else {
15867 let start_x =
15868 display_map.x_for_display_point(range.start, &text_layout_details);
15869 let end_x =
15870 display_map.x_for_display_point(range.end, &text_layout_details);
15871 start_x.min(end_x)..start_x.max(end_x)
15872 };
15873
15874 let maybe_new_selection = if skip_soft_wrap {
15875 let goal_columns = goal_columns_by_selection_id
15876 .remove(&selection.id)
15877 .unwrap_or_else(|| {
15878 let snapshot = display_map.buffer_snapshot();
15879 let start_col =
15880 snapshot.point_to_point_utf16(selection.start).column;
15881 let end_col = snapshot.point_to_point_utf16(selection.end).column;
15882 start_col.min(end_col)..start_col.max(end_col)
15883 });
15884 self.selections.find_next_columnar_selection_by_buffer_row(
15885 &display_map,
15886 row,
15887 end_row,
15888 above,
15889 &goal_columns,
15890 selection.reversed,
15891 &text_layout_details,
15892 )
15893 } else {
15894 self.selections.find_next_columnar_selection_by_display_row(
15895 &display_map,
15896 row,
15897 end_row,
15898 above,
15899 &positions,
15900 selection.reversed,
15901 &text_layout_details,
15902 )
15903 };
15904
15905 if let Some(new_selection) = maybe_new_selection {
15906 group.stack.push(new_selection.id);
15907 if above {
15908 final_selections.push(new_selection);
15909 final_selections.push(selection);
15910 } else {
15911 final_selections.push(selection);
15912 final_selections.push(new_selection);
15913 }
15914 } else {
15915 final_selections.push(selection);
15916 }
15917 } else {
15918 group.stack.pop();
15919 }
15920 } else {
15921 final_selections.push(selection);
15922 }
15923 }
15924
15925 self.change_selections(Default::default(), window, cx, |s| {
15926 s.select(final_selections);
15927 });
15928
15929 let final_selection_ids: HashSet<_> = self
15930 .selections
15931 .all::<Point>(&display_map)
15932 .iter()
15933 .map(|s| s.id)
15934 .collect();
15935 state.groups.retain_mut(|group| {
15936 // selections might get merged above so we remove invalid items from stacks
15937 group.stack.retain(|id| final_selection_ids.contains(id));
15938
15939 // single selection in stack can be treated as initial state
15940 group.stack.len() > 1
15941 });
15942
15943 if !state.groups.is_empty() {
15944 self.add_selections_state = Some(state);
15945 }
15946 }
15947
15948 pub fn insert_snippet_at_selections(
15949 &mut self,
15950 action: &InsertSnippet,
15951 window: &mut Window,
15952 cx: &mut Context<Self>,
15953 ) {
15954 self.try_insert_snippet_at_selections(action, window, cx)
15955 .log_err();
15956 }
15957
15958 fn try_insert_snippet_at_selections(
15959 &mut self,
15960 action: &InsertSnippet,
15961 window: &mut Window,
15962 cx: &mut Context<Self>,
15963 ) -> Result<()> {
15964 let insertion_ranges = self
15965 .selections
15966 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15967 .into_iter()
15968 .map(|selection| selection.range())
15969 .collect_vec();
15970
15971 let snippet = if let Some(snippet_body) = &action.snippet {
15972 if action.language.is_none() && action.name.is_none() {
15973 Snippet::parse(snippet_body)?
15974 } else {
15975 bail!("`snippet` is mutually exclusive with `language` and `name`")
15976 }
15977 } else if let Some(name) = &action.name {
15978 let project = self.project().context("no project")?;
15979 let snippet_store = project.read(cx).snippets().read(cx);
15980 let snippet = snippet_store
15981 .snippets_for(action.language.clone(), cx)
15982 .into_iter()
15983 .find(|snippet| snippet.name == *name)
15984 .context("snippet not found")?;
15985 Snippet::parse(&snippet.body)?
15986 } else {
15987 // todo(andrew): open modal to select snippet
15988 bail!("`name` or `snippet` is required")
15989 };
15990
15991 self.insert_snippet(&insertion_ranges, snippet, window, cx)
15992 }
15993
15994 fn select_match_ranges(
15995 &mut self,
15996 range: Range<MultiBufferOffset>,
15997 reversed: bool,
15998 replace_newest: bool,
15999 auto_scroll: Option<Autoscroll>,
16000 window: &mut Window,
16001 cx: &mut Context<Editor>,
16002 ) {
16003 self.unfold_ranges(
16004 std::slice::from_ref(&range),
16005 false,
16006 auto_scroll.is_some(),
16007 cx,
16008 );
16009 let effects = if let Some(scroll) = auto_scroll {
16010 SelectionEffects::scroll(scroll)
16011 } else {
16012 SelectionEffects::no_scroll()
16013 };
16014 self.change_selections(effects, window, cx, |s| {
16015 if replace_newest {
16016 s.delete(s.newest_anchor().id);
16017 }
16018 if reversed {
16019 s.insert_range(range.end..range.start);
16020 } else {
16021 s.insert_range(range);
16022 }
16023 });
16024 }
16025
16026 pub fn select_next_match_internal(
16027 &mut self,
16028 display_map: &DisplaySnapshot,
16029 replace_newest: bool,
16030 autoscroll: Option<Autoscroll>,
16031 window: &mut Window,
16032 cx: &mut Context<Self>,
16033 ) -> Result<()> {
16034 let buffer = display_map.buffer_snapshot();
16035 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
16036 if let Some(mut select_next_state) = self.select_next_state.take() {
16037 let query = &select_next_state.query;
16038 if !select_next_state.done {
16039 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
16040 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
16041 let mut next_selected_range = None;
16042
16043 let bytes_after_last_selection =
16044 buffer.bytes_in_range(last_selection.end..buffer.len());
16045 let bytes_before_first_selection =
16046 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
16047 let query_matches = query
16048 .stream_find_iter(bytes_after_last_selection)
16049 .map(|result| (last_selection.end, result))
16050 .chain(
16051 query
16052 .stream_find_iter(bytes_before_first_selection)
16053 .map(|result| (MultiBufferOffset(0), result)),
16054 );
16055
16056 for (start_offset, query_match) in query_matches {
16057 let query_match = query_match.unwrap(); // can only fail due to I/O
16058 let offset_range =
16059 start_offset + query_match.start()..start_offset + query_match.end();
16060
16061 if !select_next_state.wordwise
16062 || (!buffer.is_inside_word(offset_range.start, None)
16063 && !buffer.is_inside_word(offset_range.end, None))
16064 {
16065 let idx = selections
16066 .partition_point(|selection| selection.end <= offset_range.start);
16067 let overlaps = selections
16068 .get(idx)
16069 .map_or(false, |selection| selection.start < offset_range.end);
16070
16071 if !overlaps {
16072 next_selected_range = Some(offset_range);
16073 break;
16074 }
16075 }
16076 }
16077
16078 if let Some(next_selected_range) = next_selected_range {
16079 self.select_match_ranges(
16080 next_selected_range,
16081 last_selection.reversed,
16082 replace_newest,
16083 autoscroll,
16084 window,
16085 cx,
16086 );
16087 } else {
16088 select_next_state.done = true;
16089 }
16090 }
16091
16092 self.select_next_state = Some(select_next_state);
16093 } else {
16094 let mut only_carets = true;
16095 let mut same_text_selected = true;
16096 let mut selected_text = None;
16097
16098 let mut selections_iter = selections.iter().peekable();
16099 while let Some(selection) = selections_iter.next() {
16100 if selection.start != selection.end {
16101 only_carets = false;
16102 }
16103
16104 if same_text_selected {
16105 if selected_text.is_none() {
16106 selected_text =
16107 Some(buffer.text_for_range(selection.range()).collect::<String>());
16108 }
16109
16110 if let Some(next_selection) = selections_iter.peek() {
16111 if next_selection.len() == selection.len() {
16112 let next_selected_text = buffer
16113 .text_for_range(next_selection.range())
16114 .collect::<String>();
16115 if Some(next_selected_text) != selected_text {
16116 same_text_selected = false;
16117 selected_text = None;
16118 }
16119 } else {
16120 same_text_selected = false;
16121 selected_text = None;
16122 }
16123 }
16124 }
16125 }
16126
16127 if only_carets {
16128 for selection in &mut selections {
16129 let (word_range, _) = buffer.surrounding_word(selection.start, None);
16130 selection.start = word_range.start;
16131 selection.end = word_range.end;
16132 selection.goal = SelectionGoal::None;
16133 selection.reversed = false;
16134 self.select_match_ranges(
16135 selection.start..selection.end,
16136 selection.reversed,
16137 replace_newest,
16138 autoscroll,
16139 window,
16140 cx,
16141 );
16142 }
16143
16144 if selections.len() == 1 {
16145 let selection = selections
16146 .last()
16147 .expect("ensured that there's only one selection");
16148 let query = buffer
16149 .text_for_range(selection.start..selection.end)
16150 .collect::<String>();
16151 let is_empty = query.is_empty();
16152 let select_state = SelectNextState {
16153 query: self.build_query(&[query], cx)?,
16154 wordwise: true,
16155 done: is_empty,
16156 };
16157 self.select_next_state = Some(select_state);
16158 } else {
16159 self.select_next_state = None;
16160 }
16161 } else if let Some(selected_text) = selected_text {
16162 self.select_next_state = Some(SelectNextState {
16163 query: self.build_query(&[selected_text], cx)?,
16164 wordwise: false,
16165 done: false,
16166 });
16167 self.select_next_match_internal(
16168 display_map,
16169 replace_newest,
16170 autoscroll,
16171 window,
16172 cx,
16173 )?;
16174 }
16175 }
16176 Ok(())
16177 }
16178
16179 pub fn select_all_matches(
16180 &mut self,
16181 _action: &SelectAllMatches,
16182 window: &mut Window,
16183 cx: &mut Context<Self>,
16184 ) -> Result<()> {
16185 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16186
16187 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16188
16189 self.select_next_match_internal(&display_map, false, None, window, cx)?;
16190 let Some(select_next_state) = self.select_next_state.as_mut().filter(|state| !state.done)
16191 else {
16192 return Ok(());
16193 };
16194
16195 let mut new_selections = Vec::new();
16196 let initial_selection = self.selections.oldest::<MultiBufferOffset>(&display_map);
16197 let reversed = initial_selection.reversed;
16198 let buffer = display_map.buffer_snapshot();
16199 let query_matches = select_next_state
16200 .query
16201 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
16202
16203 for query_match in query_matches.into_iter() {
16204 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
16205 let offset_range = if reversed {
16206 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
16207 } else {
16208 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
16209 };
16210
16211 let is_partial_word_match = select_next_state.wordwise
16212 && (buffer.is_inside_word(offset_range.start, None)
16213 || buffer.is_inside_word(offset_range.end, None));
16214
16215 let is_initial_selection = MultiBufferOffset(query_match.start())
16216 == initial_selection.start
16217 && MultiBufferOffset(query_match.end()) == initial_selection.end;
16218
16219 if !is_partial_word_match && !is_initial_selection {
16220 new_selections.push(offset_range);
16221 }
16222 }
16223
16224 // Ensure that the initial range is the last selection, as
16225 // `MutableSelectionsCollection::select_ranges` makes the last selection
16226 // the newest selection, which the editor then relies on as the primary
16227 // cursor for scroll targeting. Without this, the last match would then
16228 // be automatically focused when the user started editing the selected
16229 // matches.
16230 let initial_directed_range = if reversed {
16231 initial_selection.end..initial_selection.start
16232 } else {
16233 initial_selection.start..initial_selection.end
16234 };
16235 new_selections.push(initial_directed_range);
16236
16237 select_next_state.done = true;
16238 self.unfold_ranges(&new_selections, false, false, cx);
16239 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
16240 selections.select_ranges(new_selections)
16241 });
16242
16243 Ok(())
16244 }
16245
16246 pub fn select_next(
16247 &mut self,
16248 action: &SelectNext,
16249 window: &mut Window,
16250 cx: &mut Context<Self>,
16251 ) -> Result<()> {
16252 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16253 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16254 self.select_next_match_internal(
16255 &display_map,
16256 action.replace_newest,
16257 Some(Autoscroll::newest()),
16258 window,
16259 cx,
16260 )
16261 }
16262
16263 pub fn select_previous(
16264 &mut self,
16265 action: &SelectPrevious,
16266 window: &mut Window,
16267 cx: &mut Context<Self>,
16268 ) -> Result<()> {
16269 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16270 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16271 let buffer = display_map.buffer_snapshot();
16272 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
16273 if let Some(mut select_prev_state) = self.select_prev_state.take() {
16274 let query = &select_prev_state.query;
16275 if !select_prev_state.done {
16276 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
16277 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
16278 let mut next_selected_range = None;
16279 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
16280 let bytes_before_last_selection =
16281 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
16282 let bytes_after_first_selection =
16283 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
16284 let query_matches = query
16285 .stream_find_iter(bytes_before_last_selection)
16286 .map(|result| (last_selection.start, result))
16287 .chain(
16288 query
16289 .stream_find_iter(bytes_after_first_selection)
16290 .map(|result| (buffer.len(), result)),
16291 );
16292 for (end_offset, query_match) in query_matches {
16293 let query_match = query_match.unwrap(); // can only fail due to I/O
16294 let offset_range =
16295 end_offset - query_match.end()..end_offset - query_match.start();
16296
16297 if !select_prev_state.wordwise
16298 || (!buffer.is_inside_word(offset_range.start, None)
16299 && !buffer.is_inside_word(offset_range.end, None))
16300 {
16301 next_selected_range = Some(offset_range);
16302 break;
16303 }
16304 }
16305
16306 if let Some(next_selected_range) = next_selected_range {
16307 self.select_match_ranges(
16308 next_selected_range,
16309 last_selection.reversed,
16310 action.replace_newest,
16311 Some(Autoscroll::newest()),
16312 window,
16313 cx,
16314 );
16315 } else {
16316 select_prev_state.done = true;
16317 }
16318 }
16319
16320 self.select_prev_state = Some(select_prev_state);
16321 } else {
16322 let mut only_carets = true;
16323 let mut same_text_selected = true;
16324 let mut selected_text = None;
16325
16326 let mut selections_iter = selections.iter().peekable();
16327 while let Some(selection) = selections_iter.next() {
16328 if selection.start != selection.end {
16329 only_carets = false;
16330 }
16331
16332 if same_text_selected {
16333 if selected_text.is_none() {
16334 selected_text =
16335 Some(buffer.text_for_range(selection.range()).collect::<String>());
16336 }
16337
16338 if let Some(next_selection) = selections_iter.peek() {
16339 if next_selection.len() == selection.len() {
16340 let next_selected_text = buffer
16341 .text_for_range(next_selection.range())
16342 .collect::<String>();
16343 if Some(next_selected_text) != selected_text {
16344 same_text_selected = false;
16345 selected_text = None;
16346 }
16347 } else {
16348 same_text_selected = false;
16349 selected_text = None;
16350 }
16351 }
16352 }
16353 }
16354
16355 if only_carets {
16356 for selection in &mut selections {
16357 let (word_range, _) = buffer.surrounding_word(selection.start, None);
16358 selection.start = word_range.start;
16359 selection.end = word_range.end;
16360 selection.goal = SelectionGoal::None;
16361 selection.reversed = false;
16362 self.select_match_ranges(
16363 selection.start..selection.end,
16364 selection.reversed,
16365 action.replace_newest,
16366 Some(Autoscroll::newest()),
16367 window,
16368 cx,
16369 );
16370 }
16371 if selections.len() == 1 {
16372 let selection = selections
16373 .last()
16374 .expect("ensured that there's only one selection");
16375 let query = buffer
16376 .text_for_range(selection.start..selection.end)
16377 .collect::<String>();
16378 let is_empty = query.is_empty();
16379 let select_state = SelectNextState {
16380 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
16381 wordwise: true,
16382 done: is_empty,
16383 };
16384 self.select_prev_state = Some(select_state);
16385 } else {
16386 self.select_prev_state = None;
16387 }
16388 } else if let Some(selected_text) = selected_text {
16389 self.select_prev_state = Some(SelectNextState {
16390 query: self
16391 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
16392 wordwise: false,
16393 done: false,
16394 });
16395 self.select_previous(action, window, cx)?;
16396 }
16397 }
16398 Ok(())
16399 }
16400
16401 /// Builds an `AhoCorasick` automaton from the provided patterns, while
16402 /// setting the case sensitivity based on the global
16403 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
16404 /// editor's settings.
16405 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
16406 where
16407 I: IntoIterator<Item = P>,
16408 P: AsRef<[u8]>,
16409 {
16410 let case_sensitive = self
16411 .select_next_is_case_sensitive
16412 .unwrap_or_else(|| EditorSettings::get_global(cx).search.case_sensitive);
16413
16414 let mut builder = AhoCorasickBuilder::new();
16415 builder.ascii_case_insensitive(!case_sensitive);
16416 builder.build(patterns)
16417 }
16418
16419 pub fn find_next_match(
16420 &mut self,
16421 _: &FindNextMatch,
16422 window: &mut Window,
16423 cx: &mut Context<Self>,
16424 ) -> Result<()> {
16425 let selections = self.selections.disjoint_anchors_arc();
16426 match selections.first() {
16427 Some(first) if selections.len() >= 2 => {
16428 self.change_selections(Default::default(), window, cx, |s| {
16429 s.select_ranges([first.range()]);
16430 });
16431 }
16432 _ => self.select_next(
16433 &SelectNext {
16434 replace_newest: true,
16435 },
16436 window,
16437 cx,
16438 )?,
16439 }
16440 Ok(())
16441 }
16442
16443 pub fn find_previous_match(
16444 &mut self,
16445 _: &FindPreviousMatch,
16446 window: &mut Window,
16447 cx: &mut Context<Self>,
16448 ) -> Result<()> {
16449 let selections = self.selections.disjoint_anchors_arc();
16450 match selections.last() {
16451 Some(last) if selections.len() >= 2 => {
16452 self.change_selections(Default::default(), window, cx, |s| {
16453 s.select_ranges([last.range()]);
16454 });
16455 }
16456 _ => self.select_previous(
16457 &SelectPrevious {
16458 replace_newest: true,
16459 },
16460 window,
16461 cx,
16462 )?,
16463 }
16464 Ok(())
16465 }
16466
16467 pub fn toggle_block_comments(
16468 &mut self,
16469 _: &ToggleBlockComments,
16470 window: &mut Window,
16471 cx: &mut Context<Self>,
16472 ) {
16473 if self.read_only(cx) {
16474 return;
16475 }
16476 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16477 self.transact(window, cx, |this, _window, cx| {
16478 let mut selections = this
16479 .selections
16480 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
16481 let mut edits = Vec::new();
16482 let snapshot = this.buffer.read(cx).read(cx);
16483 let empty_str: Arc<str> = Arc::default();
16484 let mut markers_inserted = Vec::new();
16485
16486 for selection in &mut selections {
16487 let start_point = selection.start;
16488 let end_point = selection.end;
16489
16490 let Some(language) =
16491 snapshot.language_scope_at(Point::new(start_point.row, start_point.column))
16492 else {
16493 continue;
16494 };
16495
16496 let Some(BlockCommentConfig {
16497 start: comment_start,
16498 end: comment_end,
16499 ..
16500 }) = language.block_comment()
16501 else {
16502 continue;
16503 };
16504
16505 let prefix_needle = comment_start.trim_end().as_bytes();
16506 let suffix_needle = comment_end.trim_start().as_bytes();
16507
16508 // Collect full lines spanning the selection as the search region
16509 let region_start = Point::new(start_point.row, 0);
16510 let region_end = Point::new(
16511 end_point.row,
16512 snapshot.line_len(MultiBufferRow(end_point.row)),
16513 );
16514 let region_bytes: Vec<u8> = snapshot
16515 .bytes_in_range(region_start..region_end)
16516 .flatten()
16517 .copied()
16518 .collect();
16519
16520 let region_start_offset = snapshot.point_to_offset(region_start);
16521 let start_byte = snapshot.point_to_offset(start_point) - region_start_offset;
16522 let end_byte = snapshot.point_to_offset(end_point) - region_start_offset;
16523
16524 let mut is_commented = false;
16525 let mut prefix_range = start_point..start_point;
16526 let mut suffix_range = end_point..end_point;
16527
16528 // Find rightmost /* at or before the selection end
16529 if let Some(prefix_pos) = region_bytes[..end_byte.min(region_bytes.len())]
16530 .windows(prefix_needle.len())
16531 .rposition(|w| w == prefix_needle)
16532 {
16533 let after_prefix = prefix_pos + prefix_needle.len();
16534
16535 // Find the first */ after that /*
16536 if let Some(suffix_pos) = region_bytes[after_prefix..]
16537 .windows(suffix_needle.len())
16538 .position(|w| w == suffix_needle)
16539 .map(|p| p + after_prefix)
16540 {
16541 let suffix_end = suffix_pos + suffix_needle.len();
16542
16543 // Case 1: /* ... */ surrounds the selection
16544 let markers_surround = prefix_pos <= start_byte
16545 && suffix_end >= end_byte
16546 && start_byte < suffix_end;
16547
16548 // Case 2: selection contains /* ... */ (only whitespace padding)
16549 let selection_contains = start_byte <= prefix_pos
16550 && suffix_end <= end_byte
16551 && region_bytes[start_byte..prefix_pos]
16552 .iter()
16553 .all(|&b| b.is_ascii_whitespace())
16554 && region_bytes[suffix_end..end_byte]
16555 .iter()
16556 .all(|&b| b.is_ascii_whitespace());
16557
16558 if markers_surround || selection_contains {
16559 is_commented = true;
16560 let prefix_pt =
16561 snapshot.offset_to_point(region_start_offset + prefix_pos);
16562 let suffix_pt =
16563 snapshot.offset_to_point(region_start_offset + suffix_pos);
16564 prefix_range = prefix_pt
16565 ..Point::new(
16566 prefix_pt.row,
16567 prefix_pt.column + prefix_needle.len() as u32,
16568 );
16569 suffix_range = suffix_pt
16570 ..Point::new(
16571 suffix_pt.row,
16572 suffix_pt.column + suffix_needle.len() as u32,
16573 );
16574 }
16575 }
16576 }
16577
16578 if is_commented {
16579 // Also remove the space after /* and before */
16580 if snapshot
16581 .bytes_in_range(prefix_range.end..snapshot.max_point())
16582 .flatten()
16583 .next()
16584 == Some(&b' ')
16585 {
16586 prefix_range.end.column += 1;
16587 }
16588 if suffix_range.start.column > 0 {
16589 let before =
16590 Point::new(suffix_range.start.row, suffix_range.start.column - 1);
16591 if snapshot
16592 .bytes_in_range(before..suffix_range.start)
16593 .flatten()
16594 .next()
16595 == Some(&b' ')
16596 {
16597 suffix_range.start.column -= 1;
16598 }
16599 }
16600
16601 edits.push((prefix_range, empty_str.clone()));
16602 edits.push((suffix_range, empty_str.clone()));
16603 } else {
16604 let prefix: Arc<str> = if comment_start.ends_with(' ') {
16605 comment_start.clone()
16606 } else {
16607 format!("{} ", comment_start).into()
16608 };
16609 let suffix: Arc<str> = if comment_end.starts_with(' ') {
16610 comment_end.clone()
16611 } else {
16612 format!(" {}", comment_end).into()
16613 };
16614
16615 edits.push((start_point..start_point, prefix.clone()));
16616 edits.push((end_point..end_point, suffix.clone()));
16617 markers_inserted.push((
16618 selection.id,
16619 prefix.len(),
16620 suffix.len(),
16621 selection.is_empty(),
16622 end_point.row,
16623 ));
16624 }
16625 }
16626
16627 drop(snapshot);
16628 this.buffer.update(cx, |buffer, cx| {
16629 buffer.edit(edits, None, cx);
16630 });
16631
16632 let mut selections = this
16633 .selections
16634 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
16635 for selection in &mut selections {
16636 if let Some((_, prefix_len, suffix_len, was_empty, suffix_row)) = markers_inserted
16637 .iter()
16638 .find(|(id, _, _, _, _)| *id == selection.id)
16639 {
16640 if *was_empty {
16641 selection.start.column = selection
16642 .start
16643 .column
16644 .saturating_sub((*prefix_len + *suffix_len) as u32);
16645 } else {
16646 selection.start.column =
16647 selection.start.column.saturating_sub(*prefix_len as u32);
16648 if selection.end.row == *suffix_row {
16649 selection.end.column += *suffix_len as u32;
16650 }
16651 }
16652 }
16653 }
16654 this.change_selections(Default::default(), _window, cx, |s| s.select(selections));
16655 });
16656 }
16657
16658 pub fn toggle_comments(
16659 &mut self,
16660 action: &ToggleComments,
16661 window: &mut Window,
16662 cx: &mut Context<Self>,
16663 ) {
16664 if self.read_only(cx) {
16665 return;
16666 }
16667 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16668 let text_layout_details = &self.text_layout_details(window, cx);
16669 self.transact(window, cx, |this, window, cx| {
16670 let mut selections = this
16671 .selections
16672 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
16673 let mut edits = Vec::new();
16674 let mut selection_edit_ranges = Vec::new();
16675 let mut last_toggled_row = None;
16676 let snapshot = this.buffer.read(cx).read(cx);
16677 let empty_str: Arc<str> = Arc::default();
16678 let mut suffixes_inserted = Vec::new();
16679 let ignore_indent = action.ignore_indent;
16680
16681 fn comment_prefix_range(
16682 snapshot: &MultiBufferSnapshot,
16683 row: MultiBufferRow,
16684 comment_prefix: &str,
16685 comment_prefix_whitespace: &str,
16686 ignore_indent: bool,
16687 ) -> Range<Point> {
16688 let indent_size = if ignore_indent {
16689 0
16690 } else {
16691 snapshot.indent_size_for_line(row).len
16692 };
16693
16694 let start = Point::new(row.0, indent_size);
16695
16696 let mut line_bytes = snapshot
16697 .bytes_in_range(start..snapshot.max_point())
16698 .flatten()
16699 .copied();
16700
16701 // If this line currently begins with the line comment prefix, then record
16702 // the range containing the prefix.
16703 if line_bytes
16704 .by_ref()
16705 .take(comment_prefix.len())
16706 .eq(comment_prefix.bytes())
16707 {
16708 // Include any whitespace that matches the comment prefix.
16709 let matching_whitespace_len = line_bytes
16710 .zip(comment_prefix_whitespace.bytes())
16711 .take_while(|(a, b)| a == b)
16712 .count() as u32;
16713 let end = Point::new(
16714 start.row,
16715 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
16716 );
16717 start..end
16718 } else {
16719 start..start
16720 }
16721 }
16722
16723 fn comment_suffix_range(
16724 snapshot: &MultiBufferSnapshot,
16725 row: MultiBufferRow,
16726 comment_suffix: &str,
16727 comment_suffix_has_leading_space: bool,
16728 ) -> Range<Point> {
16729 let end = Point::new(row.0, snapshot.line_len(row));
16730 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
16731
16732 let mut line_end_bytes = snapshot
16733 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
16734 .flatten()
16735 .copied();
16736
16737 let leading_space_len = if suffix_start_column > 0
16738 && line_end_bytes.next() == Some(b' ')
16739 && comment_suffix_has_leading_space
16740 {
16741 1
16742 } else {
16743 0
16744 };
16745
16746 // If this line currently begins with the line comment prefix, then record
16747 // the range containing the prefix.
16748 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
16749 let start = Point::new(end.row, suffix_start_column - leading_space_len);
16750 start..end
16751 } else {
16752 end..end
16753 }
16754 }
16755
16756 // TODO: Handle selections that cross excerpts
16757 for selection in &mut selections {
16758 let start_column = snapshot
16759 .indent_size_for_line(MultiBufferRow(selection.start.row))
16760 .len;
16761 let language = if let Some(language) =
16762 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
16763 {
16764 language
16765 } else {
16766 continue;
16767 };
16768
16769 selection_edit_ranges.clear();
16770
16771 // If multiple selections contain a given row, avoid processing that
16772 // row more than once.
16773 let mut start_row = MultiBufferRow(selection.start.row);
16774 if last_toggled_row == Some(start_row) {
16775 start_row = start_row.next_row();
16776 }
16777 let end_row =
16778 if selection.end.row > selection.start.row && selection.end.column == 0 {
16779 MultiBufferRow(selection.end.row - 1)
16780 } else {
16781 MultiBufferRow(selection.end.row)
16782 };
16783 last_toggled_row = Some(end_row);
16784
16785 if start_row > end_row {
16786 continue;
16787 }
16788
16789 // If the language has line comments, toggle those.
16790 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
16791
16792 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
16793 if ignore_indent {
16794 full_comment_prefixes = full_comment_prefixes
16795 .into_iter()
16796 .map(|s| Arc::from(s.trim_end()))
16797 .collect();
16798 }
16799
16800 if !full_comment_prefixes.is_empty() {
16801 let first_prefix = full_comment_prefixes
16802 .first()
16803 .expect("prefixes is non-empty");
16804 let prefix_trimmed_lengths = full_comment_prefixes
16805 .iter()
16806 .map(|p| p.trim_end_matches(' ').len())
16807 .collect::<SmallVec<[usize; 4]>>();
16808
16809 let mut all_selection_lines_are_comments = true;
16810
16811 for row in start_row.0..=end_row.0 {
16812 let row = MultiBufferRow(row);
16813 if start_row < end_row && snapshot.is_line_blank(row) {
16814 continue;
16815 }
16816
16817 let prefix_range = full_comment_prefixes
16818 .iter()
16819 .zip(prefix_trimmed_lengths.iter().copied())
16820 .map(|(prefix, trimmed_prefix_len)| {
16821 comment_prefix_range(
16822 snapshot.deref(),
16823 row,
16824 &prefix[..trimmed_prefix_len],
16825 &prefix[trimmed_prefix_len..],
16826 ignore_indent,
16827 )
16828 })
16829 .max_by_key(|range| range.end.column - range.start.column)
16830 .expect("prefixes is non-empty");
16831
16832 if prefix_range.is_empty() {
16833 all_selection_lines_are_comments = false;
16834 }
16835
16836 selection_edit_ranges.push(prefix_range);
16837 }
16838
16839 if all_selection_lines_are_comments {
16840 edits.extend(
16841 selection_edit_ranges
16842 .iter()
16843 .cloned()
16844 .map(|range| (range, empty_str.clone())),
16845 );
16846 } else {
16847 let min_column = selection_edit_ranges
16848 .iter()
16849 .map(|range| range.start.column)
16850 .min()
16851 .unwrap_or(0);
16852 edits.extend(selection_edit_ranges.iter().map(|range| {
16853 let position = Point::new(range.start.row, min_column);
16854 (position..position, first_prefix.clone())
16855 }));
16856 }
16857 } else if let Some(BlockCommentConfig {
16858 start: full_comment_prefix,
16859 end: comment_suffix,
16860 ..
16861 }) = language.block_comment()
16862 {
16863 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
16864 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
16865 let prefix_range = comment_prefix_range(
16866 snapshot.deref(),
16867 start_row,
16868 comment_prefix,
16869 comment_prefix_whitespace,
16870 ignore_indent,
16871 );
16872 let suffix_range = comment_suffix_range(
16873 snapshot.deref(),
16874 end_row,
16875 comment_suffix.trim_start_matches(' '),
16876 comment_suffix.starts_with(' '),
16877 );
16878
16879 if prefix_range.is_empty() || suffix_range.is_empty() {
16880 edits.push((
16881 prefix_range.start..prefix_range.start,
16882 full_comment_prefix.clone(),
16883 ));
16884 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
16885 suffixes_inserted.push((end_row, comment_suffix.len()));
16886 } else {
16887 edits.push((prefix_range, empty_str.clone()));
16888 edits.push((suffix_range, empty_str.clone()));
16889 }
16890 } else {
16891 continue;
16892 }
16893 }
16894
16895 drop(snapshot);
16896 this.buffer.update(cx, |buffer, cx| {
16897 buffer.edit(edits, None, cx);
16898 });
16899
16900 // Adjust selections so that they end before any comment suffixes that
16901 // were inserted.
16902 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
16903 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16904 let snapshot = this.buffer.read(cx).read(cx);
16905 for selection in &mut selections {
16906 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
16907 match row.cmp(&MultiBufferRow(selection.end.row)) {
16908 Ordering::Less => {
16909 suffixes_inserted.next();
16910 continue;
16911 }
16912 Ordering::Greater => break,
16913 Ordering::Equal => {
16914 if selection.end.column == snapshot.line_len(row) {
16915 if selection.is_empty() {
16916 selection.start.column -= suffix_len as u32;
16917 }
16918 selection.end.column -= suffix_len as u32;
16919 }
16920 break;
16921 }
16922 }
16923 }
16924 }
16925
16926 drop(snapshot);
16927 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
16928
16929 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16930 let selections_on_single_row = selections.windows(2).all(|selections| {
16931 selections[0].start.row == selections[1].start.row
16932 && selections[0].end.row == selections[1].end.row
16933 && selections[0].start.row == selections[0].end.row
16934 });
16935 let selections_selecting = selections
16936 .iter()
16937 .any(|selection| selection.start != selection.end);
16938 let advance_downwards = action.advance_downwards
16939 && selections_on_single_row
16940 && !selections_selecting
16941 && !matches!(this.mode, EditorMode::SingleLine);
16942
16943 if advance_downwards {
16944 let snapshot = this.buffer.read(cx).snapshot(cx);
16945
16946 this.change_selections(Default::default(), window, cx, |s| {
16947 s.move_cursors_with(&mut |display_snapshot, display_point, _| {
16948 let mut point = display_point.to_point(display_snapshot);
16949 point.row += 1;
16950 point = snapshot.clip_point(point, Bias::Left);
16951 let display_point = point.to_display_point(display_snapshot);
16952 let goal = SelectionGoal::HorizontalPosition(
16953 display_snapshot
16954 .x_for_display_point(display_point, text_layout_details)
16955 .into(),
16956 );
16957 (display_point, goal)
16958 })
16959 });
16960 }
16961 });
16962 }
16963
16964 pub fn select_enclosing_symbol(
16965 &mut self,
16966 _: &SelectEnclosingSymbol,
16967 window: &mut Window,
16968 cx: &mut Context<Self>,
16969 ) {
16970 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16971
16972 let buffer = self.buffer.read(cx).snapshot(cx);
16973 let old_selections = self
16974 .selections
16975 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16976 .into_boxed_slice();
16977
16978 fn update_selection(
16979 selection: &Selection<MultiBufferOffset>,
16980 buffer_snap: &MultiBufferSnapshot,
16981 ) -> Option<Selection<MultiBufferOffset>> {
16982 let cursor = selection.head();
16983 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
16984 for symbol in symbols.iter().rev() {
16985 let start = symbol.range.start.to_offset(buffer_snap);
16986 let end = symbol.range.end.to_offset(buffer_snap);
16987 let new_range = start..end;
16988 if start < selection.start || end > selection.end {
16989 return Some(Selection {
16990 id: selection.id,
16991 start: new_range.start,
16992 end: new_range.end,
16993 goal: SelectionGoal::None,
16994 reversed: selection.reversed,
16995 });
16996 }
16997 }
16998 None
16999 }
17000
17001 let mut selected_larger_symbol = false;
17002 let new_selections = old_selections
17003 .iter()
17004 .map(|selection| match update_selection(selection, &buffer) {
17005 Some(new_selection) => {
17006 if new_selection.range() != selection.range() {
17007 selected_larger_symbol = true;
17008 }
17009 new_selection
17010 }
17011 None => selection.clone(),
17012 })
17013 .collect::<Vec<_>>();
17014
17015 if selected_larger_symbol {
17016 self.change_selections(Default::default(), window, cx, |s| {
17017 s.select(new_selections);
17018 });
17019 }
17020 }
17021
17022 pub fn select_larger_syntax_node(
17023 &mut self,
17024 _: &SelectLargerSyntaxNode,
17025 window: &mut Window,
17026 cx: &mut Context<Self>,
17027 ) {
17028 let Some(visible_row_count) = self.visible_row_count() else {
17029 return;
17030 };
17031 let old_selections: Box<[_]> = self
17032 .selections
17033 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
17034 .into();
17035 if old_selections.is_empty() {
17036 return;
17037 }
17038
17039 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17040
17041 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17042 let buffer = self.buffer.read(cx).snapshot(cx);
17043
17044 let mut selected_larger_node = false;
17045 let mut new_selections = old_selections
17046 .iter()
17047 .map(|selection| {
17048 let old_range = selection.start..selection.end;
17049
17050 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
17051 // manually select word at selection
17052 if ["string_content", "inline"].contains(&node.kind()) {
17053 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
17054 // ignore if word is already selected
17055 if !word_range.is_empty() && old_range != word_range {
17056 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
17057 // only select word if start and end point belongs to same word
17058 if word_range == last_word_range {
17059 selected_larger_node = true;
17060 return Selection {
17061 id: selection.id,
17062 start: word_range.start,
17063 end: word_range.end,
17064 goal: SelectionGoal::None,
17065 reversed: selection.reversed,
17066 };
17067 }
17068 }
17069 }
17070 }
17071
17072 let mut new_range = old_range.clone();
17073 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
17074 new_range = range;
17075 if !node.is_named() {
17076 continue;
17077 }
17078 if !display_map.intersects_fold(new_range.start)
17079 && !display_map.intersects_fold(new_range.end)
17080 {
17081 break;
17082 }
17083 }
17084
17085 selected_larger_node |= new_range != old_range;
17086 Selection {
17087 id: selection.id,
17088 start: new_range.start,
17089 end: new_range.end,
17090 goal: SelectionGoal::None,
17091 reversed: selection.reversed,
17092 }
17093 })
17094 .collect::<Vec<_>>();
17095
17096 if !selected_larger_node {
17097 return; // don't put this call in the history
17098 }
17099
17100 // scroll based on transformation done to the last selection created by the user
17101 let (last_old, last_new) = old_selections
17102 .last()
17103 .zip(new_selections.last().cloned())
17104 .expect("old_selections isn't empty");
17105
17106 let is_selection_reversed = if new_selections.len() == 1 {
17107 let should_be_reversed = last_old.start != last_new.start;
17108 new_selections.last_mut().expect("checked above").reversed = should_be_reversed;
17109 should_be_reversed
17110 } else {
17111 last_new.reversed
17112 };
17113
17114 if selected_larger_node {
17115 self.select_syntax_node_history.disable_clearing = true;
17116 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17117 s.select(new_selections.clone());
17118 });
17119 self.select_syntax_node_history.disable_clearing = false;
17120 }
17121
17122 let start_row = last_new.start.to_display_point(&display_map).row().0;
17123 let end_row = last_new.end.to_display_point(&display_map).row().0;
17124 let selection_height = end_row - start_row + 1;
17125 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
17126
17127 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
17128 let scroll_behavior = if fits_on_the_screen {
17129 self.request_autoscroll(Autoscroll::fit(), cx);
17130 SelectSyntaxNodeScrollBehavior::FitSelection
17131 } else if is_selection_reversed {
17132 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
17133 SelectSyntaxNodeScrollBehavior::CursorTop
17134 } else {
17135 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
17136 SelectSyntaxNodeScrollBehavior::CursorBottom
17137 };
17138
17139 let old_selections: Box<[Selection<Anchor>]> = old_selections
17140 .iter()
17141 .map(|s| s.map(|offset| buffer.anchor_before(offset)))
17142 .collect();
17143 self.select_syntax_node_history.push((
17144 old_selections,
17145 scroll_behavior,
17146 is_selection_reversed,
17147 ));
17148 }
17149
17150 pub fn select_smaller_syntax_node(
17151 &mut self,
17152 _: &SelectSmallerSyntaxNode,
17153 window: &mut Window,
17154 cx: &mut Context<Self>,
17155 ) {
17156 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17157
17158 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
17159 self.select_syntax_node_history.pop()
17160 {
17161 if let Some(selection) = selections.last_mut() {
17162 selection.reversed = is_selection_reversed;
17163 }
17164
17165 let snapshot = self.buffer.read(cx).snapshot(cx);
17166 let selections: Vec<Selection<MultiBufferOffset>> = selections
17167 .iter()
17168 .map(|s| s.map(|anchor| anchor.to_offset(&snapshot)))
17169 .collect();
17170
17171 self.select_syntax_node_history.disable_clearing = true;
17172 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17173 s.select(selections);
17174 });
17175 self.select_syntax_node_history.disable_clearing = false;
17176
17177 match scroll_behavior {
17178 SelectSyntaxNodeScrollBehavior::CursorTop => {
17179 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
17180 }
17181 SelectSyntaxNodeScrollBehavior::FitSelection => {
17182 self.request_autoscroll(Autoscroll::fit(), cx);
17183 }
17184 SelectSyntaxNodeScrollBehavior::CursorBottom => {
17185 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
17186 }
17187 }
17188 }
17189 }
17190
17191 pub fn unwrap_syntax_node(
17192 &mut self,
17193 _: &UnwrapSyntaxNode,
17194 window: &mut Window,
17195 cx: &mut Context<Self>,
17196 ) {
17197 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17198
17199 let buffer = self.buffer.read(cx).snapshot(cx);
17200 let selections = self
17201 .selections
17202 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
17203 .into_iter()
17204 // subtracting the offset requires sorting
17205 .sorted_by_key(|i| i.start);
17206
17207 let full_edits = selections
17208 .into_iter()
17209 .filter_map(|selection| {
17210 let child = if selection.is_empty()
17211 && let Some((_, ancestor_range)) =
17212 buffer.syntax_ancestor(selection.start..selection.end)
17213 {
17214 ancestor_range
17215 } else {
17216 selection.range()
17217 };
17218
17219 let mut parent = child.clone();
17220 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
17221 parent = ancestor_range;
17222 if parent.start < child.start || parent.end > child.end {
17223 break;
17224 }
17225 }
17226
17227 if parent == child {
17228 return None;
17229 }
17230 let text = buffer.text_for_range(child).collect::<String>();
17231 Some((selection.id, parent, text))
17232 })
17233 .collect::<Vec<_>>();
17234 if full_edits.is_empty() {
17235 return;
17236 }
17237
17238 self.transact(window, cx, |this, window, cx| {
17239 this.buffer.update(cx, |buffer, cx| {
17240 buffer.edit(
17241 full_edits
17242 .iter()
17243 .map(|(_, p, t)| (p.clone(), t.clone()))
17244 .collect::<Vec<_>>(),
17245 None,
17246 cx,
17247 );
17248 });
17249 this.change_selections(Default::default(), window, cx, |s| {
17250 let mut offset = 0;
17251 let mut selections = vec![];
17252 for (id, parent, text) in full_edits {
17253 let start = parent.start - offset;
17254 offset += (parent.end - parent.start) - text.len();
17255 selections.push(Selection {
17256 id,
17257 start,
17258 end: start + text.len(),
17259 reversed: false,
17260 goal: Default::default(),
17261 });
17262 }
17263 s.select(selections);
17264 });
17265 });
17266 }
17267
17268 pub fn select_next_syntax_node(
17269 &mut self,
17270 _: &SelectNextSyntaxNode,
17271 window: &mut Window,
17272 cx: &mut Context<Self>,
17273 ) {
17274 let old_selections = self.selections.all_anchors(&self.display_snapshot(cx));
17275 if old_selections.is_empty() {
17276 return;
17277 }
17278
17279 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17280
17281 let buffer = self.buffer.read(cx).snapshot(cx);
17282 let mut selected_sibling = false;
17283
17284 let new_selections = old_selections
17285 .iter()
17286 .map(|selection| {
17287 let old_range =
17288 selection.start.to_offset(&buffer)..selection.end.to_offset(&buffer);
17289 if let Some(results) = buffer.map_excerpt_ranges(
17290 old_range,
17291 |buf, _excerpt_range, input_buffer_range| {
17292 let Some(node) = buf.syntax_next_sibling(input_buffer_range) else {
17293 return Vec::new();
17294 };
17295 vec![(
17296 BufferOffset(node.byte_range().start)
17297 ..BufferOffset(node.byte_range().end),
17298 (),
17299 )]
17300 },
17301 ) && let [(new_range, _)] = results.as_slice()
17302 {
17303 selected_sibling = true;
17304 let new_range =
17305 buffer.anchor_after(new_range.start)..buffer.anchor_before(new_range.end);
17306 Selection {
17307 id: selection.id,
17308 start: new_range.start,
17309 end: new_range.end,
17310 goal: SelectionGoal::None,
17311 reversed: selection.reversed,
17312 }
17313 } else {
17314 selection.clone()
17315 }
17316 })
17317 .collect::<Vec<_>>();
17318
17319 if selected_sibling {
17320 self.change_selections(
17321 SelectionEffects::scroll(Autoscroll::fit()),
17322 window,
17323 cx,
17324 |s| {
17325 s.select(new_selections);
17326 },
17327 );
17328 }
17329 }
17330
17331 pub fn select_prev_syntax_node(
17332 &mut self,
17333 _: &SelectPreviousSyntaxNode,
17334 window: &mut Window,
17335 cx: &mut Context<Self>,
17336 ) {
17337 let old_selections: Arc<[_]> = self.selections.all_anchors(&self.display_snapshot(cx));
17338
17339 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17340
17341 let multibuffer_snapshot = self.buffer.read(cx).snapshot(cx);
17342 let mut selected_sibling = false;
17343
17344 let new_selections = old_selections
17345 .iter()
17346 .map(|selection| {
17347 let old_range = selection.start.to_offset(&multibuffer_snapshot)
17348 ..selection.end.to_offset(&multibuffer_snapshot);
17349 if let Some(results) = multibuffer_snapshot.map_excerpt_ranges(
17350 old_range,
17351 |buf, _excerpt_range, input_buffer_range| {
17352 let Some(node) = buf.syntax_prev_sibling(input_buffer_range) else {
17353 return Vec::new();
17354 };
17355 vec![(
17356 BufferOffset(node.byte_range().start)
17357 ..BufferOffset(node.byte_range().end),
17358 (),
17359 )]
17360 },
17361 ) && let [(new_range, _)] = results.as_slice()
17362 {
17363 selected_sibling = true;
17364 let new_range = multibuffer_snapshot.anchor_after(new_range.start)
17365 ..multibuffer_snapshot.anchor_before(new_range.end);
17366 Selection {
17367 id: selection.id,
17368 start: new_range.start,
17369 end: new_range.end,
17370 goal: SelectionGoal::None,
17371 reversed: selection.reversed,
17372 }
17373 } else {
17374 selection.clone()
17375 }
17376 })
17377 .collect::<Vec<_>>();
17378
17379 if selected_sibling {
17380 self.change_selections(
17381 SelectionEffects::scroll(Autoscroll::fit()),
17382 window,
17383 cx,
17384 |s| {
17385 s.select(new_selections);
17386 },
17387 );
17388 }
17389 }
17390
17391 pub fn move_to_start_of_larger_syntax_node(
17392 &mut self,
17393 _: &MoveToStartOfLargerSyntaxNode,
17394 window: &mut Window,
17395 cx: &mut Context<Self>,
17396 ) {
17397 self.move_cursors_to_syntax_nodes(window, cx, false);
17398 }
17399
17400 pub fn move_to_end_of_larger_syntax_node(
17401 &mut self,
17402 _: &MoveToEndOfLargerSyntaxNode,
17403 window: &mut Window,
17404 cx: &mut Context<Self>,
17405 ) {
17406 self.move_cursors_to_syntax_nodes(window, cx, true);
17407 }
17408
17409 fn find_syntax_node_boundary(
17410 &self,
17411 selection_pos: MultiBufferOffset,
17412 move_to_end: bool,
17413 display_map: &DisplaySnapshot,
17414 buffer: &MultiBufferSnapshot,
17415 ) -> MultiBufferOffset {
17416 let old_range = selection_pos..selection_pos;
17417 let mut new_pos = selection_pos;
17418 let mut search_range = old_range;
17419 while let Some((node, range)) = buffer.syntax_ancestor(search_range.clone()) {
17420 search_range = range.clone();
17421 if !node.is_named()
17422 || display_map.intersects_fold(range.start)
17423 || display_map.intersects_fold(range.end)
17424 // If cursor is already at the end of the syntax node, continue searching
17425 || (move_to_end && range.end == selection_pos)
17426 // If cursor is already at the start of the syntax node, continue searching
17427 || (!move_to_end && range.start == selection_pos)
17428 {
17429 continue;
17430 }
17431
17432 // If we found a string_content node, find the largest parent that is still string_content
17433 // Enables us to skip to the end of strings without taking multiple steps inside the string
17434 let (_, final_range) = if node.kind() == "string_content" {
17435 let mut current_node = node;
17436 let mut current_range = range;
17437 while let Some((parent, parent_range)) =
17438 buffer.syntax_ancestor(current_range.clone())
17439 {
17440 if parent.kind() == "string_content" {
17441 current_node = parent;
17442 current_range = parent_range;
17443 } else {
17444 break;
17445 }
17446 }
17447
17448 (current_node, current_range)
17449 } else {
17450 (node, range)
17451 };
17452
17453 new_pos = if move_to_end {
17454 final_range.end
17455 } else {
17456 final_range.start
17457 };
17458
17459 break;
17460 }
17461
17462 new_pos
17463 }
17464
17465 fn move_cursors_to_syntax_nodes(
17466 &mut self,
17467 window: &mut Window,
17468 cx: &mut Context<Self>,
17469 move_to_end: bool,
17470 ) -> bool {
17471 let old_selections: Box<[_]> = self
17472 .selections
17473 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
17474 .into();
17475 if old_selections.is_empty() {
17476 return false;
17477 }
17478
17479 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17480
17481 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17482 let buffer = self.buffer.read(cx).snapshot(cx);
17483
17484 let mut any_cursor_moved = false;
17485 let new_selections = old_selections
17486 .iter()
17487 .map(|selection| {
17488 if !selection.is_empty() {
17489 return selection.clone();
17490 }
17491
17492 let selection_pos = selection.head();
17493 let new_pos = self.find_syntax_node_boundary(
17494 selection_pos,
17495 move_to_end,
17496 &display_map,
17497 &buffer,
17498 );
17499
17500 any_cursor_moved |= new_pos != selection_pos;
17501
17502 Selection {
17503 id: selection.id,
17504 start: new_pos,
17505 end: new_pos,
17506 goal: SelectionGoal::None,
17507 reversed: false,
17508 }
17509 })
17510 .collect::<Vec<_>>();
17511
17512 self.change_selections(Default::default(), window, cx, |s| {
17513 s.select(new_selections);
17514 });
17515 self.request_autoscroll(Autoscroll::newest(), cx);
17516
17517 any_cursor_moved
17518 }
17519
17520 pub fn select_to_start_of_larger_syntax_node(
17521 &mut self,
17522 _: &SelectToStartOfLargerSyntaxNode,
17523 window: &mut Window,
17524 cx: &mut Context<Self>,
17525 ) {
17526 self.select_to_syntax_nodes(window, cx, false);
17527 }
17528
17529 pub fn select_to_end_of_larger_syntax_node(
17530 &mut self,
17531 _: &SelectToEndOfLargerSyntaxNode,
17532 window: &mut Window,
17533 cx: &mut Context<Self>,
17534 ) {
17535 self.select_to_syntax_nodes(window, cx, true);
17536 }
17537
17538 fn select_to_syntax_nodes(
17539 &mut self,
17540 window: &mut Window,
17541 cx: &mut Context<Self>,
17542 move_to_end: bool,
17543 ) {
17544 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17545
17546 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17547 let buffer = self.buffer.read(cx).snapshot(cx);
17548 let old_selections = self.selections.all::<MultiBufferOffset>(&display_map);
17549
17550 let new_selections = old_selections
17551 .iter()
17552 .map(|selection| {
17553 let new_pos = self.find_syntax_node_boundary(
17554 selection.head(),
17555 move_to_end,
17556 &display_map,
17557 &buffer,
17558 );
17559
17560 let mut new_selection = selection.clone();
17561 new_selection.set_head(new_pos, SelectionGoal::None);
17562 new_selection
17563 })
17564 .collect::<Vec<_>>();
17565
17566 self.change_selections(Default::default(), window, cx, |s| {
17567 s.select(new_selections);
17568 });
17569 }
17570
17571 pub fn move_to_enclosing_bracket(
17572 &mut self,
17573 _: &MoveToEnclosingBracket,
17574 window: &mut Window,
17575 cx: &mut Context<Self>,
17576 ) {
17577 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17578 self.change_selections(Default::default(), window, cx, |s| {
17579 s.move_offsets_with(&mut |snapshot, selection| {
17580 let Some(enclosing_bracket_ranges) =
17581 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
17582 else {
17583 return;
17584 };
17585
17586 let mut best_length = usize::MAX;
17587 let mut best_inside = false;
17588 let mut best_in_bracket_range = false;
17589 let mut best_destination = None;
17590 for (open, close) in enclosing_bracket_ranges {
17591 let close = close.to_inclusive();
17592 let length = *close.end() - open.start;
17593 let inside = selection.start >= open.end && selection.end <= *close.start();
17594 let in_bracket_range = open.to_inclusive().contains(&selection.head())
17595 || close.contains(&selection.head());
17596
17597 // If best is next to a bracket and current isn't, skip
17598 if !in_bracket_range && best_in_bracket_range {
17599 continue;
17600 }
17601
17602 // Prefer smaller lengths unless best is inside and current isn't
17603 if length > best_length && (best_inside || !inside) {
17604 continue;
17605 }
17606
17607 best_length = length;
17608 best_inside = inside;
17609 best_in_bracket_range = in_bracket_range;
17610 best_destination = Some(
17611 if close.contains(&selection.start) && close.contains(&selection.end) {
17612 if inside { open.end } else { open.start }
17613 } else if inside {
17614 *close.start()
17615 } else {
17616 *close.end()
17617 },
17618 );
17619 }
17620
17621 if let Some(destination) = best_destination {
17622 selection.collapse_to(destination, SelectionGoal::None);
17623 }
17624 })
17625 });
17626 }
17627
17628 pub fn undo_selection(
17629 &mut self,
17630 _: &UndoSelection,
17631 window: &mut Window,
17632 cx: &mut Context<Self>,
17633 ) {
17634 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17635 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
17636 self.selection_history.mode = SelectionHistoryMode::Undoing;
17637 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17638 this.end_selection(window, cx);
17639 this.change_selections(
17640 SelectionEffects::scroll(Autoscroll::newest()),
17641 window,
17642 cx,
17643 |s| s.select_anchors(entry.selections.to_vec()),
17644 );
17645 });
17646 self.selection_history.mode = SelectionHistoryMode::Normal;
17647
17648 self.select_next_state = entry.select_next_state;
17649 self.select_prev_state = entry.select_prev_state;
17650 self.add_selections_state = entry.add_selections_state;
17651 }
17652 }
17653
17654 pub fn redo_selection(
17655 &mut self,
17656 _: &RedoSelection,
17657 window: &mut Window,
17658 cx: &mut Context<Self>,
17659 ) {
17660 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17661 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
17662 self.selection_history.mode = SelectionHistoryMode::Redoing;
17663 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17664 this.end_selection(window, cx);
17665 this.change_selections(
17666 SelectionEffects::scroll(Autoscroll::newest()),
17667 window,
17668 cx,
17669 |s| s.select_anchors(entry.selections.to_vec()),
17670 );
17671 });
17672 self.selection_history.mode = SelectionHistoryMode::Normal;
17673
17674 self.select_next_state = entry.select_next_state;
17675 self.select_prev_state = entry.select_prev_state;
17676 self.add_selections_state = entry.add_selections_state;
17677 }
17678 }
17679
17680 pub fn expand_excerpts(
17681 &mut self,
17682 action: &ExpandExcerpts,
17683 _: &mut Window,
17684 cx: &mut Context<Self>,
17685 ) {
17686 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
17687 }
17688
17689 pub fn expand_excerpts_down(
17690 &mut self,
17691 action: &ExpandExcerptsDown,
17692 _: &mut Window,
17693 cx: &mut Context<Self>,
17694 ) {
17695 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
17696 }
17697
17698 pub fn expand_excerpts_up(
17699 &mut self,
17700 action: &ExpandExcerptsUp,
17701 _: &mut Window,
17702 cx: &mut Context<Self>,
17703 ) {
17704 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
17705 }
17706
17707 pub fn expand_excerpts_for_direction(
17708 &mut self,
17709 lines: u32,
17710 direction: ExpandExcerptDirection,
17711 cx: &mut Context<Self>,
17712 ) {
17713 let selections = self.selections.disjoint_anchors_arc();
17714
17715 let lines = if lines == 0 {
17716 EditorSettings::get_global(cx).expand_excerpt_lines
17717 } else {
17718 lines
17719 };
17720
17721 let snapshot = self.buffer.read(cx).snapshot(cx);
17722 let excerpt_anchors = selections
17723 .iter()
17724 .flat_map(|selection| {
17725 snapshot
17726 .range_to_buffer_ranges(selection.range())
17727 .into_iter()
17728 .filter_map(|(buffer_snapshot, range, _)| {
17729 snapshot.anchor_in_excerpt(buffer_snapshot.anchor_after(range.start))
17730 })
17731 })
17732 .collect::<Vec<_>>();
17733
17734 if self.delegate_expand_excerpts {
17735 cx.emit(EditorEvent::ExpandExcerptsRequested {
17736 excerpt_anchors,
17737 lines,
17738 direction,
17739 });
17740 return;
17741 }
17742
17743 self.buffer.update(cx, |buffer, cx| {
17744 buffer.expand_excerpts(excerpt_anchors, lines, direction, cx)
17745 })
17746 }
17747
17748 pub(crate) fn expand_excerpt(
17749 &mut self,
17750 excerpt_anchor: Anchor,
17751 direction: ExpandExcerptDirection,
17752 window: &mut Window,
17753 cx: &mut Context<Self>,
17754 ) {
17755 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
17756
17757 if self.delegate_expand_excerpts {
17758 cx.emit(EditorEvent::ExpandExcerptsRequested {
17759 excerpt_anchors: vec![excerpt_anchor],
17760 lines: lines_to_expand,
17761 direction,
17762 });
17763 return;
17764 }
17765
17766 let current_scroll_position = self.scroll_position(cx);
17767 let mut scroll = None;
17768
17769 if direction == ExpandExcerptDirection::Down {
17770 let multi_buffer = self.buffer.read(cx);
17771 let snapshot = multi_buffer.snapshot(cx);
17772 if let Some((buffer_snapshot, excerpt_range)) =
17773 snapshot.excerpt_containing(excerpt_anchor..excerpt_anchor)
17774 {
17775 let excerpt_end_row =
17776 Point::from_anchor(&excerpt_range.context.end, &buffer_snapshot).row;
17777 let last_row = buffer_snapshot.max_point().row;
17778 let lines_below = last_row.saturating_sub(excerpt_end_row);
17779 if lines_below >= lines_to_expand {
17780 scroll = Some(
17781 current_scroll_position
17782 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
17783 );
17784 }
17785 }
17786 }
17787 if direction == ExpandExcerptDirection::Up
17788 && self
17789 .buffer
17790 .read(cx)
17791 .snapshot(cx)
17792 .excerpt_before(excerpt_anchor)
17793 .is_none()
17794 {
17795 scroll = Some(current_scroll_position);
17796 }
17797
17798 self.buffer.update(cx, |buffer, cx| {
17799 buffer.expand_excerpts([excerpt_anchor], lines_to_expand, direction, cx)
17800 });
17801
17802 if let Some(new_scroll_position) = scroll {
17803 self.set_scroll_position(new_scroll_position, window, cx);
17804 }
17805 }
17806
17807 pub fn go_to_singleton_buffer_point(
17808 &mut self,
17809 point: Point,
17810 window: &mut Window,
17811 cx: &mut Context<Self>,
17812 ) {
17813 self.go_to_singleton_buffer_range(point..point, window, cx);
17814 }
17815
17816 pub fn go_to_singleton_buffer_range(
17817 &mut self,
17818 range: Range<Point>,
17819 window: &mut Window,
17820 cx: &mut Context<Self>,
17821 ) {
17822 let multibuffer = self.buffer().read(cx);
17823 if !multibuffer.is_singleton() {
17824 return;
17825 };
17826 let anchor_range = range.to_anchors(&multibuffer.snapshot(cx));
17827 self.change_selections(
17828 SelectionEffects::default().nav_history(true),
17829 window,
17830 cx,
17831 |s| s.select_anchor_ranges([anchor_range]),
17832 );
17833 }
17834
17835 pub fn go_to_diagnostic(
17836 &mut self,
17837 action: &GoToDiagnostic,
17838 window: &mut Window,
17839 cx: &mut Context<Self>,
17840 ) {
17841 if !self.diagnostics_enabled() {
17842 return;
17843 }
17844 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17845 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
17846 }
17847
17848 pub fn go_to_prev_diagnostic(
17849 &mut self,
17850 action: &GoToPreviousDiagnostic,
17851 window: &mut Window,
17852 cx: &mut Context<Self>,
17853 ) {
17854 if !self.diagnostics_enabled() {
17855 return;
17856 }
17857 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17858 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
17859 }
17860
17861 pub fn go_to_diagnostic_impl(
17862 &mut self,
17863 direction: Direction,
17864 severity: GoToDiagnosticSeverityFilter,
17865 window: &mut Window,
17866 cx: &mut Context<Self>,
17867 ) {
17868 let buffer = self.buffer.read(cx).snapshot(cx);
17869 let selection = self
17870 .selections
17871 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
17872
17873 let mut active_group_id = None;
17874 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
17875 && active_group.active_range.start.to_offset(&buffer) == selection.start
17876 {
17877 active_group_id = Some(active_group.group_id);
17878 }
17879
17880 fn filtered<'a>(
17881 severity: GoToDiagnosticSeverityFilter,
17882 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
17883 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
17884 diagnostics
17885 .filter(move |entry| severity.matches(entry.diagnostic.severity))
17886 .filter(|entry| entry.range.start != entry.range.end)
17887 .filter(|entry| !entry.diagnostic.is_unnecessary)
17888 }
17889
17890 let before = filtered(
17891 severity,
17892 buffer
17893 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
17894 .filter(|entry| entry.range.start <= selection.start),
17895 );
17896 let after = filtered(
17897 severity,
17898 buffer
17899 .diagnostics_in_range(selection.start..buffer.len())
17900 .filter(|entry| entry.range.start >= selection.start),
17901 );
17902
17903 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
17904 if direction == Direction::Prev {
17905 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
17906 {
17907 for diagnostic in prev_diagnostics.into_iter().rev() {
17908 if diagnostic.range.start != selection.start
17909 || active_group_id
17910 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
17911 {
17912 found = Some(diagnostic);
17913 break 'outer;
17914 }
17915 }
17916 }
17917 } else {
17918 for diagnostic in after.chain(before) {
17919 if diagnostic.range.start != selection.start
17920 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
17921 {
17922 found = Some(diagnostic);
17923 break;
17924 }
17925 }
17926 }
17927 let Some(next_diagnostic) = found else {
17928 return;
17929 };
17930
17931 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
17932 let Some((buffer_anchor, _)) = buffer.anchor_to_buffer_anchor(next_diagnostic_start) else {
17933 return;
17934 };
17935 let buffer_id = buffer_anchor.buffer_id;
17936 let snapshot = self.snapshot(window, cx);
17937 if snapshot.intersects_fold(next_diagnostic.range.start) {
17938 self.unfold_ranges(
17939 std::slice::from_ref(&next_diagnostic.range),
17940 true,
17941 false,
17942 cx,
17943 );
17944 }
17945 self.change_selections(Default::default(), window, cx, |s| {
17946 s.select_ranges(vec![
17947 next_diagnostic.range.start..next_diagnostic.range.start,
17948 ])
17949 });
17950 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
17951 self.refresh_edit_prediction(false, true, window, cx);
17952 }
17953
17954 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
17955 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17956 let snapshot = self.snapshot(window, cx);
17957 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
17958 self.go_to_hunk_before_or_after_position(
17959 &snapshot,
17960 selection.head(),
17961 Direction::Next,
17962 true,
17963 window,
17964 cx,
17965 );
17966 }
17967
17968 pub fn go_to_hunk_before_or_after_position(
17969 &mut self,
17970 snapshot: &EditorSnapshot,
17971 position: Point,
17972 direction: Direction,
17973 wrap_around: bool,
17974 window: &mut Window,
17975 cx: &mut Context<Editor>,
17976 ) {
17977 let row = if direction == Direction::Next {
17978 self.hunk_after_position(snapshot, position, wrap_around)
17979 .map(|hunk| hunk.row_range.start)
17980 } else {
17981 self.hunk_before_position(snapshot, position, wrap_around)
17982 };
17983
17984 if let Some(row) = row {
17985 let destination = Point::new(row.0, 0);
17986 let autoscroll = Autoscroll::center();
17987
17988 self.unfold_ranges(&[destination..destination], false, false, cx);
17989 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17990 s.select_ranges([destination..destination]);
17991 });
17992 }
17993 }
17994
17995 fn hunk_after_position(
17996 &mut self,
17997 snapshot: &EditorSnapshot,
17998 position: Point,
17999 wrap_around: bool,
18000 ) -> Option<MultiBufferDiffHunk> {
18001 let result = snapshot
18002 .buffer_snapshot()
18003 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
18004 .find(|hunk| hunk.row_range.start.0 > position.row);
18005
18006 if wrap_around {
18007 result.or_else(|| {
18008 snapshot
18009 .buffer_snapshot()
18010 .diff_hunks_in_range(Point::zero()..position)
18011 .find(|hunk| hunk.row_range.end.0 < position.row)
18012 })
18013 } else {
18014 result
18015 }
18016 }
18017
18018 fn go_to_prev_hunk(
18019 &mut self,
18020 _: &GoToPreviousHunk,
18021 window: &mut Window,
18022 cx: &mut Context<Self>,
18023 ) {
18024 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
18025 let snapshot = self.snapshot(window, cx);
18026 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
18027 self.go_to_hunk_before_or_after_position(
18028 &snapshot,
18029 selection.head(),
18030 Direction::Prev,
18031 true,
18032 window,
18033 cx,
18034 );
18035 }
18036
18037 fn hunk_before_position(
18038 &mut self,
18039 snapshot: &EditorSnapshot,
18040 position: Point,
18041 wrap_around: bool,
18042 ) -> Option<MultiBufferRow> {
18043 let result = snapshot.buffer_snapshot().diff_hunk_before(position);
18044
18045 if wrap_around {
18046 result.or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
18047 } else {
18048 result
18049 }
18050 }
18051
18052 fn go_to_next_change(
18053 &mut self,
18054 _: &GoToNextChange,
18055 window: &mut Window,
18056 cx: &mut Context<Self>,
18057 ) {
18058 if let Some(selections) = self
18059 .change_list
18060 .next_change(1, Direction::Next)
18061 .map(|s| s.to_vec())
18062 {
18063 self.change_selections(Default::default(), window, cx, |s| {
18064 let map = s.display_snapshot();
18065 s.select_display_ranges(selections.iter().map(|a| {
18066 let point = a.to_display_point(&map);
18067 point..point
18068 }))
18069 })
18070 }
18071 }
18072
18073 fn go_to_previous_change(
18074 &mut self,
18075 _: &GoToPreviousChange,
18076 window: &mut Window,
18077 cx: &mut Context<Self>,
18078 ) {
18079 if let Some(selections) = self
18080 .change_list
18081 .next_change(1, Direction::Prev)
18082 .map(|s| s.to_vec())
18083 {
18084 self.change_selections(Default::default(), window, cx, |s| {
18085 let map = s.display_snapshot();
18086 s.select_display_ranges(selections.iter().map(|a| {
18087 let point = a.to_display_point(&map);
18088 point..point
18089 }))
18090 })
18091 }
18092 }
18093
18094 pub fn go_to_next_document_highlight(
18095 &mut self,
18096 _: &GoToNextDocumentHighlight,
18097 window: &mut Window,
18098 cx: &mut Context<Self>,
18099 ) {
18100 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
18101 }
18102
18103 pub fn go_to_prev_document_highlight(
18104 &mut self,
18105 _: &GoToPreviousDocumentHighlight,
18106 window: &mut Window,
18107 cx: &mut Context<Self>,
18108 ) {
18109 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
18110 }
18111
18112 pub fn go_to_document_highlight_before_or_after_position(
18113 &mut self,
18114 direction: Direction,
18115 window: &mut Window,
18116 cx: &mut Context<Editor>,
18117 ) {
18118 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
18119 let snapshot = self.snapshot(window, cx);
18120 let buffer = &snapshot.buffer_snapshot();
18121 let position = self
18122 .selections
18123 .newest::<Point>(&snapshot.display_snapshot)
18124 .head();
18125 let anchor_position = buffer.anchor_after(position);
18126
18127 // Get all document highlights (both read and write)
18128 let mut all_highlights = Vec::new();
18129
18130 if let Some((_, read_highlights)) = self
18131 .background_highlights
18132 .get(&HighlightKey::DocumentHighlightRead)
18133 {
18134 all_highlights.extend(read_highlights.iter());
18135 }
18136
18137 if let Some((_, write_highlights)) = self
18138 .background_highlights
18139 .get(&HighlightKey::DocumentHighlightWrite)
18140 {
18141 all_highlights.extend(write_highlights.iter());
18142 }
18143
18144 if all_highlights.is_empty() {
18145 return;
18146 }
18147
18148 // Sort highlights by position
18149 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
18150
18151 let target_highlight = match direction {
18152 Direction::Next => {
18153 // Find the first highlight after the current position
18154 all_highlights
18155 .iter()
18156 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
18157 }
18158 Direction::Prev => {
18159 // Find the last highlight before the current position
18160 all_highlights
18161 .iter()
18162 .rev()
18163 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
18164 }
18165 };
18166
18167 if let Some(highlight) = target_highlight {
18168 let destination = highlight.start.to_point(buffer);
18169 let autoscroll = Autoscroll::center();
18170
18171 self.unfold_ranges(&[destination..destination], false, false, cx);
18172 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18173 s.select_ranges([destination..destination]);
18174 });
18175 }
18176 }
18177
18178 fn go_to_line<T: 'static>(
18179 &mut self,
18180 position: Anchor,
18181 highlight_color: Option<Hsla>,
18182 window: &mut Window,
18183 cx: &mut Context<Self>,
18184 ) {
18185 let snapshot = self.snapshot(window, cx).display_snapshot;
18186 let position = position.to_point(&snapshot.buffer_snapshot());
18187 let start = snapshot
18188 .buffer_snapshot()
18189 .clip_point(Point::new(position.row, 0), Bias::Left);
18190 let end = start + Point::new(1, 0);
18191 let start = snapshot.buffer_snapshot().anchor_before(start);
18192 let end = snapshot.buffer_snapshot().anchor_before(end);
18193
18194 self.highlight_rows::<T>(
18195 start..end,
18196 highlight_color
18197 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
18198 Default::default(),
18199 cx,
18200 );
18201
18202 if self.buffer.read(cx).is_singleton() {
18203 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
18204 }
18205 }
18206
18207 pub fn go_to_definition(
18208 &mut self,
18209 _: &GoToDefinition,
18210 window: &mut Window,
18211 cx: &mut Context<Self>,
18212 ) -> Task<Result<Navigated>> {
18213 let definition =
18214 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
18215 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
18216 cx.spawn_in(window, async move |editor, cx| {
18217 if definition.await? == Navigated::Yes {
18218 return Ok(Navigated::Yes);
18219 }
18220 match fallback_strategy {
18221 GoToDefinitionFallback::None => Ok(Navigated::No),
18222 GoToDefinitionFallback::FindAllReferences => {
18223 match editor.update_in(cx, |editor, window, cx| {
18224 editor.find_all_references(&FindAllReferences::default(), window, cx)
18225 })? {
18226 Some(references) => references.await,
18227 None => Ok(Navigated::No),
18228 }
18229 }
18230 }
18231 })
18232 }
18233
18234 pub fn go_to_declaration(
18235 &mut self,
18236 _: &GoToDeclaration,
18237 window: &mut Window,
18238 cx: &mut Context<Self>,
18239 ) -> Task<Result<Navigated>> {
18240 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
18241 }
18242
18243 pub fn go_to_declaration_split(
18244 &mut self,
18245 _: &GoToDeclaration,
18246 window: &mut Window,
18247 cx: &mut Context<Self>,
18248 ) -> Task<Result<Navigated>> {
18249 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
18250 }
18251
18252 pub fn go_to_implementation(
18253 &mut self,
18254 _: &GoToImplementation,
18255 window: &mut Window,
18256 cx: &mut Context<Self>,
18257 ) -> Task<Result<Navigated>> {
18258 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
18259 }
18260
18261 pub fn go_to_implementation_split(
18262 &mut self,
18263 _: &GoToImplementationSplit,
18264 window: &mut Window,
18265 cx: &mut Context<Self>,
18266 ) -> Task<Result<Navigated>> {
18267 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
18268 }
18269
18270 pub fn go_to_type_definition(
18271 &mut self,
18272 _: &GoToTypeDefinition,
18273 window: &mut Window,
18274 cx: &mut Context<Self>,
18275 ) -> Task<Result<Navigated>> {
18276 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
18277 }
18278
18279 pub fn go_to_definition_split(
18280 &mut self,
18281 _: &GoToDefinitionSplit,
18282 window: &mut Window,
18283 cx: &mut Context<Self>,
18284 ) -> Task<Result<Navigated>> {
18285 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
18286 }
18287
18288 pub fn go_to_type_definition_split(
18289 &mut self,
18290 _: &GoToTypeDefinitionSplit,
18291 window: &mut Window,
18292 cx: &mut Context<Self>,
18293 ) -> Task<Result<Navigated>> {
18294 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
18295 }
18296
18297 fn go_to_definition_of_kind(
18298 &mut self,
18299 kind: GotoDefinitionKind,
18300 split: bool,
18301 window: &mut Window,
18302 cx: &mut Context<Self>,
18303 ) -> Task<Result<Navigated>> {
18304 let Some(provider) = self.semantics_provider.clone() else {
18305 return Task::ready(Ok(Navigated::No));
18306 };
18307 let head = self
18308 .selections
18309 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
18310 .head();
18311 let buffer = self.buffer.read(cx);
18312 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
18313 return Task::ready(Ok(Navigated::No));
18314 };
18315 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
18316 return Task::ready(Ok(Navigated::No));
18317 };
18318
18319 let nav_entry = self.navigation_entry(self.selections.newest_anchor().head(), cx);
18320
18321 cx.spawn_in(window, async move |editor, cx| {
18322 let Some(definitions) = definitions.await? else {
18323 return Ok(Navigated::No);
18324 };
18325 let navigated = editor
18326 .update_in(cx, |editor, window, cx| {
18327 editor.navigate_to_hover_links(
18328 Some(kind),
18329 definitions
18330 .into_iter()
18331 .filter(|location| {
18332 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
18333 })
18334 .map(HoverLink::Text)
18335 .collect::<Vec<_>>(),
18336 nav_entry,
18337 split,
18338 window,
18339 cx,
18340 )
18341 })?
18342 .await?;
18343 anyhow::Ok(navigated)
18344 })
18345 }
18346
18347 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
18348 let selection = self.selections.newest_anchor();
18349 let head = selection.head();
18350 let tail = selection.tail();
18351
18352 let Some((buffer, start_position)) =
18353 self.buffer.read(cx).text_anchor_for_position(head, cx)
18354 else {
18355 return;
18356 };
18357
18358 let end_position = if head != tail {
18359 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
18360 return;
18361 };
18362 Some(pos)
18363 } else {
18364 None
18365 };
18366
18367 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
18368 let url = if let Some(end_pos) = end_position {
18369 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
18370 } else {
18371 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
18372 };
18373
18374 if let Some(url) = url {
18375 cx.update(|window, cx| {
18376 if parse_zed_link(&url, cx).is_some() {
18377 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
18378 } else {
18379 cx.open_url(&url);
18380 }
18381 })?;
18382 }
18383
18384 anyhow::Ok(())
18385 });
18386
18387 url_finder.detach();
18388 }
18389
18390 pub fn open_selected_filename(
18391 &mut self,
18392 _: &OpenSelectedFilename,
18393 window: &mut Window,
18394 cx: &mut Context<Self>,
18395 ) {
18396 let Some(workspace) = self.workspace() else {
18397 return;
18398 };
18399
18400 let position = self.selections.newest_anchor().head();
18401
18402 let Some((buffer, buffer_position)) =
18403 self.buffer.read(cx).text_anchor_for_position(position, cx)
18404 else {
18405 return;
18406 };
18407
18408 let project = self.project.clone();
18409
18410 cx.spawn_in(window, async move |_, cx| {
18411 let result = find_file(&buffer, project, buffer_position, cx).await;
18412
18413 if let Some((_, path)) = result {
18414 workspace
18415 .update_in(cx, |workspace, window, cx| {
18416 workspace.open_resolved_path(path, window, cx)
18417 })?
18418 .await?;
18419 }
18420 anyhow::Ok(())
18421 })
18422 .detach();
18423 }
18424
18425 pub(crate) fn navigate_to_hover_links(
18426 &mut self,
18427 kind: Option<GotoDefinitionKind>,
18428 definitions: Vec<HoverLink>,
18429 origin: Option<NavigationEntry>,
18430 split: bool,
18431 window: &mut Window,
18432 cx: &mut Context<Editor>,
18433 ) -> Task<Result<Navigated>> {
18434 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
18435 let mut first_url_or_file = None;
18436 let definitions: Vec<_> = definitions
18437 .into_iter()
18438 .filter_map(|def| match def {
18439 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
18440 HoverLink::InlayHint(lsp_location, server_id) => {
18441 let computation =
18442 self.compute_target_location(lsp_location, server_id, window, cx);
18443 Some(cx.background_spawn(computation))
18444 }
18445 HoverLink::Url(url) => {
18446 first_url_or_file = Some(Either::Left(url));
18447 None
18448 }
18449 HoverLink::File(path) => {
18450 first_url_or_file = Some(Either::Right(path));
18451 None
18452 }
18453 })
18454 .collect();
18455
18456 let workspace = self.workspace();
18457
18458 let excerpt_context_lines = multi_buffer::excerpt_context_lines(cx);
18459 cx.spawn_in(window, async move |editor, cx| {
18460 let locations: Vec<Location> = future::join_all(definitions)
18461 .await
18462 .into_iter()
18463 .filter_map(|location| location.transpose())
18464 .collect::<Result<_>>()
18465 .context("location tasks")?;
18466 let mut locations = cx.update(|_, cx| {
18467 locations
18468 .into_iter()
18469 .map(|location| {
18470 let buffer = location.buffer.read(cx);
18471 (location.buffer, location.range.to_point(buffer))
18472 })
18473 .into_group_map()
18474 })?;
18475 let mut num_locations = 0;
18476 for ranges in locations.values_mut() {
18477 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18478 ranges.dedup();
18479 // Merge overlapping or contained ranges. After sorting by
18480 // (start, Reverse(end)), we can merge in a single pass:
18481 // if the next range starts before the current one ends,
18482 // extend the current range's end if needed.
18483 let mut i = 0;
18484 while i + 1 < ranges.len() {
18485 if ranges[i + 1].start <= ranges[i].end {
18486 let merged_end = ranges[i].end.max(ranges[i + 1].end);
18487 ranges[i].end = merged_end;
18488 ranges.remove(i + 1);
18489 } else {
18490 i += 1;
18491 }
18492 }
18493 let fits_in_one_excerpt = ranges
18494 .iter()
18495 .tuple_windows()
18496 .all(|(a, b)| b.start.row - a.end.row <= 2 * excerpt_context_lines);
18497 num_locations += if fits_in_one_excerpt { 1 } else { ranges.len() };
18498 }
18499
18500 if num_locations > 1 {
18501 let tab_kind = match kind {
18502 Some(GotoDefinitionKind::Implementation) => "Implementations",
18503 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
18504 Some(GotoDefinitionKind::Declaration) => "Declarations",
18505 Some(GotoDefinitionKind::Type) => "Types",
18506 };
18507 let title = editor
18508 .update_in(cx, |_, _, cx| {
18509 let target = locations
18510 .iter()
18511 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18512 .map(|(buffer, location)| {
18513 buffer
18514 .read(cx)
18515 .text_for_range(location.clone())
18516 .collect::<String>()
18517 })
18518 .filter(|text| !text.contains('\n'))
18519 .unique()
18520 .take(3)
18521 .join(", ");
18522 if target.is_empty() {
18523 tab_kind.to_owned()
18524 } else {
18525 format!("{tab_kind} for {target}")
18526 }
18527 })
18528 .context("buffer title")?;
18529
18530 let Some(workspace) = workspace else {
18531 return Ok(Navigated::No);
18532 };
18533
18534 let opened = workspace
18535 .update_in(cx, |workspace, window, cx| {
18536 let allow_preview = PreviewTabsSettings::get_global(cx)
18537 .enable_preview_multibuffer_from_code_navigation;
18538 if let Some((target_editor, target_pane)) =
18539 Self::open_locations_in_multibuffer(
18540 workspace,
18541 locations,
18542 title,
18543 split,
18544 allow_preview,
18545 MultibufferSelectionMode::First,
18546 window,
18547 cx,
18548 )
18549 {
18550 // We create our own nav history instead of using
18551 // `target_editor.nav_history` because `nav_history`
18552 // seems to be populated asynchronously when an item
18553 // is added to a pane
18554 let mut nav_history = target_pane
18555 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18556 target_editor.update(cx, |editor, cx| {
18557 let nav_data = editor
18558 .navigation_data(editor.selections.newest_anchor().head(), cx);
18559 let target =
18560 Some(nav_history.navigation_entry(Some(
18561 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18562 )));
18563 nav_history.push_tag(origin, target);
18564 })
18565 }
18566 })
18567 .is_ok();
18568
18569 anyhow::Ok(Navigated::from_bool(opened))
18570 } else if num_locations == 0 {
18571 // If there is one url or file, open it directly
18572 match first_url_or_file {
18573 Some(Either::Left(url)) => {
18574 cx.update(|window, cx| {
18575 if parse_zed_link(&url, cx).is_some() {
18576 window
18577 .dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
18578 } else {
18579 cx.open_url(&url);
18580 }
18581 })?;
18582 Ok(Navigated::Yes)
18583 }
18584 Some(Either::Right(path)) => {
18585 // TODO(andrew): respect preview tab settings
18586 // `enable_keep_preview_on_code_navigation` and
18587 // `enable_preview_file_from_code_navigation`
18588 let Some(workspace) = workspace else {
18589 return Ok(Navigated::No);
18590 };
18591 workspace
18592 .update_in(cx, |workspace, window, cx| {
18593 workspace.open_resolved_path(path, window, cx)
18594 })?
18595 .await?;
18596 Ok(Navigated::Yes)
18597 }
18598 None => Ok(Navigated::No),
18599 }
18600 } else {
18601 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18602
18603 editor.update_in(cx, |editor, window, cx| {
18604 let target_ranges = target_ranges
18605 .into_iter()
18606 .map(|r| editor.range_for_match(&r))
18607 .map(collapse_multiline_range)
18608 .collect::<Vec<_>>();
18609 if !split
18610 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
18611 {
18612 let multibuffer = editor.buffer.read(cx);
18613 let target_ranges = target_ranges
18614 .into_iter()
18615 .filter_map(|r| {
18616 let start = multibuffer.buffer_point_to_anchor(
18617 &target_buffer,
18618 r.start,
18619 cx,
18620 )?;
18621 let end = multibuffer.buffer_point_to_anchor(
18622 &target_buffer,
18623 r.end,
18624 cx,
18625 )?;
18626 Some(start..end)
18627 })
18628 .collect::<Vec<_>>();
18629 if target_ranges.is_empty() {
18630 return Navigated::No;
18631 }
18632
18633 editor.change_selections(
18634 SelectionEffects::default().nav_history(true),
18635 window,
18636 cx,
18637 |s| s.select_anchor_ranges(target_ranges),
18638 );
18639
18640 let target =
18641 editor.navigation_entry(editor.selections.newest_anchor().head(), cx);
18642 if let Some(mut nav_history) = editor.nav_history.clone() {
18643 nav_history.push_tag(origin, target);
18644 }
18645 } else {
18646 let Some(workspace) = workspace else {
18647 return Navigated::No;
18648 };
18649 let pane = workspace.read(cx).active_pane().clone();
18650 window.defer(cx, move |window, cx| {
18651 let (target_editor, target_pane): (Entity<Self>, Entity<Pane>) =
18652 workspace.update(cx, |workspace, cx| {
18653 let pane = if split {
18654 workspace.adjacent_pane(window, cx)
18655 } else {
18656 workspace.active_pane().clone()
18657 };
18658
18659 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18660 let keep_old_preview = preview_tabs_settings
18661 .enable_keep_preview_on_code_navigation;
18662 let allow_new_preview = preview_tabs_settings
18663 .enable_preview_file_from_code_navigation;
18664
18665 let editor = workspace.open_project_item(
18666 pane.clone(),
18667 target_buffer.clone(),
18668 true,
18669 true,
18670 keep_old_preview,
18671 allow_new_preview,
18672 window,
18673 cx,
18674 );
18675 (editor, pane)
18676 });
18677 // We create our own nav history instead of using
18678 // `target_editor.nav_history` because `nav_history`
18679 // seems to be populated asynchronously when an item
18680 // is added to a pane
18681 let mut nav_history = target_pane
18682 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18683 target_editor.update(cx, |target_editor, cx| {
18684 // When selecting a definition in a different buffer, disable the nav history
18685 // to avoid creating a history entry at the previous cursor location.
18686 pane.update(cx, |pane, _| pane.disable_history());
18687
18688 let multibuffer = target_editor.buffer.read(cx);
18689 let Some(target_buffer) = multibuffer.as_singleton() else {
18690 return Navigated::No;
18691 };
18692 let target_ranges = target_ranges
18693 .into_iter()
18694 .filter_map(|r| {
18695 let start = multibuffer.buffer_point_to_anchor(
18696 &target_buffer,
18697 r.start,
18698 cx,
18699 )?;
18700 let end = multibuffer.buffer_point_to_anchor(
18701 &target_buffer,
18702 r.end,
18703 cx,
18704 )?;
18705 Some(start..end)
18706 })
18707 .collect::<Vec<_>>();
18708 if target_ranges.is_empty() {
18709 return Navigated::No;
18710 }
18711
18712 target_editor.change_selections(
18713 SelectionEffects::default().nav_history(true),
18714 window,
18715 cx,
18716 |s| s.select_anchor_ranges(target_ranges),
18717 );
18718
18719 let nav_data = target_editor.navigation_data(
18720 target_editor.selections.newest_anchor().head(),
18721 cx,
18722 );
18723 let target =
18724 Some(nav_history.navigation_entry(Some(
18725 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18726 )));
18727 nav_history.push_tag(origin, target);
18728 pane.update(cx, |pane, _| pane.enable_history());
18729 Navigated::Yes
18730 });
18731 });
18732 }
18733 Navigated::Yes
18734 })
18735 }
18736 })
18737 }
18738
18739 fn compute_target_location(
18740 &self,
18741 lsp_location: lsp::Location,
18742 server_id: LanguageServerId,
18743 window: &mut Window,
18744 cx: &mut Context<Self>,
18745 ) -> Task<anyhow::Result<Option<Location>>> {
18746 let Some(project) = self.project.clone() else {
18747 return Task::ready(Ok(None));
18748 };
18749
18750 cx.spawn_in(window, async move |editor, cx| {
18751 let location_task = editor.update(cx, |_, cx| {
18752 project.update(cx, |project, cx| {
18753 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
18754 })
18755 })?;
18756 let location = Some({
18757 let target_buffer_handle = location_task.await.context("open local buffer")?;
18758 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
18759 let target_start = target_buffer
18760 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
18761 let target_end = target_buffer
18762 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
18763 target_buffer.anchor_after(target_start)
18764 ..target_buffer.anchor_before(target_end)
18765 });
18766 Location {
18767 buffer: target_buffer_handle,
18768 range,
18769 }
18770 });
18771 Ok(location)
18772 })
18773 }
18774
18775 fn go_to_next_reference(
18776 &mut self,
18777 _: &GoToNextReference,
18778 window: &mut Window,
18779 cx: &mut Context<Self>,
18780 ) {
18781 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
18782 if let Some(task) = task {
18783 task.detach();
18784 };
18785 }
18786
18787 fn go_to_prev_reference(
18788 &mut self,
18789 _: &GoToPreviousReference,
18790 window: &mut Window,
18791 cx: &mut Context<Self>,
18792 ) {
18793 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
18794 if let Some(task) = task {
18795 task.detach();
18796 };
18797 }
18798
18799 fn go_to_symbol_by_offset(
18800 &mut self,
18801 window: &mut Window,
18802 cx: &mut Context<Self>,
18803 offset: i8,
18804 ) -> Task<Result<()>> {
18805 let editor_snapshot = self.snapshot(window, cx);
18806
18807 // We don't care about multi-buffer symbols
18808 if !editor_snapshot.is_singleton() {
18809 return Task::ready(Ok(()));
18810 }
18811
18812 let cursor_offset = self
18813 .selections
18814 .newest::<MultiBufferOffset>(&editor_snapshot.display_snapshot)
18815 .head();
18816
18817 cx.spawn_in(window, async move |editor, wcx| -> Result<()> {
18818 let Ok(Some(remote_id)) = editor.update(wcx, |ed, cx| {
18819 let buffer = ed.buffer.read(cx).as_singleton()?;
18820 Some(buffer.read(cx).remote_id())
18821 }) else {
18822 return Ok(());
18823 };
18824
18825 let task = editor.update(wcx, |ed, cx| ed.buffer_outline_items(remote_id, cx))?;
18826 let outline_items: Vec<OutlineItem<text::Anchor>> = task.await;
18827
18828 let multi_snapshot = editor_snapshot.buffer();
18829 let buffer_range = |range: &Range<_>| {
18830 Some(
18831 multi_snapshot
18832 .buffer_anchor_range_to_anchor_range(range.clone())?
18833 .to_offset(multi_snapshot),
18834 )
18835 };
18836
18837 wcx.update_window(wcx.window_handle(), |_, window, acx| {
18838 let current_idx = outline_items
18839 .iter()
18840 .enumerate()
18841 .filter_map(|(idx, item)| {
18842 // Find the closest outline item by distance between outline text and cursor location
18843 let source_range = buffer_range(&item.source_range_for_text)?;
18844 let distance_to_closest_endpoint = cmp::min(
18845 (source_range.start.0 as isize - cursor_offset.0 as isize).abs(),
18846 (source_range.end.0 as isize - cursor_offset.0 as isize).abs(),
18847 );
18848
18849 let item_towards_offset =
18850 (source_range.start.0 as isize - cursor_offset.0 as isize).signum()
18851 == (offset as isize).signum();
18852
18853 let source_range_contains_cursor = source_range.contains(&cursor_offset);
18854
18855 // To pick the next outline to jump to, we should jump in the direction of the offset, and
18856 // we should not already be within the outline's source range. We then pick the closest outline
18857 // item.
18858 (item_towards_offset && !source_range_contains_cursor)
18859 .then_some((distance_to_closest_endpoint, idx))
18860 })
18861 .min()
18862 .map(|(_, idx)| idx);
18863
18864 let Some(idx) = current_idx else {
18865 return;
18866 };
18867
18868 let Some(range) = buffer_range(&outline_items[idx].source_range_for_text) else {
18869 return;
18870 };
18871 let selection = [range.start..range.start];
18872
18873 let _ = editor
18874 .update(acx, |editor, ecx| {
18875 editor.change_selections(
18876 SelectionEffects::scroll(Autoscroll::newest()),
18877 window,
18878 ecx,
18879 |s| s.select_ranges(selection),
18880 );
18881 })
18882 .ok();
18883 })?;
18884
18885 Ok(())
18886 })
18887 }
18888
18889 fn go_to_next_symbol(
18890 &mut self,
18891 _: &GoToNextSymbol,
18892 window: &mut Window,
18893 cx: &mut Context<Self>,
18894 ) {
18895 self.go_to_symbol_by_offset(window, cx, 1).detach();
18896 }
18897
18898 fn go_to_previous_symbol(
18899 &mut self,
18900 _: &GoToPreviousSymbol,
18901 window: &mut Window,
18902 cx: &mut Context<Self>,
18903 ) {
18904 self.go_to_symbol_by_offset(window, cx, -1).detach();
18905 }
18906
18907 pub fn go_to_reference_before_or_after_position(
18908 &mut self,
18909 direction: Direction,
18910 count: usize,
18911 window: &mut Window,
18912 cx: &mut Context<Self>,
18913 ) -> Option<Task<Result<()>>> {
18914 let selection = self.selections.newest_anchor();
18915 let head = selection.head();
18916
18917 let multi_buffer = self.buffer.read(cx);
18918
18919 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
18920 let workspace = self.workspace()?;
18921 let project = workspace.read(cx).project().clone();
18922 let references =
18923 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
18924 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
18925 let Some(locations) = references.await? else {
18926 return Ok(());
18927 };
18928
18929 if locations.is_empty() {
18930 // totally normal - the cursor may be on something which is not
18931 // a symbol (e.g. a keyword)
18932 log::info!("no references found under cursor");
18933 return Ok(());
18934 }
18935
18936 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
18937
18938 let (locations, current_location_index) =
18939 multi_buffer.update(cx, |multi_buffer, cx| {
18940 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18941 let mut locations = locations
18942 .into_iter()
18943 .filter_map(|loc| {
18944 let start = multi_buffer_snapshot.anchor_in_excerpt(loc.range.start)?;
18945 let end = multi_buffer_snapshot.anchor_in_excerpt(loc.range.end)?;
18946 Some(start..end)
18947 })
18948 .collect::<Vec<_>>();
18949 // There is an O(n) implementation, but given this list will be
18950 // small (usually <100 items), the extra O(log(n)) factor isn't
18951 // worth the (surprisingly large amount of) extra complexity.
18952 locations
18953 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
18954
18955 let head_offset = head.to_offset(&multi_buffer_snapshot);
18956
18957 let current_location_index = locations.iter().position(|loc| {
18958 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
18959 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
18960 });
18961
18962 (locations, current_location_index)
18963 });
18964
18965 let Some(current_location_index) = current_location_index else {
18966 // This indicates something has gone wrong, because we already
18967 // handle the "no references" case above
18968 log::error!(
18969 "failed to find current reference under cursor. Total references: {}",
18970 locations.len()
18971 );
18972 return Ok(());
18973 };
18974
18975 let destination_location_index = match direction {
18976 Direction::Next => (current_location_index + count) % locations.len(),
18977 Direction::Prev => {
18978 (current_location_index + locations.len() - count % locations.len())
18979 % locations.len()
18980 }
18981 };
18982
18983 // TODO(cameron): is this needed?
18984 // the thinking is to avoid "jumping to the current location" (avoid
18985 // polluting "jumplist" in vim terms)
18986 if current_location_index == destination_location_index {
18987 return Ok(());
18988 }
18989
18990 let Range { start, end } = locations[destination_location_index];
18991
18992 editor.update_in(cx, |editor, window, cx| {
18993 let effects = SelectionEffects::default();
18994
18995 editor.unfold_ranges(&[start..end], false, false, cx);
18996 editor.change_selections(effects, window, cx, |s| {
18997 s.select_ranges([start..start]);
18998 });
18999 })?;
19000
19001 Ok(())
19002 }))
19003 }
19004
19005 pub fn find_all_references(
19006 &mut self,
19007 action: &FindAllReferences,
19008 window: &mut Window,
19009 cx: &mut Context<Self>,
19010 ) -> Option<Task<Result<Navigated>>> {
19011 let always_open_multibuffer = action.always_open_multibuffer;
19012 let selection = self.selections.newest_anchor();
19013 let multi_buffer = self.buffer.read(cx);
19014 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19015 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
19016 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
19017 let head = selection_offset.head();
19018
19019 let head_anchor = multi_buffer_snapshot.anchor_at(
19020 head,
19021 if head < selection_offset.tail() {
19022 Bias::Right
19023 } else {
19024 Bias::Left
19025 },
19026 );
19027
19028 match self
19029 .find_all_references_task_sources
19030 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
19031 {
19032 Ok(_) => {
19033 log::info!(
19034 "Ignoring repeated FindAllReferences invocation with the position of already running task"
19035 );
19036 return None;
19037 }
19038 Err(i) => {
19039 self.find_all_references_task_sources.insert(i, head_anchor);
19040 }
19041 }
19042
19043 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
19044 let workspace = self.workspace()?;
19045 let project = workspace.read(cx).project().clone();
19046 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
19047 Some(cx.spawn_in(window, async move |editor, cx| {
19048 let _cleanup = cx.on_drop(&editor, move |editor, _| {
19049 if let Ok(i) = editor
19050 .find_all_references_task_sources
19051 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
19052 {
19053 editor.find_all_references_task_sources.remove(i);
19054 }
19055 });
19056
19057 let Some(locations) = references.await? else {
19058 return anyhow::Ok(Navigated::No);
19059 };
19060 let mut locations = cx.update(|_, cx| {
19061 locations
19062 .into_iter()
19063 .map(|location| {
19064 let buffer = location.buffer.read(cx);
19065 (location.buffer, location.range.to_point(buffer))
19066 })
19067 // if special-casing the single-match case, remove ranges
19068 // that intersect current selection
19069 .filter(|(location_buffer, location)| {
19070 if always_open_multibuffer || &buffer != location_buffer {
19071 return true;
19072 }
19073
19074 !location.contains_inclusive(&selection_point.range())
19075 })
19076 .into_group_map()
19077 })?;
19078 if locations.is_empty() {
19079 return anyhow::Ok(Navigated::No);
19080 }
19081 for ranges in locations.values_mut() {
19082 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
19083 ranges.dedup();
19084 }
19085 let mut num_locations = 0;
19086 for ranges in locations.values_mut() {
19087 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
19088 ranges.dedup();
19089 num_locations += ranges.len();
19090 }
19091
19092 if num_locations == 1 && !always_open_multibuffer {
19093 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
19094 let target_range = target_ranges.first().unwrap().clone();
19095
19096 return editor.update_in(cx, |editor, window, cx| {
19097 let range = target_range.to_point(target_buffer.read(cx));
19098 let range = editor.range_for_match(&range);
19099 let range = range.start..range.start;
19100
19101 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
19102 editor.go_to_singleton_buffer_range(range, window, cx);
19103 } else {
19104 let pane = workspace.read(cx).active_pane().clone();
19105 window.defer(cx, move |window, cx| {
19106 let target_editor: Entity<Self> =
19107 workspace.update(cx, |workspace, cx| {
19108 let pane = workspace.active_pane().clone();
19109
19110 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
19111 let keep_old_preview = preview_tabs_settings
19112 .enable_keep_preview_on_code_navigation;
19113 let allow_new_preview = preview_tabs_settings
19114 .enable_preview_file_from_code_navigation;
19115
19116 workspace.open_project_item(
19117 pane,
19118 target_buffer.clone(),
19119 true,
19120 true,
19121 keep_old_preview,
19122 allow_new_preview,
19123 window,
19124 cx,
19125 )
19126 });
19127 target_editor.update(cx, |target_editor, cx| {
19128 // When selecting a definition in a different buffer, disable the nav history
19129 // to avoid creating a history entry at the previous cursor location.
19130 pane.update(cx, |pane, _| pane.disable_history());
19131 target_editor.go_to_singleton_buffer_range(range, window, cx);
19132 pane.update(cx, |pane, _| pane.enable_history());
19133 });
19134 });
19135 }
19136 Navigated::No
19137 });
19138 }
19139
19140 workspace.update_in(cx, |workspace, window, cx| {
19141 let target = locations
19142 .iter()
19143 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
19144 .map(|(buffer, location)| {
19145 buffer
19146 .read(cx)
19147 .text_for_range(location.clone())
19148 .collect::<String>()
19149 })
19150 .filter(|text| !text.contains('\n'))
19151 .unique()
19152 .take(3)
19153 .join(", ");
19154 let title = if target.is_empty() {
19155 "References".to_owned()
19156 } else {
19157 format!("References to {target}")
19158 };
19159 let allow_preview = PreviewTabsSettings::get_global(cx)
19160 .enable_preview_multibuffer_from_code_navigation;
19161 Self::open_locations_in_multibuffer(
19162 workspace,
19163 locations,
19164 title,
19165 false,
19166 allow_preview,
19167 MultibufferSelectionMode::First,
19168 window,
19169 cx,
19170 );
19171 Navigated::Yes
19172 })
19173 }))
19174 }
19175
19176 /// Opens a multibuffer with the given project locations in it.
19177 pub fn open_locations_in_multibuffer(
19178 workspace: &mut Workspace,
19179 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
19180 title: String,
19181 split: bool,
19182 allow_preview: bool,
19183 multibuffer_selection_mode: MultibufferSelectionMode,
19184 window: &mut Window,
19185 cx: &mut Context<Workspace>,
19186 ) -> Option<(Entity<Editor>, Entity<Pane>)> {
19187 if locations.is_empty() {
19188 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
19189 return None;
19190 }
19191
19192 let capability = workspace.project().read(cx).capability();
19193 let mut ranges = <Vec<Range<Anchor>>>::new();
19194
19195 // a key to find existing multibuffer editors with the same set of locations
19196 // to prevent us from opening more and more multibuffer tabs for searches and the like
19197 let mut key = (title.clone(), vec![]);
19198 let excerpt_buffer = cx.new(|cx| {
19199 let key = &mut key.1;
19200 let mut multibuffer = MultiBuffer::new(capability);
19201 for (buffer, mut ranges_for_buffer) in locations {
19202 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
19203 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
19204 multibuffer.set_excerpts_for_path(
19205 PathKey::for_buffer(&buffer, cx),
19206 buffer.clone(),
19207 ranges_for_buffer.clone(),
19208 multibuffer_context_lines(cx),
19209 cx,
19210 );
19211 let snapshot = multibuffer.snapshot(cx);
19212 let buffer_snapshot = buffer.read(cx).snapshot();
19213 ranges.extend(ranges_for_buffer.into_iter().filter_map(|range| {
19214 let text_range = buffer_snapshot.anchor_range_inside(range);
19215 let start = snapshot.anchor_in_buffer(text_range.start)?;
19216 let end = snapshot.anchor_in_buffer(text_range.end)?;
19217 Some(start..end)
19218 }))
19219 }
19220
19221 multibuffer.with_title(title)
19222 });
19223 let existing = workspace.active_pane().update(cx, |pane, cx| {
19224 pane.items()
19225 .filter_map(|item| item.downcast::<Editor>())
19226 .find(|editor| {
19227 editor
19228 .read(cx)
19229 .lookup_key
19230 .as_ref()
19231 .and_then(|it| {
19232 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
19233 })
19234 .is_some_and(|it| *it == key)
19235 })
19236 });
19237 let was_existing = existing.is_some();
19238 let editor = existing.unwrap_or_else(|| {
19239 cx.new(|cx| {
19240 let mut editor = Editor::for_multibuffer(
19241 excerpt_buffer,
19242 Some(workspace.project().clone()),
19243 window,
19244 cx,
19245 );
19246 editor.lookup_key = Some(Box::new(key));
19247 editor
19248 })
19249 });
19250 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
19251 MultibufferSelectionMode::First => {
19252 if let Some(first_range) = ranges.first() {
19253 editor.change_selections(
19254 SelectionEffects::no_scroll(),
19255 window,
19256 cx,
19257 |selections| {
19258 selections.clear_disjoint();
19259 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
19260 },
19261 );
19262 }
19263 editor.highlight_background(
19264 HighlightKey::Editor,
19265 &ranges,
19266 |_, theme| theme.colors().editor_highlighted_line_background,
19267 cx,
19268 );
19269 }
19270 MultibufferSelectionMode::All => {
19271 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
19272 selections.clear_disjoint();
19273 selections.select_anchor_ranges(ranges);
19274 });
19275 }
19276 });
19277
19278 let item = Box::new(editor.clone());
19279
19280 let pane = if split {
19281 workspace.adjacent_pane(window, cx)
19282 } else {
19283 workspace.active_pane().clone()
19284 };
19285 let activate_pane = split;
19286
19287 let mut destination_index = None;
19288 pane.update(cx, |pane, cx| {
19289 if allow_preview && !was_existing {
19290 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
19291 }
19292 if was_existing && !allow_preview {
19293 pane.unpreview_item_if_preview(item.item_id());
19294 }
19295 pane.add_item(item, activate_pane, true, destination_index, window, cx);
19296 });
19297
19298 Some((editor, pane))
19299 }
19300
19301 pub fn rename(
19302 &mut self,
19303 _: &Rename,
19304 window: &mut Window,
19305 cx: &mut Context<Self>,
19306 ) -> Option<Task<Result<()>>> {
19307 use language::ToOffset as _;
19308
19309 let provider = self.semantics_provider.clone()?;
19310 let selection = self.selections.newest_anchor().clone();
19311 let (cursor_buffer, cursor_buffer_position) = self
19312 .buffer
19313 .read(cx)
19314 .text_anchor_for_position(selection.head(), cx)?;
19315 let (tail_buffer, cursor_buffer_position_end) = self
19316 .buffer
19317 .read(cx)
19318 .text_anchor_for_position(selection.tail(), cx)?;
19319 if tail_buffer != cursor_buffer {
19320 return None;
19321 }
19322
19323 let snapshot = cursor_buffer.read(cx).snapshot();
19324 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
19325 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
19326 let prepare_rename = provider.range_for_rename(&cursor_buffer, cursor_buffer_position, cx);
19327 drop(snapshot);
19328
19329 Some(cx.spawn_in(window, async move |this, cx| {
19330 let rename_range = prepare_rename.await?;
19331 if let Some(rename_range) = rename_range {
19332 this.update_in(cx, |this, window, cx| {
19333 let snapshot = cursor_buffer.read(cx).snapshot();
19334 let rename_buffer_range = rename_range.to_offset(&snapshot);
19335 let cursor_offset_in_rename_range =
19336 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
19337 let cursor_offset_in_rename_range_end =
19338 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
19339
19340 this.take_rename(false, window, cx);
19341 let buffer = this.buffer.read(cx).read(cx);
19342 let cursor_offset = selection.head().to_offset(&buffer);
19343 let rename_start =
19344 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
19345 let rename_end = rename_start + rename_buffer_range.len();
19346 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
19347 let mut old_highlight_id = None;
19348 let old_name: Arc<str> = buffer
19349 .chunks(
19350 rename_start..rename_end,
19351 LanguageAwareStyling {
19352 tree_sitter: true,
19353 diagnostics: true,
19354 },
19355 )
19356 .map(|chunk| {
19357 if old_highlight_id.is_none() {
19358 old_highlight_id = chunk.syntax_highlight_id;
19359 }
19360 chunk.text
19361 })
19362 .collect::<String>()
19363 .into();
19364
19365 drop(buffer);
19366
19367 // Position the selection in the rename editor so that it matches the current selection.
19368 this.show_local_selections = false;
19369 let rename_editor = cx.new(|cx| {
19370 let mut editor = Editor::single_line(window, cx);
19371 editor.buffer.update(cx, |buffer, cx| {
19372 buffer.edit(
19373 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
19374 None,
19375 cx,
19376 )
19377 });
19378 let cursor_offset_in_rename_range =
19379 MultiBufferOffset(cursor_offset_in_rename_range);
19380 let cursor_offset_in_rename_range_end =
19381 MultiBufferOffset(cursor_offset_in_rename_range_end);
19382 let rename_selection_range = match cursor_offset_in_rename_range
19383 .cmp(&cursor_offset_in_rename_range_end)
19384 {
19385 Ordering::Equal => {
19386 editor.select_all(&SelectAll, window, cx);
19387 return editor;
19388 }
19389 Ordering::Less => {
19390 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
19391 }
19392 Ordering::Greater => {
19393 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
19394 }
19395 };
19396 if rename_selection_range.end.0 > old_name.len() {
19397 editor.select_all(&SelectAll, window, cx);
19398 } else {
19399 editor.change_selections(Default::default(), window, cx, |s| {
19400 s.select_ranges([rename_selection_range]);
19401 });
19402 }
19403 editor
19404 });
19405 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
19406 if e == &EditorEvent::Focused {
19407 cx.emit(EditorEvent::FocusedIn)
19408 }
19409 })
19410 .detach();
19411
19412 let write_highlights =
19413 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
19414 let read_highlights =
19415 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
19416 let ranges = write_highlights
19417 .iter()
19418 .flat_map(|(_, ranges)| ranges.iter())
19419 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
19420 .cloned()
19421 .collect();
19422
19423 this.highlight_text(
19424 HighlightKey::Rename,
19425 ranges,
19426 HighlightStyle {
19427 fade_out: Some(0.6),
19428 ..Default::default()
19429 },
19430 cx,
19431 );
19432 let rename_focus_handle = rename_editor.focus_handle(cx);
19433 window.focus(&rename_focus_handle, cx);
19434 let block_id = this.insert_blocks(
19435 [BlockProperties {
19436 style: BlockStyle::Flex,
19437 placement: BlockPlacement::Below(range.start),
19438 height: Some(1),
19439 render: Arc::new({
19440 let rename_editor = rename_editor.clone();
19441 move |cx: &mut BlockContext| {
19442 let mut text_style = cx.editor_style.text.clone();
19443 if let Some(highlight_style) = old_highlight_id
19444 .and_then(|h| cx.editor_style.syntax.get(h).cloned())
19445 {
19446 text_style = text_style.highlight(highlight_style);
19447 }
19448 div()
19449 .block_mouse_except_scroll()
19450 .pl(cx.anchor_x)
19451 .child(EditorElement::new(
19452 &rename_editor,
19453 EditorStyle {
19454 background: cx.theme().system().transparent,
19455 local_player: cx.editor_style.local_player,
19456 text: text_style,
19457 scrollbar_width: cx.editor_style.scrollbar_width,
19458 syntax: cx.editor_style.syntax.clone(),
19459 status: cx.editor_style.status.clone(),
19460 inlay_hints_style: HighlightStyle {
19461 font_weight: Some(FontWeight::BOLD),
19462 ..make_inlay_hints_style(cx.app)
19463 },
19464 edit_prediction_styles: make_suggestion_styles(
19465 cx.app,
19466 ),
19467 ..EditorStyle::default()
19468 },
19469 ))
19470 .into_any_element()
19471 }
19472 }),
19473 priority: 0,
19474 }],
19475 Some(Autoscroll::fit()),
19476 cx,
19477 )[0];
19478 this.pending_rename = Some(RenameState {
19479 range,
19480 old_name,
19481 editor: rename_editor,
19482 block_id,
19483 });
19484 })?;
19485 }
19486
19487 Ok(())
19488 }))
19489 }
19490
19491 pub fn confirm_rename(
19492 &mut self,
19493 _: &ConfirmRename,
19494 window: &mut Window,
19495 cx: &mut Context<Self>,
19496 ) -> Option<Task<Result<()>>> {
19497 let rename = self.take_rename(false, window, cx)?;
19498 let workspace = self.workspace()?.downgrade();
19499 let (buffer, start) = self
19500 .buffer
19501 .read(cx)
19502 .text_anchor_for_position(rename.range.start, cx)?;
19503 let (end_buffer, _) = self
19504 .buffer
19505 .read(cx)
19506 .text_anchor_for_position(rename.range.end, cx)?;
19507 if buffer != end_buffer {
19508 return None;
19509 }
19510
19511 let old_name = rename.old_name;
19512 let new_name = rename.editor.read(cx).text(cx);
19513
19514 let rename = self.semantics_provider.as_ref()?.perform_rename(
19515 &buffer,
19516 start,
19517 new_name.clone(),
19518 cx,
19519 )?;
19520
19521 Some(cx.spawn_in(window, async move |editor, cx| {
19522 let project_transaction = rename.await?;
19523 Self::open_project_transaction(
19524 &editor,
19525 workspace,
19526 project_transaction,
19527 format!("Rename: {} → {}", old_name, new_name),
19528 cx,
19529 )
19530 .await?;
19531
19532 editor.update(cx, |editor, cx| {
19533 editor.refresh_document_highlights(cx);
19534 })?;
19535 Ok(())
19536 }))
19537 }
19538
19539 fn take_rename(
19540 &mut self,
19541 moving_cursor: bool,
19542 window: &mut Window,
19543 cx: &mut Context<Self>,
19544 ) -> Option<RenameState> {
19545 let rename = self.pending_rename.take()?;
19546 if rename.editor.focus_handle(cx).is_focused(window) {
19547 window.focus(&self.focus_handle, cx);
19548 }
19549
19550 self.remove_blocks(
19551 [rename.block_id].into_iter().collect(),
19552 Some(Autoscroll::fit()),
19553 cx,
19554 );
19555 self.clear_highlights(HighlightKey::Rename, cx);
19556 self.show_local_selections = true;
19557
19558 if moving_cursor {
19559 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
19560 editor
19561 .selections
19562 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
19563 .head()
19564 });
19565
19566 // Update the selection to match the position of the selection inside
19567 // the rename editor.
19568 let snapshot = self.buffer.read(cx).read(cx);
19569 let rename_range = rename.range.to_offset(&snapshot);
19570 let cursor_in_editor = snapshot
19571 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
19572 .min(rename_range.end);
19573 drop(snapshot);
19574
19575 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19576 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
19577 });
19578 } else {
19579 self.refresh_document_highlights(cx);
19580 }
19581
19582 Some(rename)
19583 }
19584
19585 pub fn pending_rename(&self) -> Option<&RenameState> {
19586 self.pending_rename.as_ref()
19587 }
19588
19589 fn format(
19590 &mut self,
19591 _: &Format,
19592 window: &mut Window,
19593 cx: &mut Context<Self>,
19594 ) -> Option<Task<Result<()>>> {
19595 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19596
19597 let project = match &self.project {
19598 Some(project) => project.clone(),
19599 None => return None,
19600 };
19601
19602 Some(self.perform_format(
19603 project,
19604 FormatTrigger::Manual,
19605 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
19606 window,
19607 cx,
19608 ))
19609 }
19610
19611 fn format_selections(
19612 &mut self,
19613 _: &FormatSelections,
19614 window: &mut Window,
19615 cx: &mut Context<Self>,
19616 ) -> Option<Task<Result<()>>> {
19617 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19618
19619 let project = match &self.project {
19620 Some(project) => project.clone(),
19621 None => return None,
19622 };
19623
19624 let ranges = self
19625 .selections
19626 .all_adjusted(&self.display_snapshot(cx))
19627 .into_iter()
19628 .map(|selection| selection.range())
19629 .collect_vec();
19630
19631 Some(self.perform_format(
19632 project,
19633 FormatTrigger::Manual,
19634 FormatTarget::Ranges(ranges),
19635 window,
19636 cx,
19637 ))
19638 }
19639
19640 fn perform_format(
19641 &mut self,
19642 project: Entity<Project>,
19643 trigger: FormatTrigger,
19644 target: FormatTarget,
19645 window: &mut Window,
19646 cx: &mut Context<Self>,
19647 ) -> Task<Result<()>> {
19648 let buffer = self.buffer.clone();
19649 let (buffers, target) = match target {
19650 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
19651 FormatTarget::Ranges(selection_ranges) => {
19652 let multi_buffer = buffer.read(cx);
19653 let snapshot = multi_buffer.read(cx);
19654 let mut buffers = HashSet::default();
19655 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
19656 BTreeMap::new();
19657 for selection_range in selection_ranges {
19658 for (buffer_snapshot, buffer_range, _) in
19659 snapshot.range_to_buffer_ranges(selection_range.start..selection_range.end)
19660 {
19661 let buffer_id = buffer_snapshot.remote_id();
19662 let start = buffer_snapshot.anchor_before(buffer_range.start);
19663 let end = buffer_snapshot.anchor_after(buffer_range.end);
19664 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
19665 buffer_id_to_ranges
19666 .entry(buffer_id)
19667 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
19668 .or_insert_with(|| vec![start..end]);
19669 }
19670 }
19671 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
19672 }
19673 };
19674
19675 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
19676 let selections_prev = transaction_id_prev
19677 .and_then(|transaction_id_prev| {
19678 // default to selections as they were after the last edit, if we have them,
19679 // instead of how they are now.
19680 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
19681 // will take you back to where you made the last edit, instead of staying where you scrolled
19682 self.selection_history
19683 .transaction(transaction_id_prev)
19684 .map(|t| t.0.clone())
19685 })
19686 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
19687
19688 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
19689 let format = project.update(cx, |project, cx| {
19690 project.format(buffers, target, true, trigger, cx)
19691 });
19692
19693 cx.spawn_in(window, async move |editor, cx| {
19694 let transaction = futures::select_biased! {
19695 transaction = format.log_err().fuse() => transaction,
19696 () = timeout => {
19697 log::warn!("timed out waiting for formatting");
19698 None
19699 }
19700 };
19701
19702 buffer.update(cx, |buffer, cx| {
19703 if let Some(transaction) = transaction
19704 && !buffer.is_singleton()
19705 {
19706 buffer.push_transaction(&transaction.0, cx);
19707 }
19708 cx.notify();
19709 });
19710
19711 if let Some(transaction_id_now) =
19712 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))
19713 {
19714 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
19715 if has_new_transaction {
19716 editor
19717 .update(cx, |editor, _| {
19718 editor
19719 .selection_history
19720 .insert_transaction(transaction_id_now, selections_prev);
19721 })
19722 .ok();
19723 }
19724 }
19725
19726 Ok(())
19727 })
19728 }
19729
19730 fn organize_imports(
19731 &mut self,
19732 _: &OrganizeImports,
19733 window: &mut Window,
19734 cx: &mut Context<Self>,
19735 ) -> Option<Task<Result<()>>> {
19736 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19737 let project = match &self.project {
19738 Some(project) => project.clone(),
19739 None => return None,
19740 };
19741 Some(self.perform_code_action_kind(
19742 project,
19743 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
19744 window,
19745 cx,
19746 ))
19747 }
19748
19749 fn perform_code_action_kind(
19750 &mut self,
19751 project: Entity<Project>,
19752 kind: CodeActionKind,
19753 window: &mut Window,
19754 cx: &mut Context<Self>,
19755 ) -> Task<Result<()>> {
19756 let buffer = self.buffer.clone();
19757 let buffers = buffer.read(cx).all_buffers();
19758 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
19759 let apply_action = project.update(cx, |project, cx| {
19760 project.apply_code_action_kind(buffers, kind, true, cx)
19761 });
19762 cx.spawn_in(window, async move |_, cx| {
19763 let transaction = futures::select_biased! {
19764 () = timeout => {
19765 log::warn!("timed out waiting for executing code action");
19766 None
19767 }
19768 transaction = apply_action.log_err().fuse() => transaction,
19769 };
19770 buffer.update(cx, |buffer, cx| {
19771 // check if we need this
19772 if let Some(transaction) = transaction
19773 && !buffer.is_singleton()
19774 {
19775 buffer.push_transaction(&transaction.0, cx);
19776 }
19777 cx.notify();
19778 });
19779 Ok(())
19780 })
19781 }
19782
19783 pub fn restart_language_server(
19784 &mut self,
19785 _: &RestartLanguageServer,
19786 _: &mut Window,
19787 cx: &mut Context<Self>,
19788 ) {
19789 if let Some(project) = self.project.clone() {
19790 self.buffer.update(cx, |multi_buffer, cx| {
19791 project.update(cx, |project, cx| {
19792 project.restart_language_servers_for_buffers(
19793 multi_buffer.all_buffers().into_iter().collect(),
19794 HashSet::default(),
19795 cx,
19796 );
19797 });
19798 })
19799 }
19800 }
19801
19802 pub fn stop_language_server(
19803 &mut self,
19804 _: &StopLanguageServer,
19805 _: &mut Window,
19806 cx: &mut Context<Self>,
19807 ) {
19808 if let Some(project) = self.project.clone() {
19809 self.buffer.update(cx, |multi_buffer, cx| {
19810 project.update(cx, |project, cx| {
19811 project.stop_language_servers_for_buffers(
19812 multi_buffer.all_buffers().into_iter().collect(),
19813 HashSet::default(),
19814 cx,
19815 );
19816 });
19817 });
19818 }
19819 }
19820
19821 fn cancel_language_server_work(
19822 workspace: &mut Workspace,
19823 _: &actions::CancelLanguageServerWork,
19824 _: &mut Window,
19825 cx: &mut Context<Workspace>,
19826 ) {
19827 let project = workspace.project();
19828 let buffers = workspace
19829 .active_item(cx)
19830 .and_then(|item| item.act_as::<Editor>(cx))
19831 .map_or(HashSet::default(), |editor| {
19832 editor.read(cx).buffer.read(cx).all_buffers()
19833 });
19834 project.update(cx, |project, cx| {
19835 project.cancel_language_server_work_for_buffers(buffers, cx);
19836 });
19837 }
19838
19839 fn show_character_palette(
19840 &mut self,
19841 _: &ShowCharacterPalette,
19842 window: &mut Window,
19843 _: &mut Context<Self>,
19844 ) {
19845 window.show_character_palette();
19846 }
19847
19848 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
19849 if !self.diagnostics_enabled() {
19850 return;
19851 }
19852
19853 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
19854 let buffer = self.buffer.read(cx).snapshot(cx);
19855 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
19856 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
19857 let is_valid = buffer
19858 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
19859 .any(|entry| {
19860 entry.diagnostic.is_primary
19861 && !entry.range.is_empty()
19862 && entry.range.start == primary_range_start
19863 && entry.diagnostic.message == active_diagnostics.active_message
19864 });
19865
19866 if !is_valid {
19867 self.dismiss_diagnostics(cx);
19868 }
19869 }
19870 }
19871
19872 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
19873 match &self.active_diagnostics {
19874 ActiveDiagnostic::Group(group) => Some(group),
19875 _ => None,
19876 }
19877 }
19878
19879 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
19880 if !self.diagnostics_enabled() {
19881 return;
19882 }
19883 self.dismiss_diagnostics(cx);
19884 self.active_diagnostics = ActiveDiagnostic::All;
19885 }
19886
19887 fn activate_diagnostics(
19888 &mut self,
19889 buffer_id: BufferId,
19890 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
19891 window: &mut Window,
19892 cx: &mut Context<Self>,
19893 ) {
19894 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19895 return;
19896 }
19897 self.dismiss_diagnostics(cx);
19898 let snapshot = self.snapshot(window, cx);
19899 let buffer = self.buffer.read(cx).snapshot(cx);
19900 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
19901 return;
19902 };
19903
19904 let diagnostic_group = buffer
19905 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
19906 .collect::<Vec<_>>();
19907
19908 let language_registry = self
19909 .project()
19910 .map(|project| project.read(cx).languages().clone());
19911
19912 let blocks = renderer.render_group(
19913 diagnostic_group,
19914 buffer_id,
19915 snapshot,
19916 cx.weak_entity(),
19917 language_registry,
19918 cx,
19919 );
19920
19921 let blocks = self.display_map.update(cx, |display_map, cx| {
19922 display_map.insert_blocks(blocks, cx).into_iter().collect()
19923 });
19924 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
19925 active_range: buffer.anchor_before(diagnostic.range.start)
19926 ..buffer.anchor_after(diagnostic.range.end),
19927 active_message: diagnostic.diagnostic.message.clone(),
19928 group_id: diagnostic.diagnostic.group_id,
19929 blocks,
19930 });
19931 cx.notify();
19932 }
19933
19934 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
19935 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19936 return;
19937 };
19938
19939 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
19940 if let ActiveDiagnostic::Group(group) = prev {
19941 self.display_map.update(cx, |display_map, cx| {
19942 display_map.remove_blocks(group.blocks, cx);
19943 });
19944 cx.notify();
19945 }
19946 }
19947
19948 /// Disable inline diagnostics rendering for this editor.
19949 pub fn disable_inline_diagnostics(&mut self) {
19950 self.inline_diagnostics_enabled = false;
19951 self.inline_diagnostics_update = Task::ready(());
19952 self.inline_diagnostics.clear();
19953 }
19954
19955 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
19956 self.diagnostics_enabled = false;
19957 self.dismiss_diagnostics(cx);
19958 self.inline_diagnostics_update = Task::ready(());
19959 self.inline_diagnostics.clear();
19960 }
19961
19962 pub fn disable_word_completions(&mut self) {
19963 self.word_completions_enabled = false;
19964 }
19965
19966 pub fn diagnostics_enabled(&self) -> bool {
19967 self.diagnostics_enabled && self.lsp_data_enabled()
19968 }
19969
19970 pub fn inline_diagnostics_enabled(&self) -> bool {
19971 self.inline_diagnostics_enabled && self.diagnostics_enabled()
19972 }
19973
19974 pub fn show_inline_diagnostics(&self) -> bool {
19975 self.show_inline_diagnostics
19976 }
19977
19978 pub fn toggle_inline_diagnostics(
19979 &mut self,
19980 _: &ToggleInlineDiagnostics,
19981 window: &mut Window,
19982 cx: &mut Context<Editor>,
19983 ) {
19984 self.show_inline_diagnostics = !self.show_inline_diagnostics;
19985 self.refresh_inline_diagnostics(false, window, cx);
19986 }
19987
19988 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
19989 self.diagnostics_max_severity = severity;
19990 self.display_map.update(cx, |display_map, _| {
19991 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
19992 });
19993 }
19994
19995 pub fn toggle_diagnostics(
19996 &mut self,
19997 _: &ToggleDiagnostics,
19998 window: &mut Window,
19999 cx: &mut Context<Editor>,
20000 ) {
20001 if !self.diagnostics_enabled() {
20002 return;
20003 }
20004
20005 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
20006 EditorSettings::get_global(cx)
20007 .diagnostics_max_severity
20008 .filter(|severity| severity != &DiagnosticSeverity::Off)
20009 .unwrap_or(DiagnosticSeverity::Hint)
20010 } else {
20011 DiagnosticSeverity::Off
20012 };
20013 self.set_max_diagnostics_severity(new_severity, cx);
20014 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
20015 self.active_diagnostics = ActiveDiagnostic::None;
20016 self.inline_diagnostics_update = Task::ready(());
20017 self.inline_diagnostics.clear();
20018 } else {
20019 self.refresh_inline_diagnostics(false, window, cx);
20020 }
20021
20022 cx.notify();
20023 }
20024
20025 pub fn toggle_minimap(
20026 &mut self,
20027 _: &ToggleMinimap,
20028 window: &mut Window,
20029 cx: &mut Context<Editor>,
20030 ) {
20031 if self.supports_minimap(cx) {
20032 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
20033 }
20034 }
20035
20036 fn refresh_inline_diagnostics(
20037 &mut self,
20038 debounce: bool,
20039 window: &mut Window,
20040 cx: &mut Context<Self>,
20041 ) {
20042 let max_severity = ProjectSettings::get_global(cx)
20043 .diagnostics
20044 .inline
20045 .max_severity
20046 .unwrap_or(self.diagnostics_max_severity);
20047
20048 if !self.inline_diagnostics_enabled()
20049 || !self.diagnostics_enabled()
20050 || !self.show_inline_diagnostics
20051 || max_severity == DiagnosticSeverity::Off
20052 {
20053 self.inline_diagnostics_update = Task::ready(());
20054 self.inline_diagnostics.clear();
20055 return;
20056 }
20057
20058 let debounce_ms = ProjectSettings::get_global(cx)
20059 .diagnostics
20060 .inline
20061 .update_debounce_ms;
20062 let debounce = if debounce && debounce_ms > 0 {
20063 Some(Duration::from_millis(debounce_ms))
20064 } else {
20065 None
20066 };
20067 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
20068 if let Some(debounce) = debounce {
20069 cx.background_executor().timer(debounce).await;
20070 }
20071 let Some(snapshot) = editor.upgrade().map(|editor| {
20072 editor.update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
20073 }) else {
20074 return;
20075 };
20076
20077 let new_inline_diagnostics = cx
20078 .background_spawn(async move {
20079 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
20080 for diagnostic_entry in
20081 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
20082 {
20083 let message = diagnostic_entry
20084 .diagnostic
20085 .message
20086 .split_once('\n')
20087 .map(|(line, _)| line)
20088 .map(SharedString::new)
20089 .unwrap_or_else(|| {
20090 SharedString::new(&*diagnostic_entry.diagnostic.message)
20091 });
20092 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
20093 let (Ok(i) | Err(i)) = inline_diagnostics
20094 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
20095 inline_diagnostics.insert(
20096 i,
20097 (
20098 start_anchor,
20099 InlineDiagnostic {
20100 message,
20101 group_id: diagnostic_entry.diagnostic.group_id,
20102 start: diagnostic_entry.range.start.to_point(&snapshot),
20103 is_primary: diagnostic_entry.diagnostic.is_primary,
20104 severity: diagnostic_entry.diagnostic.severity,
20105 },
20106 ),
20107 );
20108 }
20109 inline_diagnostics
20110 })
20111 .await;
20112
20113 editor
20114 .update(cx, |editor, cx| {
20115 editor.inline_diagnostics = new_inline_diagnostics;
20116 cx.notify();
20117 })
20118 .ok();
20119 });
20120 }
20121
20122 fn pull_diagnostics(
20123 &mut self,
20124 buffer_id: BufferId,
20125 _window: &Window,
20126 cx: &mut Context<Self>,
20127 ) -> Option<()> {
20128 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
20129 // skip any LSP updates for it.
20130
20131 if self.active_diagnostics == ActiveDiagnostic::All || !self.diagnostics_enabled() {
20132 return None;
20133 }
20134 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
20135 .diagnostics
20136 .lsp_pull_diagnostics;
20137 if !pull_diagnostics_settings.enabled {
20138 return None;
20139 }
20140 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
20141 let project = self.project()?.downgrade();
20142 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
20143
20144 self.pull_diagnostics_task = cx.spawn(async move |_, cx| {
20145 cx.background_executor().timer(debounce).await;
20146 if let Ok(task) = project.update(cx, |project, cx| {
20147 project.lsp_store().update(cx, |lsp_store, cx| {
20148 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
20149 })
20150 }) {
20151 task.await.log_err();
20152 }
20153 project
20154 .update(cx, |project, cx| {
20155 project.lsp_store().update(cx, |lsp_store, cx| {
20156 lsp_store.pull_document_diagnostics_for_buffer_edit(buffer_id, cx);
20157 })
20158 })
20159 .log_err();
20160 });
20161
20162 Some(())
20163 }
20164
20165 pub fn set_selections_from_remote(
20166 &mut self,
20167 selections: Vec<Selection<Anchor>>,
20168 pending_selection: Option<Selection<Anchor>>,
20169 window: &mut Window,
20170 cx: &mut Context<Self>,
20171 ) {
20172 let old_cursor_position = self.selections.newest_anchor().head();
20173 self.selections
20174 .change_with(&self.display_snapshot(cx), |s| {
20175 s.select_anchors(selections);
20176 if let Some(pending_selection) = pending_selection {
20177 s.set_pending(pending_selection, SelectMode::Character);
20178 } else {
20179 s.clear_pending();
20180 }
20181 });
20182 self.selections_did_change(
20183 false,
20184 &old_cursor_position,
20185 SelectionEffects::default(),
20186 window,
20187 cx,
20188 );
20189 }
20190
20191 pub fn transact(
20192 &mut self,
20193 window: &mut Window,
20194 cx: &mut Context<Self>,
20195 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
20196 ) -> Option<TransactionId> {
20197 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
20198 this.start_transaction_at(Instant::now(), window, cx);
20199 update(this, window, cx);
20200 this.end_transaction_at(Instant::now(), cx)
20201 })
20202 }
20203
20204 pub fn start_transaction_at(
20205 &mut self,
20206 now: Instant,
20207 window: &mut Window,
20208 cx: &mut Context<Self>,
20209 ) -> Option<TransactionId> {
20210 self.end_selection(window, cx);
20211 if let Some(tx_id) = self
20212 .buffer
20213 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
20214 {
20215 self.selection_history
20216 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
20217 cx.emit(EditorEvent::TransactionBegun {
20218 transaction_id: tx_id,
20219 });
20220 Some(tx_id)
20221 } else {
20222 None
20223 }
20224 }
20225
20226 pub fn end_transaction_at(
20227 &mut self,
20228 now: Instant,
20229 cx: &mut Context<Self>,
20230 ) -> Option<TransactionId> {
20231 if let Some(transaction_id) = self
20232 .buffer
20233 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
20234 {
20235 if let Some((_, end_selections)) =
20236 self.selection_history.transaction_mut(transaction_id)
20237 {
20238 *end_selections = Some(self.selections.disjoint_anchors_arc());
20239 } else {
20240 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
20241 }
20242
20243 cx.emit(EditorEvent::Edited { transaction_id });
20244 Some(transaction_id)
20245 } else {
20246 None
20247 }
20248 }
20249
20250 pub fn modify_transaction_selection_history(
20251 &mut self,
20252 transaction_id: TransactionId,
20253 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
20254 ) -> bool {
20255 self.selection_history
20256 .transaction_mut(transaction_id)
20257 .map(modify)
20258 .is_some()
20259 }
20260
20261 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
20262 if self.selection_mark_mode {
20263 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20264 s.move_with(&mut |_, sel| {
20265 sel.collapse_to(sel.head(), SelectionGoal::None);
20266 });
20267 })
20268 }
20269 self.selection_mark_mode = true;
20270 cx.notify();
20271 }
20272
20273 pub fn swap_selection_ends(
20274 &mut self,
20275 _: &actions::SwapSelectionEnds,
20276 window: &mut Window,
20277 cx: &mut Context<Self>,
20278 ) {
20279 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20280 s.move_with(&mut |_, sel| {
20281 if sel.start != sel.end {
20282 sel.reversed = !sel.reversed
20283 }
20284 });
20285 });
20286 self.request_autoscroll(Autoscroll::newest(), cx);
20287 cx.notify();
20288 }
20289
20290 pub fn toggle_focus(
20291 workspace: &mut Workspace,
20292 _: &actions::ToggleFocus,
20293 window: &mut Window,
20294 cx: &mut Context<Workspace>,
20295 ) {
20296 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
20297 return;
20298 };
20299 workspace.activate_item(&item, true, true, window, cx);
20300 }
20301
20302 pub fn toggle_fold(
20303 &mut self,
20304 _: &actions::ToggleFold,
20305 window: &mut Window,
20306 cx: &mut Context<Self>,
20307 ) {
20308 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
20309 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20310 let selection = self.selections.newest::<Point>(&display_map);
20311
20312 let range = if selection.is_empty() {
20313 let point = selection.head().to_display_point(&display_map);
20314 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
20315 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
20316 .to_point(&display_map);
20317 start..end
20318 } else {
20319 selection.range()
20320 };
20321 if display_map.folds_in_range(range).next().is_some() {
20322 self.unfold_lines(&Default::default(), window, cx)
20323 } else {
20324 self.fold(&Default::default(), window, cx)
20325 }
20326 } else {
20327 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20328 let buffer_ids: HashSet<_> = self
20329 .selections
20330 .disjoint_anchor_ranges()
20331 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20332 .collect();
20333
20334 let should_unfold = buffer_ids
20335 .iter()
20336 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
20337
20338 for buffer_id in buffer_ids {
20339 if should_unfold {
20340 self.unfold_buffer(buffer_id, cx);
20341 } else {
20342 self.fold_buffer(buffer_id, cx);
20343 }
20344 }
20345 }
20346 }
20347
20348 pub fn toggle_fold_recursive(
20349 &mut self,
20350 _: &actions::ToggleFoldRecursive,
20351 window: &mut Window,
20352 cx: &mut Context<Self>,
20353 ) {
20354 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20355
20356 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20357 let range = if selection.is_empty() {
20358 let point = selection.head().to_display_point(&display_map);
20359 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
20360 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
20361 .to_point(&display_map);
20362 start..end
20363 } else {
20364 selection.range()
20365 };
20366 if display_map.folds_in_range(range).next().is_some() {
20367 self.unfold_recursive(&Default::default(), window, cx)
20368 } else {
20369 self.fold_recursive(&Default::default(), window, cx)
20370 }
20371 }
20372
20373 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
20374 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
20375 let mut to_fold = Vec::new();
20376 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20377 let selections = self.selections.all_adjusted(&display_map);
20378
20379 for selection in selections {
20380 let range = selection.range().sorted();
20381 let buffer_start_row = range.start.row;
20382
20383 if range.start.row != range.end.row {
20384 let mut found = false;
20385 let mut row = range.start.row;
20386 while row <= range.end.row {
20387 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
20388 {
20389 found = true;
20390 row = crease.range().end.row + 1;
20391 to_fold.push(crease);
20392 } else {
20393 row += 1
20394 }
20395 }
20396 if found {
20397 continue;
20398 }
20399 }
20400
20401 for row in (0..=range.start.row).rev() {
20402 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
20403 && crease.range().end.row >= buffer_start_row
20404 {
20405 to_fold.push(crease);
20406 if row <= range.start.row {
20407 break;
20408 }
20409 }
20410 }
20411 }
20412
20413 self.fold_creases(to_fold, true, window, cx);
20414 } else {
20415 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20416 let buffer_ids = self
20417 .selections
20418 .disjoint_anchor_ranges()
20419 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20420 .collect::<HashSet<_>>();
20421 for buffer_id in buffer_ids {
20422 self.fold_buffer(buffer_id, cx);
20423 }
20424 }
20425 }
20426
20427 pub fn toggle_fold_all(
20428 &mut self,
20429 _: &actions::ToggleFoldAll,
20430 window: &mut Window,
20431 cx: &mut Context<Self>,
20432 ) {
20433 let has_folds = if self.buffer.read(cx).is_singleton() {
20434 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20435 let has_folds = display_map
20436 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
20437 .next()
20438 .is_some();
20439 has_folds
20440 } else {
20441 let snapshot = self.buffer.read(cx).snapshot(cx);
20442 let has_folds = snapshot
20443 .all_buffer_ids()
20444 .any(|buffer_id| self.is_buffer_folded(buffer_id, cx));
20445 has_folds
20446 };
20447
20448 if has_folds {
20449 self.unfold_all(&actions::UnfoldAll, window, cx);
20450 } else {
20451 self.fold_all(&actions::FoldAll, window, cx);
20452 }
20453 }
20454
20455 fn fold_at_level(
20456 &mut self,
20457 fold_at: &FoldAtLevel,
20458 window: &mut Window,
20459 cx: &mut Context<Self>,
20460 ) {
20461 if !self.buffer.read(cx).is_singleton() {
20462 return;
20463 }
20464
20465 let fold_at_level = fold_at.0;
20466 let snapshot = self.buffer.read(cx).snapshot(cx);
20467 let mut to_fold = Vec::new();
20468 let mut stack = vec![(0, snapshot.max_row().0, 1)];
20469
20470 let row_ranges_to_keep: Vec<Range<u32>> = self
20471 .selections
20472 .all::<Point>(&self.display_snapshot(cx))
20473 .into_iter()
20474 .map(|sel| sel.start.row..sel.end.row)
20475 .collect();
20476
20477 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
20478 while start_row < end_row {
20479 match self
20480 .snapshot(window, cx)
20481 .crease_for_buffer_row(MultiBufferRow(start_row))
20482 {
20483 Some(crease) => {
20484 let nested_start_row = crease.range().start.row + 1;
20485 let nested_end_row = crease.range().end.row;
20486
20487 if current_level < fold_at_level {
20488 stack.push((nested_start_row, nested_end_row, current_level + 1));
20489 } else if current_level == fold_at_level {
20490 // Fold iff there is no selection completely contained within the fold region
20491 if !row_ranges_to_keep.iter().any(|selection| {
20492 selection.end >= nested_start_row
20493 && selection.start <= nested_end_row
20494 }) {
20495 to_fold.push(crease);
20496 }
20497 }
20498
20499 start_row = nested_end_row + 1;
20500 }
20501 None => start_row += 1,
20502 }
20503 }
20504 }
20505
20506 self.fold_creases(to_fold, true, window, cx);
20507 }
20508
20509 pub fn fold_at_level_1(
20510 &mut self,
20511 _: &actions::FoldAtLevel1,
20512 window: &mut Window,
20513 cx: &mut Context<Self>,
20514 ) {
20515 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
20516 }
20517
20518 pub fn fold_at_level_2(
20519 &mut self,
20520 _: &actions::FoldAtLevel2,
20521 window: &mut Window,
20522 cx: &mut Context<Self>,
20523 ) {
20524 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
20525 }
20526
20527 pub fn fold_at_level_3(
20528 &mut self,
20529 _: &actions::FoldAtLevel3,
20530 window: &mut Window,
20531 cx: &mut Context<Self>,
20532 ) {
20533 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
20534 }
20535
20536 pub fn fold_at_level_4(
20537 &mut self,
20538 _: &actions::FoldAtLevel4,
20539 window: &mut Window,
20540 cx: &mut Context<Self>,
20541 ) {
20542 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
20543 }
20544
20545 pub fn fold_at_level_5(
20546 &mut self,
20547 _: &actions::FoldAtLevel5,
20548 window: &mut Window,
20549 cx: &mut Context<Self>,
20550 ) {
20551 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
20552 }
20553
20554 pub fn fold_at_level_6(
20555 &mut self,
20556 _: &actions::FoldAtLevel6,
20557 window: &mut Window,
20558 cx: &mut Context<Self>,
20559 ) {
20560 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
20561 }
20562
20563 pub fn fold_at_level_7(
20564 &mut self,
20565 _: &actions::FoldAtLevel7,
20566 window: &mut Window,
20567 cx: &mut Context<Self>,
20568 ) {
20569 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
20570 }
20571
20572 pub fn fold_at_level_8(
20573 &mut self,
20574 _: &actions::FoldAtLevel8,
20575 window: &mut Window,
20576 cx: &mut Context<Self>,
20577 ) {
20578 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
20579 }
20580
20581 pub fn fold_at_level_9(
20582 &mut self,
20583 _: &actions::FoldAtLevel9,
20584 window: &mut Window,
20585 cx: &mut Context<Self>,
20586 ) {
20587 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
20588 }
20589
20590 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
20591 if self.buffer.read(cx).is_singleton() {
20592 let mut fold_ranges = Vec::new();
20593 let snapshot = self.buffer.read(cx).snapshot(cx);
20594
20595 for row in 0..snapshot.max_row().0 {
20596 if let Some(foldable_range) = self
20597 .snapshot(window, cx)
20598 .crease_for_buffer_row(MultiBufferRow(row))
20599 {
20600 fold_ranges.push(foldable_range);
20601 }
20602 }
20603
20604 self.fold_creases(fold_ranges, true, window, cx);
20605 } else {
20606 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
20607 editor
20608 .update_in(cx, |editor, _, cx| {
20609 let snapshot = editor.buffer.read(cx).snapshot(cx);
20610 for buffer_id in snapshot.all_buffer_ids() {
20611 editor.fold_buffer(buffer_id, cx);
20612 }
20613 })
20614 .ok();
20615 });
20616 }
20617 }
20618
20619 pub fn fold_function_bodies(
20620 &mut self,
20621 _: &actions::FoldFunctionBodies,
20622 window: &mut Window,
20623 cx: &mut Context<Self>,
20624 ) {
20625 let snapshot = self.buffer.read(cx).snapshot(cx);
20626
20627 let ranges = snapshot
20628 .text_object_ranges(
20629 MultiBufferOffset(0)..snapshot.len(),
20630 TreeSitterOptions::default(),
20631 )
20632 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
20633 .collect::<Vec<_>>();
20634
20635 let creases = ranges
20636 .into_iter()
20637 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
20638 .collect();
20639
20640 self.fold_creases(creases, true, window, cx);
20641 }
20642
20643 pub fn fold_recursive(
20644 &mut self,
20645 _: &actions::FoldRecursive,
20646 window: &mut Window,
20647 cx: &mut Context<Self>,
20648 ) {
20649 let mut to_fold = Vec::new();
20650 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20651 let selections = self.selections.all_adjusted(&display_map);
20652
20653 for selection in selections {
20654 let range = selection.range().sorted();
20655 let buffer_start_row = range.start.row;
20656
20657 if range.start.row != range.end.row {
20658 let mut found = false;
20659 for row in range.start.row..=range.end.row {
20660 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
20661 found = true;
20662 to_fold.push(crease);
20663 }
20664 }
20665 if found {
20666 continue;
20667 }
20668 }
20669
20670 for row in (0..=range.start.row).rev() {
20671 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
20672 if crease.range().end.row >= buffer_start_row {
20673 to_fold.push(crease);
20674 } else {
20675 break;
20676 }
20677 }
20678 }
20679 }
20680
20681 self.fold_creases(to_fold, true, window, cx);
20682 }
20683
20684 pub fn fold_at(
20685 &mut self,
20686 buffer_row: MultiBufferRow,
20687 window: &mut Window,
20688 cx: &mut Context<Self>,
20689 ) {
20690 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20691
20692 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
20693 let autoscroll = self
20694 .selections
20695 .all::<Point>(&display_map)
20696 .iter()
20697 .any(|selection| crease.range().overlaps(&selection.range()));
20698
20699 self.fold_creases(vec![crease], autoscroll, window, cx);
20700 }
20701 }
20702
20703 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
20704 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
20705 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20706 let buffer = display_map.buffer_snapshot();
20707 let selections = self.selections.all::<Point>(&display_map);
20708 let ranges = selections
20709 .iter()
20710 .map(|s| {
20711 let range = s.display_range(&display_map).sorted();
20712 let mut start = range.start.to_point(&display_map);
20713 let mut end = range.end.to_point(&display_map);
20714 start.column = 0;
20715 end.column = buffer.line_len(MultiBufferRow(end.row));
20716 start..end
20717 })
20718 .collect::<Vec<_>>();
20719
20720 self.unfold_ranges(&ranges, true, true, cx);
20721 } else {
20722 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20723 let buffer_ids = self
20724 .selections
20725 .disjoint_anchor_ranges()
20726 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20727 .collect::<HashSet<_>>();
20728 for buffer_id in buffer_ids {
20729 self.unfold_buffer(buffer_id, cx);
20730 }
20731 }
20732 }
20733
20734 pub fn unfold_recursive(
20735 &mut self,
20736 _: &UnfoldRecursive,
20737 _window: &mut Window,
20738 cx: &mut Context<Self>,
20739 ) {
20740 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20741 let selections = self.selections.all::<Point>(&display_map);
20742 let ranges = selections
20743 .iter()
20744 .map(|s| {
20745 let mut range = s.display_range(&display_map).sorted();
20746 *range.start.column_mut() = 0;
20747 *range.end.column_mut() = display_map.line_len(range.end.row());
20748 let start = range.start.to_point(&display_map);
20749 let end = range.end.to_point(&display_map);
20750 start..end
20751 })
20752 .collect::<Vec<_>>();
20753
20754 self.unfold_ranges(&ranges, true, true, cx);
20755 }
20756
20757 pub fn unfold_at(
20758 &mut self,
20759 buffer_row: MultiBufferRow,
20760 _window: &mut Window,
20761 cx: &mut Context<Self>,
20762 ) {
20763 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20764
20765 let intersection_range = Point::new(buffer_row.0, 0)
20766 ..Point::new(
20767 buffer_row.0,
20768 display_map.buffer_snapshot().line_len(buffer_row),
20769 );
20770
20771 let autoscroll = self
20772 .selections
20773 .all::<Point>(&display_map)
20774 .iter()
20775 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
20776
20777 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
20778 }
20779
20780 pub fn unfold_all(
20781 &mut self,
20782 _: &actions::UnfoldAll,
20783 _window: &mut Window,
20784 cx: &mut Context<Self>,
20785 ) {
20786 if self.buffer.read(cx).is_singleton() {
20787 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20788 self.unfold_ranges(
20789 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
20790 true,
20791 true,
20792 cx,
20793 );
20794 } else {
20795 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
20796 editor
20797 .update(cx, |editor, cx| {
20798 let snapshot = editor.buffer.read(cx).snapshot(cx);
20799 for buffer_id in snapshot.all_buffer_ids() {
20800 editor.unfold_buffer(buffer_id, cx);
20801 }
20802 })
20803 .ok();
20804 });
20805 }
20806 }
20807
20808 pub fn fold_selected_ranges(
20809 &mut self,
20810 _: &FoldSelectedRanges,
20811 window: &mut Window,
20812 cx: &mut Context<Self>,
20813 ) {
20814 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20815 let selections = self.selections.all_adjusted(&display_map);
20816 let ranges = selections
20817 .into_iter()
20818 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
20819 .collect::<Vec<_>>();
20820 self.fold_creases(ranges, true, window, cx);
20821 }
20822
20823 pub fn fold_ranges<T: ToOffset + Clone>(
20824 &mut self,
20825 ranges: Vec<Range<T>>,
20826 auto_scroll: bool,
20827 window: &mut Window,
20828 cx: &mut Context<Self>,
20829 ) {
20830 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20831 let ranges = ranges
20832 .into_iter()
20833 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
20834 .collect::<Vec<_>>();
20835 self.fold_creases(ranges, auto_scroll, window, cx);
20836 }
20837
20838 pub fn fold_creases<T: ToOffset + Clone>(
20839 &mut self,
20840 creases: Vec<Crease<T>>,
20841 auto_scroll: bool,
20842 window: &mut Window,
20843 cx: &mut Context<Self>,
20844 ) {
20845 if creases.is_empty() {
20846 return;
20847 }
20848
20849 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
20850
20851 if auto_scroll {
20852 self.request_autoscroll(Autoscroll::fit(), cx);
20853 }
20854
20855 cx.notify();
20856
20857 self.scrollbar_marker_state.dirty = true;
20858 self.update_data_on_scroll(window, cx);
20859 self.folds_did_change(cx);
20860 }
20861
20862 /// Removes any folds whose ranges intersect any of the given ranges.
20863 pub fn unfold_ranges<T: ToOffset + Clone>(
20864 &mut self,
20865 ranges: &[Range<T>],
20866 inclusive: bool,
20867 auto_scroll: bool,
20868 cx: &mut Context<Self>,
20869 ) {
20870 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20871 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx);
20872 });
20873 self.folds_did_change(cx);
20874 }
20875
20876 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20877 self.fold_buffers([buffer_id], cx);
20878 }
20879
20880 pub fn fold_buffers(
20881 &mut self,
20882 buffer_ids: impl IntoIterator<Item = BufferId>,
20883 cx: &mut Context<Self>,
20884 ) {
20885 if self.buffer().read(cx).is_singleton() {
20886 return;
20887 }
20888
20889 let ids_to_fold: Vec<BufferId> = buffer_ids
20890 .into_iter()
20891 .filter(|id| !self.is_buffer_folded(*id, cx))
20892 .collect();
20893
20894 if ids_to_fold.is_empty() {
20895 return;
20896 }
20897
20898 self.display_map.update(cx, |display_map, cx| {
20899 display_map.fold_buffers(ids_to_fold.clone(), cx)
20900 });
20901
20902 let snapshot = self.display_snapshot(cx);
20903 self.selections.change_with(&snapshot, |selections| {
20904 for buffer_id in ids_to_fold.iter().copied() {
20905 selections.remove_selections_from_buffer(buffer_id);
20906 }
20907 });
20908
20909 cx.emit(EditorEvent::BufferFoldToggled {
20910 ids: ids_to_fold,
20911 folded: true,
20912 });
20913 cx.notify();
20914 }
20915
20916 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20917 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
20918 return;
20919 }
20920 self.display_map.update(cx, |display_map, cx| {
20921 display_map.unfold_buffers([buffer_id], cx);
20922 });
20923 cx.emit(EditorEvent::BufferFoldToggled {
20924 ids: vec![buffer_id],
20925 folded: false,
20926 });
20927 cx.notify();
20928 }
20929
20930 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
20931 self.display_map.read(cx).is_buffer_folded(buffer)
20932 }
20933
20934 pub fn has_any_buffer_folded(&self, cx: &App) -> bool {
20935 if self.buffer().read(cx).is_singleton() {
20936 return false;
20937 }
20938 !self.folded_buffers(cx).is_empty()
20939 }
20940
20941 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
20942 self.display_map.read(cx).folded_buffers()
20943 }
20944
20945 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20946 self.display_map.update(cx, |display_map, cx| {
20947 display_map.disable_header_for_buffer(buffer_id, cx);
20948 });
20949 cx.notify();
20950 }
20951
20952 /// Removes any folds with the given ranges.
20953 pub fn remove_folds_with_type<T: ToOffset + Clone>(
20954 &mut self,
20955 ranges: &[Range<T>],
20956 type_id: TypeId,
20957 auto_scroll: bool,
20958 cx: &mut Context<Self>,
20959 ) {
20960 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20961 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
20962 });
20963 self.folds_did_change(cx);
20964 }
20965
20966 fn remove_folds_with<T: ToOffset + Clone>(
20967 &mut self,
20968 ranges: &[Range<T>],
20969 auto_scroll: bool,
20970 cx: &mut Context<Self>,
20971 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
20972 ) {
20973 if ranges.is_empty() {
20974 return;
20975 }
20976
20977 self.display_map.update(cx, update);
20978
20979 if auto_scroll {
20980 self.request_autoscroll(Autoscroll::fit(), cx);
20981 }
20982
20983 cx.notify();
20984 self.scrollbar_marker_state.dirty = true;
20985 self.active_indent_guides_state.dirty = true;
20986 }
20987
20988 pub fn update_renderer_widths(
20989 &mut self,
20990 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
20991 cx: &mut Context<Self>,
20992 ) -> bool {
20993 self.display_map
20994 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
20995 }
20996
20997 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
20998 self.display_map.read(cx).fold_placeholder.clone()
20999 }
21000
21001 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
21002 self.buffer.update(cx, |buffer, cx| {
21003 buffer.set_all_diff_hunks_expanded(cx);
21004 });
21005 }
21006
21007 pub fn expand_all_diff_hunks(
21008 &mut self,
21009 _: &ExpandAllDiffHunks,
21010 _window: &mut Window,
21011 cx: &mut Context<Self>,
21012 ) {
21013 self.buffer.update(cx, |buffer, cx| {
21014 buffer.expand_diff_hunks(vec![Anchor::Min..Anchor::Max], cx)
21015 });
21016 }
21017
21018 pub fn collapse_all_diff_hunks(
21019 &mut self,
21020 _: &CollapseAllDiffHunks,
21021 _window: &mut Window,
21022 cx: &mut Context<Self>,
21023 ) {
21024 self.buffer.update(cx, |buffer, cx| {
21025 buffer.collapse_diff_hunks(vec![Anchor::Min..Anchor::Max], cx)
21026 });
21027 }
21028
21029 pub fn toggle_selected_diff_hunks(
21030 &mut self,
21031 _: &ToggleSelectedDiffHunks,
21032 _window: &mut Window,
21033 cx: &mut Context<Self>,
21034 ) {
21035 let ranges: Vec<_> = self
21036 .selections
21037 .disjoint_anchors()
21038 .iter()
21039 .map(|s| s.range())
21040 .collect();
21041 self.toggle_diff_hunks_in_ranges(ranges, cx);
21042 }
21043
21044 pub fn diff_hunks_in_ranges<'a>(
21045 &'a self,
21046 ranges: &'a [Range<Anchor>],
21047 buffer: &'a MultiBufferSnapshot,
21048 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
21049 ranges.iter().flat_map(move |range| {
21050 let end_excerpt = buffer.excerpt_containing(range.end..range.end);
21051 let range = range.to_point(buffer);
21052 let mut peek_end = range.end;
21053 if range.end.row < buffer.max_row().0 {
21054 peek_end = Point::new(range.end.row + 1, 0);
21055 }
21056 buffer
21057 .diff_hunks_in_range(range.start..peek_end)
21058 .filter(move |hunk| {
21059 if let Some((_, excerpt_range)) = &end_excerpt
21060 && let Some(end_anchor) =
21061 buffer.anchor_in_excerpt(excerpt_range.context.end)
21062 && let Some(hunk_end_anchor) =
21063 buffer.anchor_in_excerpt(hunk.excerpt_range.context.end)
21064 && hunk_end_anchor.cmp(&end_anchor, buffer).is_gt()
21065 {
21066 false
21067 } else {
21068 true
21069 }
21070 })
21071 })
21072 }
21073
21074 pub fn has_stageable_diff_hunks_in_ranges(
21075 &self,
21076 ranges: &[Range<Anchor>],
21077 snapshot: &MultiBufferSnapshot,
21078 ) -> bool {
21079 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
21080 hunks.any(|hunk| hunk.status().has_secondary_hunk())
21081 }
21082
21083 pub fn toggle_staged_selected_diff_hunks(
21084 &mut self,
21085 _: &::git::ToggleStaged,
21086 _: &mut Window,
21087 cx: &mut Context<Self>,
21088 ) {
21089 let snapshot = self.buffer.read(cx).snapshot(cx);
21090 let ranges: Vec<_> = self
21091 .selections
21092 .disjoint_anchors()
21093 .iter()
21094 .map(|s| s.range())
21095 .collect();
21096 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
21097 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
21098 }
21099
21100 pub fn set_render_diff_hunk_controls(
21101 &mut self,
21102 render_diff_hunk_controls: RenderDiffHunkControlsFn,
21103 cx: &mut Context<Self>,
21104 ) {
21105 self.render_diff_hunk_controls = render_diff_hunk_controls;
21106 cx.notify();
21107 }
21108
21109 pub fn stage_and_next(
21110 &mut self,
21111 _: &::git::StageAndNext,
21112 window: &mut Window,
21113 cx: &mut Context<Self>,
21114 ) {
21115 self.do_stage_or_unstage_and_next(true, window, cx);
21116 }
21117
21118 pub fn unstage_and_next(
21119 &mut self,
21120 _: &::git::UnstageAndNext,
21121 window: &mut Window,
21122 cx: &mut Context<Self>,
21123 ) {
21124 self.do_stage_or_unstage_and_next(false, window, cx);
21125 }
21126
21127 pub fn stage_or_unstage_diff_hunks(
21128 &mut self,
21129 stage: bool,
21130 ranges: Vec<Range<Anchor>>,
21131 cx: &mut Context<Self>,
21132 ) {
21133 if self.delegate_stage_and_restore {
21134 let snapshot = self.buffer.read(cx).snapshot(cx);
21135 let hunks: Vec<_> = self.diff_hunks_in_ranges(&ranges, &snapshot).collect();
21136 if !hunks.is_empty() {
21137 cx.emit(EditorEvent::StageOrUnstageRequested { stage, hunks });
21138 }
21139 return;
21140 }
21141 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
21142 cx.spawn(async move |this, cx| {
21143 task.await?;
21144 this.update(cx, |this, cx| {
21145 let snapshot = this.buffer.read(cx).snapshot(cx);
21146 let chunk_by = this
21147 .diff_hunks_in_ranges(&ranges, &snapshot)
21148 .chunk_by(|hunk| hunk.buffer_id);
21149 for (buffer_id, hunks) in &chunk_by {
21150 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
21151 }
21152 })
21153 })
21154 .detach_and_log_err(cx);
21155 }
21156
21157 fn save_buffers_for_ranges_if_needed(
21158 &mut self,
21159 ranges: &[Range<Anchor>],
21160 cx: &mut Context<Editor>,
21161 ) -> Task<Result<()>> {
21162 let multibuffer = self.buffer.read(cx);
21163 let snapshot = multibuffer.read(cx);
21164 let buffer_ids: HashSet<_> = ranges
21165 .iter()
21166 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
21167 .collect();
21168 drop(snapshot);
21169
21170 let mut buffers = HashSet::default();
21171 for buffer_id in buffer_ids {
21172 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
21173 let buffer = buffer_entity.read(cx);
21174 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
21175 {
21176 buffers.insert(buffer_entity);
21177 }
21178 }
21179 }
21180
21181 if let Some(project) = &self.project {
21182 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
21183 } else {
21184 Task::ready(Ok(()))
21185 }
21186 }
21187
21188 fn do_stage_or_unstage_and_next(
21189 &mut self,
21190 stage: bool,
21191 window: &mut Window,
21192 cx: &mut Context<Self>,
21193 ) {
21194 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
21195
21196 if ranges.iter().any(|range| range.start != range.end) {
21197 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
21198 return;
21199 }
21200
21201 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
21202
21203 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
21204 let wrap_around = !all_diff_hunks_expanded;
21205 let snapshot = self.snapshot(window, cx);
21206 let position = self
21207 .selections
21208 .newest::<Point>(&snapshot.display_snapshot)
21209 .head();
21210
21211 self.go_to_hunk_before_or_after_position(
21212 &snapshot,
21213 position,
21214 Direction::Next,
21215 wrap_around,
21216 window,
21217 cx,
21218 );
21219 }
21220
21221 pub(crate) fn do_stage_or_unstage(
21222 &self,
21223 stage: bool,
21224 buffer_id: BufferId,
21225 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
21226 cx: &mut App,
21227 ) -> Option<()> {
21228 let project = self.project()?;
21229 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
21230 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
21231 let buffer_snapshot = buffer.read(cx).snapshot();
21232 let file_exists = buffer_snapshot
21233 .file()
21234 .is_some_and(|file| file.disk_state().exists());
21235 diff.update(cx, |diff, cx| {
21236 diff.stage_or_unstage_hunks(
21237 stage,
21238 &hunks
21239 .map(|hunk| buffer_diff::DiffHunk {
21240 buffer_range: hunk.buffer_range,
21241 // We don't need to pass in word diffs here because they're only used for rendering and
21242 // this function changes internal state
21243 base_word_diffs: Vec::default(),
21244 buffer_word_diffs: Vec::default(),
21245 diff_base_byte_range: hunk.diff_base_byte_range.start.0
21246 ..hunk.diff_base_byte_range.end.0,
21247 secondary_status: hunk.status.secondary,
21248 range: Point::zero()..Point::zero(), // unused
21249 })
21250 .collect::<Vec<_>>(),
21251 &buffer_snapshot,
21252 file_exists,
21253 cx,
21254 )
21255 });
21256 None
21257 }
21258
21259 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
21260 let ranges: Vec<_> = self
21261 .selections
21262 .disjoint_anchors()
21263 .iter()
21264 .map(|s| s.range())
21265 .collect();
21266 self.buffer
21267 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
21268 }
21269
21270 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
21271 self.buffer.update(cx, |buffer, cx| {
21272 let ranges = vec![Anchor::Min..Anchor::Max];
21273 if !buffer.all_diff_hunks_expanded()
21274 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
21275 {
21276 buffer.collapse_diff_hunks(ranges, cx);
21277 true
21278 } else {
21279 false
21280 }
21281 })
21282 }
21283
21284 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
21285 if self.buffer.read(cx).all_diff_hunks_expanded() {
21286 return true;
21287 }
21288 let ranges = vec![Anchor::Min..Anchor::Max];
21289 self.buffer
21290 .read(cx)
21291 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
21292 }
21293
21294 fn toggle_diff_hunks_in_ranges(
21295 &mut self,
21296 ranges: Vec<Range<Anchor>>,
21297 cx: &mut Context<Editor>,
21298 ) {
21299 self.buffer.update(cx, |buffer, cx| {
21300 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
21301 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
21302 })
21303 }
21304
21305 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
21306 self.buffer.update(cx, |buffer, cx| {
21307 buffer.toggle_single_diff_hunk(range, cx);
21308 })
21309 }
21310
21311 pub(crate) fn apply_all_diff_hunks(
21312 &mut self,
21313 _: &ApplyAllDiffHunks,
21314 window: &mut Window,
21315 cx: &mut Context<Self>,
21316 ) {
21317 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
21318
21319 let buffers = self.buffer.read(cx).all_buffers();
21320 for branch_buffer in buffers {
21321 branch_buffer.update(cx, |branch_buffer, cx| {
21322 branch_buffer.merge_into_base(Vec::new(), cx);
21323 });
21324 }
21325
21326 if let Some(project) = self.project.clone() {
21327 self.save(
21328 SaveOptions {
21329 format: true,
21330 autosave: false,
21331 },
21332 project,
21333 window,
21334 cx,
21335 )
21336 .detach_and_log_err(cx);
21337 }
21338 }
21339
21340 pub(crate) fn apply_selected_diff_hunks(
21341 &mut self,
21342 _: &ApplyDiffHunk,
21343 window: &mut Window,
21344 cx: &mut Context<Self>,
21345 ) {
21346 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
21347 let snapshot = self.snapshot(window, cx);
21348 let hunks = snapshot.hunks_for_ranges(
21349 self.selections
21350 .all(&snapshot.display_snapshot)
21351 .into_iter()
21352 .map(|selection| selection.range()),
21353 );
21354 let mut ranges_by_buffer = HashMap::default();
21355 self.transact(window, cx, |editor, _window, cx| {
21356 for hunk in hunks {
21357 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
21358 ranges_by_buffer
21359 .entry(buffer.clone())
21360 .or_insert_with(Vec::new)
21361 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
21362 }
21363 }
21364
21365 for (buffer, ranges) in ranges_by_buffer {
21366 buffer.update(cx, |buffer, cx| {
21367 buffer.merge_into_base(ranges, cx);
21368 });
21369 }
21370 });
21371
21372 if let Some(project) = self.project.clone() {
21373 self.save(
21374 SaveOptions {
21375 format: true,
21376 autosave: false,
21377 },
21378 project,
21379 window,
21380 cx,
21381 )
21382 .detach_and_log_err(cx);
21383 }
21384 }
21385
21386 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
21387 if hovered != self.gutter_hovered {
21388 self.gutter_hovered = hovered;
21389 cx.notify();
21390 }
21391 }
21392
21393 pub fn insert_blocks(
21394 &mut self,
21395 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
21396 autoscroll: Option<Autoscroll>,
21397 cx: &mut Context<Self>,
21398 ) -> Vec<CustomBlockId> {
21399 let blocks = self
21400 .display_map
21401 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
21402 if let Some(autoscroll) = autoscroll {
21403 self.request_autoscroll(autoscroll, cx);
21404 }
21405 cx.notify();
21406 blocks
21407 }
21408
21409 pub fn resize_blocks(
21410 &mut self,
21411 heights: HashMap<CustomBlockId, u32>,
21412 autoscroll: Option<Autoscroll>,
21413 cx: &mut Context<Self>,
21414 ) {
21415 self.display_map
21416 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
21417 if let Some(autoscroll) = autoscroll {
21418 self.request_autoscroll(autoscroll, cx);
21419 }
21420 cx.notify();
21421 }
21422
21423 pub fn replace_blocks(
21424 &mut self,
21425 renderers: HashMap<CustomBlockId, RenderBlock>,
21426 autoscroll: Option<Autoscroll>,
21427 cx: &mut Context<Self>,
21428 ) {
21429 self.display_map
21430 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
21431 if let Some(autoscroll) = autoscroll {
21432 self.request_autoscroll(autoscroll, cx);
21433 }
21434 cx.notify();
21435 }
21436
21437 pub fn remove_blocks(
21438 &mut self,
21439 block_ids: HashSet<CustomBlockId>,
21440 autoscroll: Option<Autoscroll>,
21441 cx: &mut Context<Self>,
21442 ) {
21443 self.display_map.update(cx, |display_map, cx| {
21444 display_map.remove_blocks(block_ids, cx)
21445 });
21446 if let Some(autoscroll) = autoscroll {
21447 self.request_autoscroll(autoscroll, cx);
21448 }
21449 cx.notify();
21450 }
21451
21452 pub fn row_for_block(
21453 &self,
21454 block_id: CustomBlockId,
21455 cx: &mut Context<Self>,
21456 ) -> Option<DisplayRow> {
21457 self.display_map
21458 .update(cx, |map, cx| map.row_for_block(block_id, cx))
21459 }
21460
21461 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
21462 self.focused_block = Some(focused_block);
21463 }
21464
21465 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
21466 self.focused_block.take()
21467 }
21468
21469 pub fn insert_creases(
21470 &mut self,
21471 creases: impl IntoIterator<Item = Crease<Anchor>>,
21472 cx: &mut Context<Self>,
21473 ) -> Vec<CreaseId> {
21474 self.display_map
21475 .update(cx, |map, cx| map.insert_creases(creases, cx))
21476 }
21477
21478 pub fn remove_creases(
21479 &mut self,
21480 ids: impl IntoIterator<Item = CreaseId>,
21481 cx: &mut Context<Self>,
21482 ) -> Vec<(CreaseId, Range<Anchor>)> {
21483 self.display_map
21484 .update(cx, |map, cx| map.remove_creases(ids, cx))
21485 }
21486
21487 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
21488 self.display_map
21489 .update(cx, |map, cx| map.snapshot(cx))
21490 .longest_row()
21491 }
21492
21493 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
21494 self.display_map
21495 .update(cx, |map, cx| map.snapshot(cx))
21496 .max_point()
21497 }
21498
21499 pub fn text(&self, cx: &App) -> String {
21500 self.buffer.read(cx).read(cx).text()
21501 }
21502
21503 pub fn is_empty(&self, cx: &App) -> bool {
21504 self.buffer.read(cx).read(cx).is_empty()
21505 }
21506
21507 pub fn text_option(&self, cx: &App) -> Option<String> {
21508 let text = self.text(cx);
21509 let text = text.trim();
21510
21511 if text.is_empty() {
21512 return None;
21513 }
21514
21515 Some(text.to_string())
21516 }
21517
21518 pub fn set_text(
21519 &mut self,
21520 text: impl Into<Arc<str>>,
21521 window: &mut Window,
21522 cx: &mut Context<Self>,
21523 ) {
21524 self.transact(window, cx, |this, _, cx| {
21525 this.buffer
21526 .read(cx)
21527 .as_singleton()
21528 .expect("you can only call set_text on editors for singleton buffers")
21529 .update(cx, |buffer, cx| buffer.set_text(text, cx));
21530 });
21531 }
21532
21533 pub fn display_text(&self, cx: &mut App) -> String {
21534 self.display_map
21535 .update(cx, |map, cx| map.snapshot(cx))
21536 .text()
21537 }
21538
21539 fn create_minimap(
21540 &self,
21541 minimap_settings: MinimapSettings,
21542 window: &mut Window,
21543 cx: &mut Context<Self>,
21544 ) -> Option<Entity<Self>> {
21545 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
21546 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
21547 }
21548
21549 fn initialize_new_minimap(
21550 &self,
21551 minimap_settings: MinimapSettings,
21552 window: &mut Window,
21553 cx: &mut Context<Self>,
21554 ) -> Entity<Self> {
21555 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
21556 const MINIMAP_FONT_FAMILY: SharedString = SharedString::new_static(".ZedMono");
21557
21558 let mut minimap = Editor::new_internal(
21559 EditorMode::Minimap {
21560 parent: cx.weak_entity(),
21561 },
21562 self.buffer.clone(),
21563 None,
21564 Some(self.display_map.clone()),
21565 window,
21566 cx,
21567 );
21568 let my_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
21569 let minimap_snapshot = minimap.display_map.update(cx, |map, cx| map.snapshot(cx));
21570 minimap.scroll_manager.clone_state(
21571 &self.scroll_manager,
21572 &my_snapshot,
21573 &minimap_snapshot,
21574 cx,
21575 );
21576 minimap.set_text_style_refinement(TextStyleRefinement {
21577 font_size: Some(MINIMAP_FONT_SIZE),
21578 font_weight: Some(MINIMAP_FONT_WEIGHT),
21579 font_family: Some(MINIMAP_FONT_FAMILY),
21580 ..Default::default()
21581 });
21582 minimap.update_minimap_configuration(minimap_settings, cx);
21583 cx.new(|_| minimap)
21584 }
21585
21586 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
21587 let current_line_highlight = minimap_settings
21588 .current_line_highlight
21589 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
21590 self.set_current_line_highlight(Some(current_line_highlight));
21591 }
21592
21593 pub fn minimap(&self) -> Option<&Entity<Self>> {
21594 self.minimap
21595 .as_ref()
21596 .filter(|_| self.minimap_visibility.visible())
21597 }
21598
21599 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
21600 let mut wrap_guides = smallvec![];
21601
21602 if self.show_wrap_guides == Some(false) {
21603 return wrap_guides;
21604 }
21605
21606 let settings = self.buffer.read(cx).language_settings(cx);
21607 if settings.show_wrap_guides {
21608 match self.soft_wrap_mode(cx) {
21609 SoftWrap::Column(soft_wrap) => {
21610 wrap_guides.push((soft_wrap as usize, true));
21611 }
21612 SoftWrap::Bounded(soft_wrap) => {
21613 wrap_guides.push((soft_wrap as usize, true));
21614 }
21615 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
21616 }
21617 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
21618 }
21619
21620 wrap_guides
21621 }
21622
21623 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
21624 let settings = self.buffer.read(cx).language_settings(cx);
21625 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
21626 match mode {
21627 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
21628 SoftWrap::None
21629 }
21630 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
21631 language_settings::SoftWrap::PreferredLineLength => {
21632 SoftWrap::Column(settings.preferred_line_length)
21633 }
21634 language_settings::SoftWrap::Bounded => {
21635 SoftWrap::Bounded(settings.preferred_line_length)
21636 }
21637 }
21638 }
21639
21640 pub fn set_soft_wrap_mode(
21641 &mut self,
21642 mode: language_settings::SoftWrap,
21643 cx: &mut Context<Self>,
21644 ) {
21645 self.soft_wrap_mode_override = Some(mode);
21646 cx.notify();
21647 }
21648
21649 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
21650 self.hard_wrap = hard_wrap;
21651 cx.notify();
21652 }
21653
21654 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
21655 self.text_style_refinement = Some(style);
21656 }
21657
21658 /// called by the Element so we know what style we were most recently rendered with.
21659 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
21660 // We intentionally do not inform the display map about the minimap style
21661 // so that wrapping is not recalculated and stays consistent for the editor
21662 // and its linked minimap.
21663 if !self.mode.is_minimap() {
21664 let font = style.text.font();
21665 let font_size = style.text.font_size.to_pixels(window.rem_size());
21666 let display_map = self
21667 .placeholder_display_map
21668 .as_ref()
21669 .filter(|_| self.is_empty(cx))
21670 .unwrap_or(&self.display_map);
21671
21672 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
21673 }
21674 self.style = Some(style);
21675 }
21676
21677 pub fn style(&mut self, cx: &App) -> &EditorStyle {
21678 if self.style.is_none() {
21679 self.style = Some(self.create_style(cx));
21680 }
21681 self.style.as_ref().unwrap()
21682 }
21683
21684 // Called by the element. This method is not designed to be called outside of the editor
21685 // element's layout code because it does not notify when rewrapping is computed synchronously.
21686 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
21687 if self.is_empty(cx) {
21688 self.placeholder_display_map
21689 .as_ref()
21690 .map_or(false, |display_map| {
21691 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
21692 })
21693 } else {
21694 self.display_map
21695 .update(cx, |map, cx| map.set_wrap_width(width, cx))
21696 }
21697 }
21698
21699 pub fn set_soft_wrap(&mut self) {
21700 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
21701 }
21702
21703 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
21704 if self.soft_wrap_mode_override.is_some() {
21705 self.soft_wrap_mode_override.take();
21706 } else {
21707 let soft_wrap = match self.soft_wrap_mode(cx) {
21708 SoftWrap::GitDiff => return,
21709 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
21710 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
21711 language_settings::SoftWrap::None
21712 }
21713 };
21714 self.soft_wrap_mode_override = Some(soft_wrap);
21715 }
21716 cx.notify();
21717 }
21718
21719 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
21720 let Some(workspace) = self.workspace() else {
21721 return;
21722 };
21723 let fs = workspace.read(cx).app_state().fs.clone();
21724 let current_show = TabBarSettings::get_global(cx).show;
21725 update_settings_file(fs, cx, move |setting, _| {
21726 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
21727 });
21728 }
21729
21730 pub fn toggle_indent_guides(
21731 &mut self,
21732 _: &ToggleIndentGuides,
21733 _: &mut Window,
21734 cx: &mut Context<Self>,
21735 ) {
21736 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
21737 self.buffer
21738 .read(cx)
21739 .language_settings(cx)
21740 .indent_guides
21741 .enabled
21742 });
21743 self.show_indent_guides = Some(!currently_enabled);
21744 cx.notify();
21745 }
21746
21747 fn should_show_indent_guides(&self) -> Option<bool> {
21748 self.show_indent_guides
21749 }
21750
21751 pub fn disable_indent_guides_for_buffer(
21752 &mut self,
21753 buffer_id: BufferId,
21754 cx: &mut Context<Self>,
21755 ) {
21756 self.buffers_with_disabled_indent_guides.insert(buffer_id);
21757 cx.notify();
21758 }
21759
21760 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
21761 self.buffers_with_disabled_indent_guides
21762 .contains(&buffer_id)
21763 }
21764
21765 pub fn toggle_line_numbers(
21766 &mut self,
21767 _: &ToggleLineNumbers,
21768 _: &mut Window,
21769 cx: &mut Context<Self>,
21770 ) {
21771 let mut editor_settings = EditorSettings::get_global(cx).clone();
21772 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
21773 EditorSettings::override_global(editor_settings, cx);
21774 }
21775
21776 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
21777 if let Some(show_line_numbers) = self.show_line_numbers {
21778 return show_line_numbers;
21779 }
21780 EditorSettings::get_global(cx).gutter.line_numbers
21781 }
21782
21783 pub fn relative_line_numbers(&self, cx: &App) -> RelativeLineNumbers {
21784 match (
21785 self.use_relative_line_numbers,
21786 EditorSettings::get_global(cx).relative_line_numbers,
21787 ) {
21788 (None, setting) => setting,
21789 (Some(false), _) => RelativeLineNumbers::Disabled,
21790 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
21791 (Some(true), _) => RelativeLineNumbers::Enabled,
21792 }
21793 }
21794
21795 pub fn toggle_relative_line_numbers(
21796 &mut self,
21797 _: &ToggleRelativeLineNumbers,
21798 _: &mut Window,
21799 cx: &mut Context<Self>,
21800 ) {
21801 let is_relative = self.relative_line_numbers(cx);
21802 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
21803 }
21804
21805 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
21806 self.use_relative_line_numbers = is_relative;
21807 cx.notify();
21808 }
21809
21810 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
21811 self.show_gutter = show_gutter;
21812 cx.notify();
21813 }
21814
21815 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
21816 self.show_scrollbars = ScrollbarAxes {
21817 horizontal: show,
21818 vertical: show,
21819 };
21820 cx.notify();
21821 }
21822
21823 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21824 self.show_scrollbars.vertical = show;
21825 cx.notify();
21826 }
21827
21828 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21829 self.show_scrollbars.horizontal = show;
21830 cx.notify();
21831 }
21832
21833 pub fn set_minimap_visibility(
21834 &mut self,
21835 minimap_visibility: MinimapVisibility,
21836 window: &mut Window,
21837 cx: &mut Context<Self>,
21838 ) {
21839 if self.minimap_visibility != minimap_visibility {
21840 if minimap_visibility.visible() && self.minimap.is_none() {
21841 let minimap_settings = EditorSettings::get_global(cx).minimap;
21842 self.minimap =
21843 self.create_minimap(minimap_settings.with_show_override(), window, cx);
21844 }
21845 self.minimap_visibility = minimap_visibility;
21846 cx.notify();
21847 }
21848 }
21849
21850 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21851 self.set_show_scrollbars(false, cx);
21852 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
21853 }
21854
21855 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21856 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
21857 }
21858
21859 /// Normally the text in full mode and auto height editors is padded on the
21860 /// left side by roughly half a character width for improved hit testing.
21861 ///
21862 /// Use this method to disable this for cases where this is not wanted (e.g.
21863 /// if you want to align the editor text with some other text above or below)
21864 /// or if you want to add this padding to single-line editors.
21865 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
21866 self.offset_content = offset_content;
21867 cx.notify();
21868 }
21869
21870 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
21871 self.show_line_numbers = Some(show_line_numbers);
21872 cx.notify();
21873 }
21874
21875 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
21876 self.disable_expand_excerpt_buttons = true;
21877 cx.notify();
21878 }
21879
21880 pub fn set_number_deleted_lines(&mut self, number: bool, cx: &mut Context<Self>) {
21881 self.number_deleted_lines = number;
21882 cx.notify();
21883 }
21884
21885 pub fn set_delegate_expand_excerpts(&mut self, delegate: bool) {
21886 self.delegate_expand_excerpts = delegate;
21887 }
21888
21889 pub fn set_delegate_stage_and_restore(&mut self, delegate: bool) {
21890 self.delegate_stage_and_restore = delegate;
21891 }
21892
21893 pub fn set_delegate_open_excerpts(&mut self, delegate: bool) {
21894 self.delegate_open_excerpts = delegate;
21895 }
21896
21897 pub fn set_on_local_selections_changed(
21898 &mut self,
21899 callback: Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
21900 ) {
21901 self.on_local_selections_changed = callback;
21902 }
21903
21904 pub fn set_suppress_selection_callback(&mut self, suppress: bool) {
21905 self.suppress_selection_callback = suppress;
21906 }
21907
21908 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
21909 self.show_git_diff_gutter = Some(show_git_diff_gutter);
21910 cx.notify();
21911 }
21912
21913 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
21914 self.show_code_actions = Some(show_code_actions);
21915 cx.notify();
21916 }
21917
21918 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
21919 self.show_runnables = Some(show_runnables);
21920 cx.notify();
21921 }
21922
21923 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
21924 self.show_breakpoints = Some(show_breakpoints);
21925 cx.notify();
21926 }
21927
21928 pub fn set_show_diff_review_button(&mut self, show: bool, cx: &mut Context<Self>) {
21929 self.show_diff_review_button = show;
21930 cx.notify();
21931 }
21932
21933 pub fn show_diff_review_button(&self) -> bool {
21934 self.show_diff_review_button
21935 }
21936
21937 pub fn render_diff_review_button(
21938 &self,
21939 display_row: DisplayRow,
21940 width: Pixels,
21941 cx: &mut Context<Self>,
21942 ) -> impl IntoElement {
21943 let text_color = cx.theme().colors().text;
21944 let icon_color = cx.theme().colors().icon_accent;
21945
21946 h_flex()
21947 .id("diff_review_button")
21948 .cursor_pointer()
21949 .w(width - px(1.))
21950 .h(relative(0.9))
21951 .justify_center()
21952 .rounded_sm()
21953 .border_1()
21954 .border_color(text_color.opacity(0.1))
21955 .bg(text_color.opacity(0.15))
21956 .hover(|s| {
21957 s.bg(icon_color.opacity(0.4))
21958 .border_color(icon_color.opacity(0.5))
21959 })
21960 .child(Icon::new(IconName::Plus).size(IconSize::Small))
21961 .tooltip(Tooltip::text("Add Review (drag to select multiple lines)"))
21962 .on_mouse_down(
21963 gpui::MouseButton::Left,
21964 cx.listener(move |editor, _event: &gpui::MouseDownEvent, window, cx| {
21965 editor.start_diff_review_drag(display_row, window, cx);
21966 }),
21967 )
21968 }
21969
21970 pub fn start_diff_review_drag(
21971 &mut self,
21972 display_row: DisplayRow,
21973 window: &mut Window,
21974 cx: &mut Context<Self>,
21975 ) {
21976 let snapshot = self.snapshot(window, cx);
21977 let point = snapshot
21978 .display_snapshot
21979 .display_point_to_point(DisplayPoint::new(display_row, 0), Bias::Left);
21980 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21981 self.diff_review_drag_state = Some(DiffReviewDragState {
21982 start_anchor: anchor,
21983 current_anchor: anchor,
21984 });
21985 cx.notify();
21986 }
21987
21988 pub fn update_diff_review_drag(
21989 &mut self,
21990 display_row: DisplayRow,
21991 window: &mut Window,
21992 cx: &mut Context<Self>,
21993 ) {
21994 if self.diff_review_drag_state.is_none() {
21995 return;
21996 }
21997 let snapshot = self.snapshot(window, cx);
21998 let point = snapshot
21999 .display_snapshot
22000 .display_point_to_point(display_row.as_display_point(), Bias::Left);
22001 let anchor = snapshot.buffer_snapshot().anchor_before(point);
22002 if let Some(drag_state) = &mut self.diff_review_drag_state {
22003 drag_state.current_anchor = anchor;
22004 cx.notify();
22005 }
22006 }
22007
22008 pub fn end_diff_review_drag(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22009 if let Some(drag_state) = self.diff_review_drag_state.take() {
22010 let snapshot = self.snapshot(window, cx);
22011 let range = drag_state.row_range(&snapshot.display_snapshot);
22012 self.show_diff_review_overlay(*range.start()..*range.end(), window, cx);
22013 }
22014 cx.notify();
22015 }
22016
22017 pub fn cancel_diff_review_drag(&mut self, cx: &mut Context<Self>) {
22018 self.diff_review_drag_state = None;
22019 cx.notify();
22020 }
22021
22022 /// Calculates the appropriate block height for the diff review overlay.
22023 /// Height is in lines: 2 for input row, 1 for header when comments exist,
22024 /// and 2 lines per comment when expanded.
22025 fn calculate_overlay_height(
22026 &self,
22027 hunk_key: &DiffHunkKey,
22028 comments_expanded: bool,
22029 snapshot: &MultiBufferSnapshot,
22030 ) -> u32 {
22031 let comment_count = self.hunk_comment_count(hunk_key, snapshot);
22032 let base_height: u32 = 2; // Input row with avatar and buttons
22033
22034 if comment_count == 0 {
22035 base_height
22036 } else if comments_expanded {
22037 // Header (1 line) + 2 lines per comment
22038 base_height + 1 + (comment_count as u32 * 2)
22039 } else {
22040 // Just header when collapsed
22041 base_height + 1
22042 }
22043 }
22044
22045 pub fn show_diff_review_overlay(
22046 &mut self,
22047 display_range: Range<DisplayRow>,
22048 window: &mut Window,
22049 cx: &mut Context<Self>,
22050 ) {
22051 let Range { start, end } = display_range.sorted();
22052
22053 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
22054 let editor_snapshot = self.snapshot(window, cx);
22055
22056 // Convert display rows to multibuffer points
22057 let start_point = editor_snapshot
22058 .display_snapshot
22059 .display_point_to_point(start.as_display_point(), Bias::Left);
22060 let end_point = editor_snapshot
22061 .display_snapshot
22062 .display_point_to_point(end.as_display_point(), Bias::Left);
22063 let end_multi_buffer_row = MultiBufferRow(end_point.row);
22064
22065 // Create anchor range for the selected lines (start of first line to end of last line)
22066 let line_end = Point::new(
22067 end_point.row,
22068 buffer_snapshot.line_len(end_multi_buffer_row),
22069 );
22070 let anchor_range =
22071 buffer_snapshot.anchor_after(start_point)..buffer_snapshot.anchor_before(line_end);
22072
22073 // Compute the hunk key for this display row
22074 let file_path = buffer_snapshot
22075 .file_at(start_point)
22076 .map(|file: &Arc<dyn language::File>| file.path().clone())
22077 .unwrap_or_else(|| Arc::from(util::rel_path::RelPath::empty()));
22078 let hunk_start_anchor = buffer_snapshot.anchor_before(start_point);
22079 let new_hunk_key = DiffHunkKey {
22080 file_path,
22081 hunk_start_anchor,
22082 };
22083
22084 // Check if we already have an overlay for this hunk
22085 if let Some(existing_overlay) = self.diff_review_overlays.iter().find(|overlay| {
22086 Self::hunk_keys_match(&overlay.hunk_key, &new_hunk_key, &buffer_snapshot)
22087 }) {
22088 // Just focus the existing overlay's prompt editor
22089 let focus_handle = existing_overlay.prompt_editor.focus_handle(cx);
22090 window.focus(&focus_handle, cx);
22091 return;
22092 }
22093
22094 // Dismiss overlays that have no comments for their hunks
22095 self.dismiss_overlays_without_comments(cx);
22096
22097 // Get the current user's avatar URI from the project's user_store
22098 let user_avatar_uri = self.project.as_ref().and_then(|project| {
22099 let user_store = project.read(cx).user_store();
22100 user_store
22101 .read(cx)
22102 .current_user()
22103 .map(|user| user.avatar_uri.clone())
22104 });
22105
22106 // Create anchor at the end of the last row so the block appears immediately below it
22107 // Use multibuffer coordinates for anchor creation
22108 let line_len = buffer_snapshot.line_len(end_multi_buffer_row);
22109 let anchor = buffer_snapshot.anchor_after(Point::new(end_multi_buffer_row.0, line_len));
22110
22111 // Use the hunk key we already computed
22112 let hunk_key = new_hunk_key;
22113
22114 // Create the prompt editor for the review input
22115 let prompt_editor = cx.new(|cx| {
22116 let mut editor = Editor::single_line(window, cx);
22117 editor.set_placeholder_text("Add a review comment...", window, cx);
22118 editor
22119 });
22120
22121 // Register the Newline action on the prompt editor to submit the review
22122 let parent_editor = cx.entity().downgrade();
22123 let subscription = prompt_editor.update(cx, |prompt_editor, _cx| {
22124 prompt_editor.register_action({
22125 let parent_editor = parent_editor.clone();
22126 move |_: &crate::actions::Newline, window, cx| {
22127 if let Some(editor) = parent_editor.upgrade() {
22128 editor.update(cx, |editor, cx| {
22129 editor.submit_diff_review_comment(window, cx);
22130 });
22131 }
22132 }
22133 })
22134 });
22135
22136 // Calculate initial height based on existing comments for this hunk
22137 let initial_height = self.calculate_overlay_height(&hunk_key, true, &buffer_snapshot);
22138
22139 // Create the overlay block
22140 let prompt_editor_for_render = prompt_editor.clone();
22141 let hunk_key_for_render = hunk_key.clone();
22142 let editor_handle = cx.entity().downgrade();
22143 let block = BlockProperties {
22144 style: BlockStyle::Sticky,
22145 placement: BlockPlacement::Below(anchor),
22146 height: Some(initial_height),
22147 render: Arc::new(move |cx| {
22148 Self::render_diff_review_overlay(
22149 &prompt_editor_for_render,
22150 &hunk_key_for_render,
22151 &editor_handle,
22152 cx,
22153 )
22154 }),
22155 priority: 0,
22156 };
22157
22158 let block_ids = self.insert_blocks([block], None, cx);
22159 let Some(block_id) = block_ids.into_iter().next() else {
22160 log::error!("Failed to insert diff review overlay block");
22161 return;
22162 };
22163
22164 self.diff_review_overlays.push(DiffReviewOverlay {
22165 anchor_range,
22166 block_id,
22167 prompt_editor: prompt_editor.clone(),
22168 hunk_key,
22169 comments_expanded: true,
22170 inline_edit_editors: HashMap::default(),
22171 inline_edit_subscriptions: HashMap::default(),
22172 user_avatar_uri,
22173 _subscription: subscription,
22174 });
22175
22176 // Focus the prompt editor
22177 let focus_handle = prompt_editor.focus_handle(cx);
22178 window.focus(&focus_handle, cx);
22179
22180 cx.notify();
22181 }
22182
22183 /// Dismisses all diff review overlays.
22184 pub fn dismiss_all_diff_review_overlays(&mut self, cx: &mut Context<Self>) {
22185 if self.diff_review_overlays.is_empty() {
22186 return;
22187 }
22188 let block_ids: HashSet<_> = self
22189 .diff_review_overlays
22190 .drain(..)
22191 .map(|overlay| overlay.block_id)
22192 .collect();
22193 self.remove_blocks(block_ids, None, cx);
22194 cx.notify();
22195 }
22196
22197 /// Dismisses overlays that have no comments stored for their hunks.
22198 /// Keeps overlays that have at least one comment.
22199 fn dismiss_overlays_without_comments(&mut self, cx: &mut Context<Self>) {
22200 let snapshot = self.buffer.read(cx).snapshot(cx);
22201
22202 // First, compute which overlays have comments (to avoid borrow issues with retain)
22203 let overlays_with_comments: Vec<bool> = self
22204 .diff_review_overlays
22205 .iter()
22206 .map(|overlay| self.hunk_comment_count(&overlay.hunk_key, &snapshot) > 0)
22207 .collect();
22208
22209 // Now collect block IDs to remove and retain overlays
22210 let mut block_ids_to_remove = HashSet::default();
22211 let mut index = 0;
22212 self.diff_review_overlays.retain(|overlay| {
22213 let has_comments = overlays_with_comments[index];
22214 index += 1;
22215 if !has_comments {
22216 block_ids_to_remove.insert(overlay.block_id);
22217 }
22218 has_comments
22219 });
22220
22221 if !block_ids_to_remove.is_empty() {
22222 self.remove_blocks(block_ids_to_remove, None, cx);
22223 cx.notify();
22224 }
22225 }
22226
22227 /// Refreshes the diff review overlay block to update its height and render function.
22228 /// Uses resize_blocks and replace_blocks to avoid visual flicker from remove+insert.
22229 fn refresh_diff_review_overlay_height(
22230 &mut self,
22231 hunk_key: &DiffHunkKey,
22232 _window: &mut Window,
22233 cx: &mut Context<Self>,
22234 ) {
22235 // Extract all needed data from overlay first to avoid borrow conflicts
22236 let snapshot = self.buffer.read(cx).snapshot(cx);
22237 let (comments_expanded, block_id, prompt_editor) = {
22238 let Some(overlay) = self
22239 .diff_review_overlays
22240 .iter()
22241 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
22242 else {
22243 return;
22244 };
22245
22246 (
22247 overlay.comments_expanded,
22248 overlay.block_id,
22249 overlay.prompt_editor.clone(),
22250 )
22251 };
22252
22253 // Calculate new height
22254 let snapshot = self.buffer.read(cx).snapshot(cx);
22255 let new_height = self.calculate_overlay_height(hunk_key, comments_expanded, &snapshot);
22256
22257 // Update the block height using resize_blocks (avoids flicker)
22258 let mut heights = HashMap::default();
22259 heights.insert(block_id, new_height);
22260 self.resize_blocks(heights, None, cx);
22261
22262 // Update the render function using replace_blocks (avoids flicker)
22263 let hunk_key_for_render = hunk_key.clone();
22264 let editor_handle = cx.entity().downgrade();
22265 let render: Arc<dyn Fn(&mut BlockContext) -> AnyElement + Send + Sync> =
22266 Arc::new(move |cx| {
22267 Self::render_diff_review_overlay(
22268 &prompt_editor,
22269 &hunk_key_for_render,
22270 &editor_handle,
22271 cx,
22272 )
22273 });
22274
22275 let mut renderers = HashMap::default();
22276 renderers.insert(block_id, render);
22277 self.replace_blocks(renderers, None, cx);
22278 }
22279
22280 /// Action handler for SubmitDiffReviewComment.
22281 pub fn submit_diff_review_comment_action(
22282 &mut self,
22283 _: &SubmitDiffReviewComment,
22284 window: &mut Window,
22285 cx: &mut Context<Self>,
22286 ) {
22287 self.submit_diff_review_comment(window, cx);
22288 }
22289
22290 /// Stores the diff review comment locally.
22291 /// Comments are stored per-hunk and can later be batch-submitted to the Agent panel.
22292 pub fn submit_diff_review_comment(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22293 // Find the overlay that currently has focus
22294 let overlay_index = self
22295 .diff_review_overlays
22296 .iter()
22297 .position(|overlay| overlay.prompt_editor.focus_handle(cx).is_focused(window));
22298 let Some(overlay_index) = overlay_index else {
22299 return;
22300 };
22301 let overlay = &self.diff_review_overlays[overlay_index];
22302
22303 let comment_text = overlay.prompt_editor.read(cx).text(cx).trim().to_string();
22304 if comment_text.is_empty() {
22305 return;
22306 }
22307
22308 let anchor_range = overlay.anchor_range.clone();
22309 let hunk_key = overlay.hunk_key.clone();
22310
22311 self.add_review_comment(hunk_key.clone(), comment_text, anchor_range, cx);
22312
22313 // Clear the prompt editor but keep the overlay open
22314 if let Some(overlay) = self.diff_review_overlays.get(overlay_index) {
22315 overlay.prompt_editor.update(cx, |editor, cx| {
22316 editor.clear(window, cx);
22317 });
22318 }
22319
22320 // Refresh the overlay to update the block height for the new comment
22321 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22322
22323 cx.notify();
22324 }
22325
22326 /// Returns the prompt editor for the diff review overlay, if one is active.
22327 /// This is primarily used for testing.
22328 pub fn diff_review_prompt_editor(&self) -> Option<&Entity<Editor>> {
22329 self.diff_review_overlays
22330 .first()
22331 .map(|overlay| &overlay.prompt_editor)
22332 }
22333
22334 /// Returns the line range for the first diff review overlay, if one is active.
22335 /// Returns (start_row, end_row) as physical line numbers in the underlying file.
22336 pub fn diff_review_line_range(&self, cx: &App) -> Option<(u32, u32)> {
22337 let overlay = self.diff_review_overlays.first()?;
22338 let snapshot = self.buffer.read(cx).snapshot(cx);
22339 let start_point = overlay.anchor_range.start.to_point(&snapshot);
22340 let end_point = overlay.anchor_range.end.to_point(&snapshot);
22341 let start_row = snapshot
22342 .point_to_buffer_point(start_point)
22343 .map(|(_, p)| p.row)
22344 .unwrap_or(start_point.row);
22345 let end_row = snapshot
22346 .point_to_buffer_point(end_point)
22347 .map(|(_, p)| p.row)
22348 .unwrap_or(end_point.row);
22349 Some((start_row, end_row))
22350 }
22351
22352 /// Sets whether the comments section is expanded in the diff review overlay.
22353 /// This is primarily used for testing.
22354 pub fn set_diff_review_comments_expanded(&mut self, expanded: bool, cx: &mut Context<Self>) {
22355 for overlay in &mut self.diff_review_overlays {
22356 overlay.comments_expanded = expanded;
22357 }
22358 cx.notify();
22359 }
22360
22361 /// Compares two DiffHunkKeys for equality by resolving their anchors.
22362 fn hunk_keys_match(a: &DiffHunkKey, b: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> bool {
22363 a.file_path == b.file_path
22364 && a.hunk_start_anchor.to_point(snapshot) == b.hunk_start_anchor.to_point(snapshot)
22365 }
22366
22367 /// Returns comments for a specific hunk, ordered by creation time.
22368 pub fn comments_for_hunk<'a>(
22369 &'a self,
22370 key: &DiffHunkKey,
22371 snapshot: &MultiBufferSnapshot,
22372 ) -> &'a [StoredReviewComment] {
22373 let key_point = key.hunk_start_anchor.to_point(snapshot);
22374 self.stored_review_comments
22375 .iter()
22376 .find(|(k, _)| {
22377 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
22378 })
22379 .map(|(_, comments)| comments.as_slice())
22380 .unwrap_or(&[])
22381 }
22382
22383 /// Returns the total count of stored review comments across all hunks.
22384 pub fn total_review_comment_count(&self) -> usize {
22385 self.stored_review_comments
22386 .iter()
22387 .map(|(_, v)| v.len())
22388 .sum()
22389 }
22390
22391 /// Returns the count of comments for a specific hunk.
22392 pub fn hunk_comment_count(&self, key: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> usize {
22393 let key_point = key.hunk_start_anchor.to_point(snapshot);
22394 self.stored_review_comments
22395 .iter()
22396 .find(|(k, _)| {
22397 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
22398 })
22399 .map(|(_, v)| v.len())
22400 .unwrap_or(0)
22401 }
22402
22403 /// Adds a new review comment to a specific hunk.
22404 pub fn add_review_comment(
22405 &mut self,
22406 hunk_key: DiffHunkKey,
22407 comment: String,
22408 anchor_range: Range<Anchor>,
22409 cx: &mut Context<Self>,
22410 ) -> usize {
22411 let id = self.next_review_comment_id;
22412 self.next_review_comment_id += 1;
22413
22414 let stored_comment = StoredReviewComment::new(id, comment, anchor_range);
22415
22416 let snapshot = self.buffer.read(cx).snapshot(cx);
22417 let key_point = hunk_key.hunk_start_anchor.to_point(&snapshot);
22418
22419 // Find existing entry for this hunk or add a new one
22420 if let Some((_, comments)) = self.stored_review_comments.iter_mut().find(|(k, _)| {
22421 k.file_path == hunk_key.file_path
22422 && k.hunk_start_anchor.to_point(&snapshot) == key_point
22423 }) {
22424 comments.push(stored_comment);
22425 } else {
22426 self.stored_review_comments
22427 .push((hunk_key, vec![stored_comment]));
22428 }
22429
22430 cx.emit(EditorEvent::ReviewCommentsChanged {
22431 total_count: self.total_review_comment_count(),
22432 });
22433 cx.notify();
22434 id
22435 }
22436
22437 /// Removes a review comment by ID from any hunk.
22438 pub fn remove_review_comment(&mut self, id: usize, cx: &mut Context<Self>) -> bool {
22439 for (_, comments) in self.stored_review_comments.iter_mut() {
22440 if let Some(index) = comments.iter().position(|c| c.id == id) {
22441 comments.remove(index);
22442 cx.emit(EditorEvent::ReviewCommentsChanged {
22443 total_count: self.total_review_comment_count(),
22444 });
22445 cx.notify();
22446 return true;
22447 }
22448 }
22449 false
22450 }
22451
22452 /// Updates a review comment's text by ID.
22453 pub fn update_review_comment(
22454 &mut self,
22455 id: usize,
22456 new_comment: String,
22457 cx: &mut Context<Self>,
22458 ) -> bool {
22459 for (_, comments) in self.stored_review_comments.iter_mut() {
22460 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
22461 comment.comment = new_comment;
22462 comment.is_editing = false;
22463 cx.emit(EditorEvent::ReviewCommentsChanged {
22464 total_count: self.total_review_comment_count(),
22465 });
22466 cx.notify();
22467 return true;
22468 }
22469 }
22470 false
22471 }
22472
22473 /// Sets a comment's editing state.
22474 pub fn set_comment_editing(&mut self, id: usize, is_editing: bool, cx: &mut Context<Self>) {
22475 for (_, comments) in self.stored_review_comments.iter_mut() {
22476 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
22477 comment.is_editing = is_editing;
22478 cx.notify();
22479 return;
22480 }
22481 }
22482 }
22483
22484 /// Takes all stored comments from all hunks, clearing the storage.
22485 /// Returns a Vec of (hunk_key, comments) pairs.
22486 pub fn take_all_review_comments(
22487 &mut self,
22488 cx: &mut Context<Self>,
22489 ) -> Vec<(DiffHunkKey, Vec<StoredReviewComment>)> {
22490 // Dismiss all overlays when taking comments (e.g., when sending to agent)
22491 self.dismiss_all_diff_review_overlays(cx);
22492 let comments = std::mem::take(&mut self.stored_review_comments);
22493 // Reset the ID counter since all comments have been taken
22494 self.next_review_comment_id = 0;
22495 cx.emit(EditorEvent::ReviewCommentsChanged { total_count: 0 });
22496 cx.notify();
22497 comments
22498 }
22499
22500 /// Removes review comments whose anchors are no longer valid or whose
22501 /// associated diff hunks no longer exist.
22502 ///
22503 /// This should be called when the buffer changes to prevent orphaned comments
22504 /// from accumulating.
22505 pub fn cleanup_orphaned_review_comments(&mut self, cx: &mut Context<Self>) {
22506 let snapshot = self.buffer.read(cx).snapshot(cx);
22507 let original_count = self.total_review_comment_count();
22508
22509 // Remove comments with invalid hunk anchors
22510 self.stored_review_comments
22511 .retain(|(hunk_key, _)| hunk_key.hunk_start_anchor.is_valid(&snapshot));
22512
22513 // Also clean up individual comments with invalid anchor ranges
22514 for (_, comments) in &mut self.stored_review_comments {
22515 comments.retain(|comment| {
22516 comment.range.start.is_valid(&snapshot) && comment.range.end.is_valid(&snapshot)
22517 });
22518 }
22519
22520 // Remove empty hunk entries
22521 self.stored_review_comments
22522 .retain(|(_, comments)| !comments.is_empty());
22523
22524 let new_count = self.total_review_comment_count();
22525 if new_count != original_count {
22526 cx.emit(EditorEvent::ReviewCommentsChanged {
22527 total_count: new_count,
22528 });
22529 cx.notify();
22530 }
22531 }
22532
22533 /// Toggles the expanded state of the comments section in the overlay.
22534 pub fn toggle_review_comments_expanded(
22535 &mut self,
22536 _: &ToggleReviewCommentsExpanded,
22537 window: &mut Window,
22538 cx: &mut Context<Self>,
22539 ) {
22540 // Find the overlay that currently has focus, or use the first one
22541 let overlay_info = self.diff_review_overlays.iter_mut().find_map(|overlay| {
22542 if overlay.prompt_editor.focus_handle(cx).is_focused(window) {
22543 overlay.comments_expanded = !overlay.comments_expanded;
22544 Some(overlay.hunk_key.clone())
22545 } else {
22546 None
22547 }
22548 });
22549
22550 // If no focused overlay found, toggle the first one
22551 let hunk_key = overlay_info.or_else(|| {
22552 self.diff_review_overlays.first_mut().map(|overlay| {
22553 overlay.comments_expanded = !overlay.comments_expanded;
22554 overlay.hunk_key.clone()
22555 })
22556 });
22557
22558 if let Some(hunk_key) = hunk_key {
22559 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22560 cx.notify();
22561 }
22562 }
22563
22564 /// Handles the EditReviewComment action - sets a comment into editing mode.
22565 pub fn edit_review_comment(
22566 &mut self,
22567 action: &EditReviewComment,
22568 window: &mut Window,
22569 cx: &mut Context<Self>,
22570 ) {
22571 let comment_id = action.id;
22572
22573 // Set the comment to editing mode
22574 self.set_comment_editing(comment_id, true, cx);
22575
22576 // Find the overlay that contains this comment and create an inline editor if needed
22577 // First, find which hunk this comment belongs to
22578 let hunk_key = self
22579 .stored_review_comments
22580 .iter()
22581 .find_map(|(key, comments)| {
22582 if comments.iter().any(|c| c.id == comment_id) {
22583 Some(key.clone())
22584 } else {
22585 None
22586 }
22587 });
22588
22589 let snapshot = self.buffer.read(cx).snapshot(cx);
22590 if let Some(hunk_key) = hunk_key {
22591 if let Some(overlay) = self
22592 .diff_review_overlays
22593 .iter_mut()
22594 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22595 {
22596 if let std::collections::hash_map::Entry::Vacant(entry) =
22597 overlay.inline_edit_editors.entry(comment_id)
22598 {
22599 // Find the comment text
22600 let comment_text = self
22601 .stored_review_comments
22602 .iter()
22603 .flat_map(|(_, comments)| comments)
22604 .find(|c| c.id == comment_id)
22605 .map(|c| c.comment.clone())
22606 .unwrap_or_default();
22607
22608 // Create inline editor
22609 let parent_editor = cx.entity().downgrade();
22610 let inline_editor = cx.new(|cx| {
22611 let mut editor = Editor::single_line(window, cx);
22612 editor.set_text(&*comment_text, window, cx);
22613 // Select all text for easy replacement
22614 editor.select_all(&crate::actions::SelectAll, window, cx);
22615 editor
22616 });
22617
22618 // Register the Newline action to confirm the edit
22619 let subscription = inline_editor.update(cx, |inline_editor, _cx| {
22620 inline_editor.register_action({
22621 let parent_editor = parent_editor.clone();
22622 move |_: &crate::actions::Newline, window, cx| {
22623 if let Some(editor) = parent_editor.upgrade() {
22624 editor.update(cx, |editor, cx| {
22625 editor.confirm_edit_review_comment(comment_id, window, cx);
22626 });
22627 }
22628 }
22629 })
22630 });
22631
22632 // Store the subscription to keep the action handler alive
22633 overlay
22634 .inline_edit_subscriptions
22635 .insert(comment_id, subscription);
22636
22637 // Focus the inline editor
22638 let focus_handle = inline_editor.focus_handle(cx);
22639 window.focus(&focus_handle, cx);
22640
22641 entry.insert(inline_editor);
22642 }
22643 }
22644 }
22645
22646 cx.notify();
22647 }
22648
22649 /// Confirms an inline edit of a review comment.
22650 pub fn confirm_edit_review_comment(
22651 &mut self,
22652 comment_id: usize,
22653 _window: &mut Window,
22654 cx: &mut Context<Self>,
22655 ) {
22656 // Get the new text from the inline editor
22657 // Find the overlay containing this comment's inline editor
22658 let snapshot = self.buffer.read(cx).snapshot(cx);
22659 let hunk_key = self
22660 .stored_review_comments
22661 .iter()
22662 .find_map(|(key, comments)| {
22663 if comments.iter().any(|c| c.id == comment_id) {
22664 Some(key.clone())
22665 } else {
22666 None
22667 }
22668 });
22669
22670 let new_text = hunk_key
22671 .as_ref()
22672 .and_then(|hunk_key| {
22673 self.diff_review_overlays
22674 .iter()
22675 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
22676 })
22677 .as_ref()
22678 .and_then(|overlay| overlay.inline_edit_editors.get(&comment_id))
22679 .map(|editor| editor.read(cx).text(cx).trim().to_string());
22680
22681 if let Some(new_text) = new_text {
22682 if !new_text.is_empty() {
22683 self.update_review_comment(comment_id, new_text, cx);
22684 }
22685 }
22686
22687 // Remove the inline editor and its subscription
22688 if let Some(hunk_key) = hunk_key {
22689 if let Some(overlay) = self
22690 .diff_review_overlays
22691 .iter_mut()
22692 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22693 {
22694 overlay.inline_edit_editors.remove(&comment_id);
22695 overlay.inline_edit_subscriptions.remove(&comment_id);
22696 }
22697 }
22698
22699 // Clear editing state
22700 self.set_comment_editing(comment_id, false, cx);
22701 }
22702
22703 /// Cancels an inline edit of a review comment.
22704 pub fn cancel_edit_review_comment(
22705 &mut self,
22706 comment_id: usize,
22707 _window: &mut Window,
22708 cx: &mut Context<Self>,
22709 ) {
22710 // Find which hunk this comment belongs to
22711 let hunk_key = self
22712 .stored_review_comments
22713 .iter()
22714 .find_map(|(key, comments)| {
22715 if comments.iter().any(|c| c.id == comment_id) {
22716 Some(key.clone())
22717 } else {
22718 None
22719 }
22720 });
22721
22722 // Remove the inline editor and its subscription
22723 if let Some(hunk_key) = hunk_key {
22724 let snapshot = self.buffer.read(cx).snapshot(cx);
22725 if let Some(overlay) = self
22726 .diff_review_overlays
22727 .iter_mut()
22728 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22729 {
22730 overlay.inline_edit_editors.remove(&comment_id);
22731 overlay.inline_edit_subscriptions.remove(&comment_id);
22732 }
22733 }
22734
22735 // Clear editing state
22736 self.set_comment_editing(comment_id, false, cx);
22737 }
22738
22739 /// Action handler for ConfirmEditReviewComment.
22740 pub fn confirm_edit_review_comment_action(
22741 &mut self,
22742 action: &ConfirmEditReviewComment,
22743 window: &mut Window,
22744 cx: &mut Context<Self>,
22745 ) {
22746 self.confirm_edit_review_comment(action.id, window, cx);
22747 }
22748
22749 /// Action handler for CancelEditReviewComment.
22750 pub fn cancel_edit_review_comment_action(
22751 &mut self,
22752 action: &CancelEditReviewComment,
22753 window: &mut Window,
22754 cx: &mut Context<Self>,
22755 ) {
22756 self.cancel_edit_review_comment(action.id, window, cx);
22757 }
22758
22759 /// Handles the DeleteReviewComment action - removes a comment.
22760 pub fn delete_review_comment(
22761 &mut self,
22762 action: &DeleteReviewComment,
22763 window: &mut Window,
22764 cx: &mut Context<Self>,
22765 ) {
22766 // Get the hunk key before removing the comment
22767 // Find the hunk key from the comment itself
22768 let comment_id = action.id;
22769 let hunk_key = self
22770 .stored_review_comments
22771 .iter()
22772 .find_map(|(key, comments)| {
22773 if comments.iter().any(|c| c.id == comment_id) {
22774 Some(key.clone())
22775 } else {
22776 None
22777 }
22778 });
22779
22780 // Also get it from the overlay for refresh purposes
22781 let overlay_hunk_key = self
22782 .diff_review_overlays
22783 .first()
22784 .map(|o| o.hunk_key.clone());
22785
22786 self.remove_review_comment(action.id, cx);
22787
22788 // Refresh the overlay height after removing a comment
22789 if let Some(hunk_key) = hunk_key.or(overlay_hunk_key) {
22790 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22791 }
22792 }
22793
22794 fn render_diff_review_overlay(
22795 prompt_editor: &Entity<Editor>,
22796 hunk_key: &DiffHunkKey,
22797 editor_handle: &WeakEntity<Editor>,
22798 cx: &mut BlockContext,
22799 ) -> AnyElement {
22800 fn format_line_ranges(ranges: &[(u32, u32)]) -> Option<String> {
22801 if ranges.is_empty() {
22802 return None;
22803 }
22804 let formatted: Vec<String> = ranges
22805 .iter()
22806 .map(|(start, end)| {
22807 let start_line = start + 1;
22808 let end_line = end + 1;
22809 if start_line == end_line {
22810 format!("Line {start_line}")
22811 } else {
22812 format!("Lines {start_line}-{end_line}")
22813 }
22814 })
22815 .collect();
22816 // Don't show label for single line in single excerpt
22817 if ranges.len() == 1 && ranges[0].0 == ranges[0].1 {
22818 return None;
22819 }
22820 Some(formatted.join(" ⋯ "))
22821 }
22822
22823 let theme = cx.theme();
22824 let colors = theme.colors();
22825
22826 let (comments, comments_expanded, inline_editors, user_avatar_uri, line_ranges) =
22827 editor_handle
22828 .upgrade()
22829 .map(|editor| {
22830 let editor = editor.read(cx);
22831 let snapshot = editor.buffer().read(cx).snapshot(cx);
22832 let comments = editor.comments_for_hunk(hunk_key, &snapshot).to_vec();
22833 let (expanded, editors, avatar_uri, line_ranges) = editor
22834 .diff_review_overlays
22835 .iter()
22836 .find(|overlay| {
22837 Editor::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot)
22838 })
22839 .map(|o| {
22840 let start_point = o.anchor_range.start.to_point(&snapshot);
22841 let end_point = o.anchor_range.end.to_point(&snapshot);
22842 // Get line ranges per excerpt to detect discontinuities
22843 let buffer_ranges =
22844 snapshot.range_to_buffer_ranges(start_point..end_point);
22845 let ranges: Vec<(u32, u32)> = buffer_ranges
22846 .iter()
22847 .map(|(buffer_snapshot, range, _)| {
22848 let start = buffer_snapshot.offset_to_point(range.start.0).row;
22849 let end = buffer_snapshot.offset_to_point(range.end.0).row;
22850 (start, end)
22851 })
22852 .collect();
22853 (
22854 o.comments_expanded,
22855 o.inline_edit_editors.clone(),
22856 o.user_avatar_uri.clone(),
22857 if ranges.is_empty() {
22858 None
22859 } else {
22860 Some(ranges)
22861 },
22862 )
22863 })
22864 .unwrap_or((true, HashMap::default(), None, None));
22865 (comments, expanded, editors, avatar_uri, line_ranges)
22866 })
22867 .unwrap_or((Vec::new(), true, HashMap::default(), None, None));
22868
22869 let comment_count = comments.len();
22870 let avatar_size = px(20.);
22871 let action_icon_size = IconSize::XSmall;
22872
22873 v_flex()
22874 .w_full()
22875 .bg(colors.editor_background)
22876 .border_b_1()
22877 .border_color(colors.border)
22878 .px_2()
22879 .pb_2()
22880 .gap_2()
22881 // Line range indicator (only shown for multi-line selections or multiple excerpts)
22882 .when_some(line_ranges, |el, ranges| {
22883 let label = format_line_ranges(&ranges);
22884 if let Some(label) = label {
22885 el.child(
22886 h_flex()
22887 .w_full()
22888 .px_2()
22889 .child(Label::new(label).size(LabelSize::Small).color(Color::Muted)),
22890 )
22891 } else {
22892 el
22893 }
22894 })
22895 // Top row: editable input with user's avatar
22896 .child(
22897 h_flex()
22898 .w_full()
22899 .items_center()
22900 .gap_2()
22901 .px_2()
22902 .py_1p5()
22903 .rounded_md()
22904 .bg(colors.surface_background)
22905 .child(
22906 div()
22907 .size(avatar_size)
22908 .flex_shrink_0()
22909 .rounded_full()
22910 .overflow_hidden()
22911 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22912 Avatar::new(avatar_uri.clone())
22913 .size(avatar_size)
22914 .into_any_element()
22915 } else {
22916 Icon::new(IconName::Person)
22917 .size(IconSize::Small)
22918 .color(ui::Color::Muted)
22919 .into_any_element()
22920 }),
22921 )
22922 .child(
22923 div()
22924 .flex_1()
22925 .border_1()
22926 .border_color(colors.border)
22927 .rounded_md()
22928 .bg(colors.editor_background)
22929 .px_2()
22930 .py_1()
22931 .child(prompt_editor.clone()),
22932 )
22933 .child(
22934 h_flex()
22935 .flex_shrink_0()
22936 .gap_1()
22937 .child(
22938 IconButton::new("diff-review-close", IconName::Close)
22939 .icon_color(ui::Color::Muted)
22940 .icon_size(action_icon_size)
22941 .tooltip(Tooltip::text("Close"))
22942 .on_click(|_, window, cx| {
22943 window
22944 .dispatch_action(Box::new(crate::actions::Cancel), cx);
22945 }),
22946 )
22947 .child(
22948 IconButton::new("diff-review-add", IconName::Return)
22949 .icon_color(ui::Color::Muted)
22950 .icon_size(action_icon_size)
22951 .tooltip(Tooltip::text("Add comment"))
22952 .on_click(|_, window, cx| {
22953 window.dispatch_action(
22954 Box::new(crate::actions::SubmitDiffReviewComment),
22955 cx,
22956 );
22957 }),
22958 ),
22959 ),
22960 )
22961 // Expandable comments section (only shown when there are comments)
22962 .when(comment_count > 0, |el| {
22963 el.child(Self::render_comments_section(
22964 comments,
22965 comments_expanded,
22966 inline_editors,
22967 user_avatar_uri,
22968 avatar_size,
22969 action_icon_size,
22970 colors,
22971 ))
22972 })
22973 .into_any_element()
22974 }
22975
22976 fn render_comments_section(
22977 comments: Vec<StoredReviewComment>,
22978 expanded: bool,
22979 inline_editors: HashMap<usize, Entity<Editor>>,
22980 user_avatar_uri: Option<SharedUri>,
22981 avatar_size: Pixels,
22982 action_icon_size: IconSize,
22983 colors: &theme::ThemeColors,
22984 ) -> impl IntoElement {
22985 let comment_count = comments.len();
22986
22987 v_flex()
22988 .w_full()
22989 .gap_1()
22990 // Header with expand/collapse toggle
22991 .child(
22992 h_flex()
22993 .id("review-comments-header")
22994 .w_full()
22995 .items_center()
22996 .gap_1()
22997 .px_2()
22998 .py_1()
22999 .cursor_pointer()
23000 .rounded_md()
23001 .hover(|style| style.bg(colors.ghost_element_hover))
23002 .on_click(|_, window: &mut Window, cx| {
23003 window.dispatch_action(
23004 Box::new(crate::actions::ToggleReviewCommentsExpanded),
23005 cx,
23006 );
23007 })
23008 .child(
23009 Icon::new(if expanded {
23010 IconName::ChevronDown
23011 } else {
23012 IconName::ChevronRight
23013 })
23014 .size(IconSize::Small)
23015 .color(ui::Color::Muted),
23016 )
23017 .child(
23018 Label::new(format!(
23019 "{} Comment{}",
23020 comment_count,
23021 if comment_count == 1 { "" } else { "s" }
23022 ))
23023 .size(LabelSize::Small)
23024 .color(Color::Muted),
23025 ),
23026 )
23027 // Comments list (when expanded)
23028 .when(expanded, |el| {
23029 el.children(comments.into_iter().map(|comment| {
23030 let inline_editor = inline_editors.get(&comment.id).cloned();
23031 Self::render_comment_row(
23032 comment,
23033 inline_editor,
23034 user_avatar_uri.clone(),
23035 avatar_size,
23036 action_icon_size,
23037 colors,
23038 )
23039 }))
23040 })
23041 }
23042
23043 fn render_comment_row(
23044 comment: StoredReviewComment,
23045 inline_editor: Option<Entity<Editor>>,
23046 user_avatar_uri: Option<SharedUri>,
23047 avatar_size: Pixels,
23048 action_icon_size: IconSize,
23049 colors: &theme::ThemeColors,
23050 ) -> impl IntoElement {
23051 let comment_id = comment.id;
23052 let is_editing = inline_editor.is_some();
23053
23054 h_flex()
23055 .w_full()
23056 .items_center()
23057 .gap_2()
23058 .px_2()
23059 .py_1p5()
23060 .rounded_md()
23061 .bg(colors.surface_background)
23062 .child(
23063 div()
23064 .size(avatar_size)
23065 .flex_shrink_0()
23066 .rounded_full()
23067 .overflow_hidden()
23068 .child(if let Some(ref avatar_uri) = user_avatar_uri {
23069 Avatar::new(avatar_uri.clone())
23070 .size(avatar_size)
23071 .into_any_element()
23072 } else {
23073 Icon::new(IconName::Person)
23074 .size(IconSize::Small)
23075 .color(ui::Color::Muted)
23076 .into_any_element()
23077 }),
23078 )
23079 .child(if let Some(editor) = inline_editor {
23080 // Inline edit mode: show an editable text field
23081 div()
23082 .flex_1()
23083 .border_1()
23084 .border_color(colors.border)
23085 .rounded_md()
23086 .bg(colors.editor_background)
23087 .px_2()
23088 .py_1()
23089 .child(editor)
23090 .into_any_element()
23091 } else {
23092 // Display mode: show the comment text
23093 div()
23094 .flex_1()
23095 .text_sm()
23096 .text_color(colors.text)
23097 .child(comment.comment)
23098 .into_any_element()
23099 })
23100 .child(if is_editing {
23101 // Editing mode: show close and confirm buttons
23102 h_flex()
23103 .gap_1()
23104 .child(
23105 IconButton::new(
23106 format!("diff-review-cancel-edit-{comment_id}"),
23107 IconName::Close,
23108 )
23109 .icon_color(ui::Color::Muted)
23110 .icon_size(action_icon_size)
23111 .tooltip(Tooltip::text("Cancel"))
23112 .on_click(move |_, window, cx| {
23113 window.dispatch_action(
23114 Box::new(crate::actions::CancelEditReviewComment {
23115 id: comment_id,
23116 }),
23117 cx,
23118 );
23119 }),
23120 )
23121 .child(
23122 IconButton::new(
23123 format!("diff-review-confirm-edit-{comment_id}"),
23124 IconName::Return,
23125 )
23126 .icon_color(ui::Color::Muted)
23127 .icon_size(action_icon_size)
23128 .tooltip(Tooltip::text("Confirm"))
23129 .on_click(move |_, window, cx| {
23130 window.dispatch_action(
23131 Box::new(crate::actions::ConfirmEditReviewComment {
23132 id: comment_id,
23133 }),
23134 cx,
23135 );
23136 }),
23137 )
23138 .into_any_element()
23139 } else {
23140 // Display mode: no action buttons for now (edit/delete not yet implemented)
23141 gpui::Empty.into_any_element()
23142 })
23143 }
23144
23145 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
23146 if self.display_map.read(cx).masked != masked {
23147 self.display_map.update(cx, |map, _| map.masked = masked);
23148 }
23149 cx.notify()
23150 }
23151
23152 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
23153 self.show_wrap_guides = Some(show_wrap_guides);
23154 cx.notify();
23155 }
23156
23157 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
23158 self.show_indent_guides = Some(show_indent_guides);
23159 cx.notify();
23160 }
23161
23162 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
23163 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
23164 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
23165 && let Some(dir) = file.abs_path(cx).parent()
23166 {
23167 return Some(dir.to_owned());
23168 }
23169 }
23170
23171 None
23172 }
23173
23174 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
23175 self.active_buffer(cx)?
23176 .read(cx)
23177 .file()
23178 .and_then(|f| f.as_local())
23179 }
23180
23181 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
23182 self.active_buffer(cx).and_then(|buffer| {
23183 let buffer = buffer.read(cx);
23184 if let Some(project_path) = buffer.project_path(cx) {
23185 let project = self.project()?.read(cx);
23186 project.absolute_path(&project_path, cx)
23187 } else {
23188 buffer
23189 .file()
23190 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
23191 }
23192 })
23193 }
23194
23195 pub fn reveal_in_finder(
23196 &mut self,
23197 _: &RevealInFileManager,
23198 _window: &mut Window,
23199 cx: &mut Context<Self>,
23200 ) {
23201 if let Some(path) = self.target_file_abs_path(cx) {
23202 if let Some(project) = self.project() {
23203 project.update(cx, |project, cx| project.reveal_path(&path, cx));
23204 } else {
23205 cx.reveal_path(&path);
23206 }
23207 }
23208 }
23209
23210 pub fn copy_path(
23211 &mut self,
23212 _: &zed_actions::workspace::CopyPath,
23213 _window: &mut Window,
23214 cx: &mut Context<Self>,
23215 ) {
23216 if let Some(path) = self.target_file_abs_path(cx)
23217 && let Some(path) = path.to_str()
23218 {
23219 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
23220 } else {
23221 cx.propagate();
23222 }
23223 }
23224
23225 pub fn copy_relative_path(
23226 &mut self,
23227 _: &zed_actions::workspace::CopyRelativePath,
23228 _window: &mut Window,
23229 cx: &mut Context<Self>,
23230 ) {
23231 if let Some(path) = self.active_buffer(cx).and_then(|buffer| {
23232 let project = self.project()?.read(cx);
23233 let path = buffer.read(cx).file()?.path();
23234 let path = path.display(project.path_style(cx));
23235 Some(path)
23236 }) {
23237 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
23238 } else {
23239 cx.propagate();
23240 }
23241 }
23242
23243 /// Returns the project path for the editor's buffer, if any buffer is
23244 /// opened in the editor.
23245 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
23246 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
23247 buffer.read(cx).project_path(cx)
23248 } else {
23249 None
23250 }
23251 }
23252
23253 // Returns true if the editor handled a go-to-line request
23254 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
23255 maybe!({
23256 let breakpoint_store = self.breakpoint_store.as_ref()?;
23257
23258 let (active_stack_frame, debug_line_pane_id) = {
23259 let store = breakpoint_store.read(cx);
23260 let active_stack_frame = store.active_position().cloned();
23261 let debug_line_pane_id = store.active_debug_line_pane_id();
23262 (active_stack_frame, debug_line_pane_id)
23263 };
23264
23265 let Some(active_stack_frame) = active_stack_frame else {
23266 self.clear_row_highlights::<ActiveDebugLine>();
23267 return None;
23268 };
23269
23270 if let Some(debug_line_pane_id) = debug_line_pane_id {
23271 if let Some(workspace) = self
23272 .workspace
23273 .as_ref()
23274 .and_then(|(workspace, _)| workspace.upgrade())
23275 {
23276 let editor_pane_id = workspace
23277 .read(cx)
23278 .pane_for_item_id(cx.entity_id())
23279 .map(|pane| pane.entity_id());
23280
23281 if editor_pane_id.is_some_and(|id| id != debug_line_pane_id) {
23282 self.clear_row_highlights::<ActiveDebugLine>();
23283 return None;
23284 }
23285 }
23286 }
23287
23288 let position = active_stack_frame.position;
23289
23290 let snapshot = self.buffer.read(cx).snapshot(cx);
23291 let multibuffer_anchor = snapshot.anchor_in_excerpt(position)?;
23292
23293 self.clear_row_highlights::<ActiveDebugLine>();
23294
23295 self.go_to_line::<ActiveDebugLine>(
23296 multibuffer_anchor,
23297 Some(cx.theme().colors().editor_debugger_active_line_background),
23298 window,
23299 cx,
23300 );
23301
23302 cx.notify();
23303
23304 Some(())
23305 })
23306 .is_some()
23307 }
23308
23309 pub fn copy_file_name_without_extension(
23310 &mut self,
23311 _: &CopyFileNameWithoutExtension,
23312 _: &mut Window,
23313 cx: &mut Context<Self>,
23314 ) {
23315 if let Some(file_stem) = self.active_buffer(cx).and_then(|buffer| {
23316 let file = buffer.read(cx).file()?;
23317 file.path().file_stem()
23318 }) {
23319 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
23320 }
23321 }
23322
23323 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
23324 if let Some(file_name) = self.active_buffer(cx).and_then(|buffer| {
23325 let file = buffer.read(cx).file()?;
23326 Some(file.file_name(cx))
23327 }) {
23328 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
23329 }
23330 }
23331
23332 pub fn toggle_git_blame(
23333 &mut self,
23334 _: &::git::Blame,
23335 window: &mut Window,
23336 cx: &mut Context<Self>,
23337 ) {
23338 self.show_git_blame_gutter = !self.show_git_blame_gutter;
23339
23340 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
23341 self.start_git_blame(true, window, cx);
23342 }
23343
23344 cx.notify();
23345 }
23346
23347 pub fn toggle_git_blame_inline(
23348 &mut self,
23349 _: &ToggleGitBlameInline,
23350 window: &mut Window,
23351 cx: &mut Context<Self>,
23352 ) {
23353 self.toggle_git_blame_inline_internal(true, window, cx);
23354 cx.notify();
23355 }
23356
23357 pub fn open_git_blame_commit(
23358 &mut self,
23359 _: &OpenGitBlameCommit,
23360 window: &mut Window,
23361 cx: &mut Context<Self>,
23362 ) {
23363 self.open_git_blame_commit_internal(window, cx);
23364 }
23365
23366 fn open_git_blame_commit_internal(
23367 &mut self,
23368 window: &mut Window,
23369 cx: &mut Context<Self>,
23370 ) -> Option<()> {
23371 let blame = self.blame.as_ref()?;
23372 let snapshot = self.snapshot(window, cx);
23373 let cursor = self
23374 .selections
23375 .newest::<Point>(&snapshot.display_snapshot)
23376 .head();
23377 let (buffer, point) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
23378 let (_, blame_entry) = blame
23379 .update(cx, |blame, cx| {
23380 blame
23381 .blame_for_rows(
23382 &[RowInfo {
23383 buffer_id: Some(buffer.remote_id()),
23384 buffer_row: Some(point.row),
23385 ..Default::default()
23386 }],
23387 cx,
23388 )
23389 .next()
23390 })
23391 .flatten()?;
23392 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23393 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
23394 let workspace = self.workspace()?.downgrade();
23395 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
23396 None
23397 }
23398
23399 pub fn git_blame_inline_enabled(&self) -> bool {
23400 self.git_blame_inline_enabled
23401 }
23402
23403 pub fn toggle_selection_menu(
23404 &mut self,
23405 _: &ToggleSelectionMenu,
23406 _: &mut Window,
23407 cx: &mut Context<Self>,
23408 ) {
23409 self.show_selection_menu = self
23410 .show_selection_menu
23411 .map(|show_selections_menu| !show_selections_menu)
23412 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
23413
23414 cx.notify();
23415 }
23416
23417 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
23418 self.show_selection_menu
23419 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
23420 }
23421
23422 fn start_git_blame(
23423 &mut self,
23424 user_triggered: bool,
23425 window: &mut Window,
23426 cx: &mut Context<Self>,
23427 ) {
23428 if let Some(project) = self.project() {
23429 if let Some(buffer) = self.buffer().read(cx).as_singleton()
23430 && buffer.read(cx).file().is_none()
23431 {
23432 return;
23433 }
23434
23435 let focused = self.focus_handle(cx).contains_focused(window, cx);
23436
23437 let project = project.clone();
23438 let blame = cx
23439 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
23440 self.blame_subscription =
23441 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
23442 self.blame = Some(blame);
23443 }
23444 }
23445
23446 fn toggle_git_blame_inline_internal(
23447 &mut self,
23448 user_triggered: bool,
23449 window: &mut Window,
23450 cx: &mut Context<Self>,
23451 ) {
23452 if self.git_blame_inline_enabled {
23453 self.git_blame_inline_enabled = false;
23454 self.show_git_blame_inline = false;
23455 self.show_git_blame_inline_delay_task.take();
23456 } else {
23457 self.git_blame_inline_enabled = true;
23458 self.start_git_blame_inline(user_triggered, window, cx);
23459 }
23460
23461 cx.notify();
23462 }
23463
23464 fn start_git_blame_inline(
23465 &mut self,
23466 user_triggered: bool,
23467 window: &mut Window,
23468 cx: &mut Context<Self>,
23469 ) {
23470 self.start_git_blame(user_triggered, window, cx);
23471
23472 if ProjectSettings::get_global(cx)
23473 .git
23474 .inline_blame_delay()
23475 .is_some()
23476 {
23477 self.start_inline_blame_timer(window, cx);
23478 } else {
23479 self.show_git_blame_inline = true
23480 }
23481 }
23482
23483 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
23484 self.blame.as_ref()
23485 }
23486
23487 pub fn show_git_blame_gutter(&self) -> bool {
23488 self.show_git_blame_gutter
23489 }
23490
23491 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
23492 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
23493 }
23494
23495 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
23496 self.show_git_blame_inline
23497 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
23498 && !self.newest_selection_head_on_empty_line(cx)
23499 && self.has_blame_entries(cx)
23500 }
23501
23502 fn has_blame_entries(&self, cx: &App) -> bool {
23503 self.blame()
23504 .is_some_and(|blame| blame.read(cx).has_generated_entries())
23505 }
23506
23507 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
23508 let cursor_anchor = self.selections.newest_anchor().head();
23509
23510 let snapshot = self.buffer.read(cx).snapshot(cx);
23511 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
23512
23513 snapshot.line_len(buffer_row) == 0
23514 }
23515
23516 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
23517 let buffer_and_selection = maybe!({
23518 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
23519 let selection_range = selection.range();
23520
23521 let multi_buffer = self.buffer().read(cx);
23522 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
23523 let buffer_ranges = multi_buffer_snapshot
23524 .range_to_buffer_ranges(selection_range.start..selection_range.end);
23525
23526 let (buffer_snapshot, range, _) = if selection.reversed {
23527 buffer_ranges.first()
23528 } else {
23529 buffer_ranges.last()
23530 }?;
23531
23532 let buffer_range = range.to_point(buffer_snapshot);
23533 let buffer = multi_buffer.buffer(buffer_snapshot.remote_id()).unwrap();
23534
23535 let Some(buffer_diff) = multi_buffer.diff_for(buffer_snapshot.remote_id()) else {
23536 return Some((buffer, buffer_range.start.row..buffer_range.end.row));
23537 };
23538
23539 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
23540 let start = buffer_diff_snapshot
23541 .buffer_point_to_base_text_point(buffer_range.start, &buffer_snapshot);
23542 let end = buffer_diff_snapshot
23543 .buffer_point_to_base_text_point(buffer_range.end, &buffer_snapshot);
23544
23545 Some((buffer, start.row..end.row))
23546 });
23547
23548 let Some((buffer, selection)) = buffer_and_selection else {
23549 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
23550 };
23551
23552 let Some(project) = self.project() else {
23553 return Task::ready(Err(anyhow!("editor does not have project")));
23554 };
23555
23556 project.update(cx, |project, cx| {
23557 project.get_permalink_to_line(&buffer, selection, cx)
23558 })
23559 }
23560
23561 pub fn copy_permalink_to_line(
23562 &mut self,
23563 _: &CopyPermalinkToLine,
23564 window: &mut Window,
23565 cx: &mut Context<Self>,
23566 ) {
23567 let permalink_task = self.get_permalink_to_line(cx);
23568 let workspace = self.workspace();
23569
23570 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
23571 Ok(permalink) => {
23572 cx.update(|_, cx| {
23573 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
23574 })
23575 .ok();
23576 }
23577 Err(err) => {
23578 let message = format!("Failed to copy permalink: {err}");
23579
23580 anyhow::Result::<()>::Err(err).log_err();
23581
23582 if let Some(workspace) = workspace {
23583 workspace
23584 .update_in(cx, |workspace, _, cx| {
23585 struct CopyPermalinkToLine;
23586
23587 workspace.show_toast(
23588 Toast::new(
23589 NotificationId::unique::<CopyPermalinkToLine>(),
23590 message,
23591 ),
23592 cx,
23593 )
23594 })
23595 .ok();
23596 }
23597 }
23598 })
23599 .detach();
23600 }
23601
23602 pub fn copy_file_location(
23603 &mut self,
23604 _: &CopyFileLocation,
23605 _: &mut Window,
23606 cx: &mut Context<Self>,
23607 ) {
23608 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
23609
23610 let start_line = selection.start.row + 1;
23611 let end_line = selection.end.row + 1;
23612
23613 let end_line = if selection.end.column == 0 && end_line > start_line {
23614 end_line - 1
23615 } else {
23616 end_line
23617 };
23618
23619 if let Some(file_location) = self.active_buffer(cx).and_then(|buffer| {
23620 let project = self.project()?.read(cx);
23621 let file = buffer.read(cx).file()?;
23622 let path = file.path().display(project.path_style(cx));
23623
23624 let location = if start_line == end_line {
23625 format!("{path}:{start_line}")
23626 } else {
23627 format!("{path}:{start_line}-{end_line}")
23628 };
23629 Some(location)
23630 }) {
23631 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
23632 }
23633 }
23634
23635 pub fn open_permalink_to_line(
23636 &mut self,
23637 _: &OpenPermalinkToLine,
23638 window: &mut Window,
23639 cx: &mut Context<Self>,
23640 ) {
23641 let permalink_task = self.get_permalink_to_line(cx);
23642 let workspace = self.workspace();
23643
23644 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
23645 Ok(permalink) => {
23646 cx.update(|_, cx| {
23647 cx.open_url(permalink.as_ref());
23648 })
23649 .ok();
23650 }
23651 Err(err) => {
23652 let message = format!("Failed to open permalink: {err}");
23653
23654 anyhow::Result::<()>::Err(err).log_err();
23655
23656 if let Some(workspace) = workspace {
23657 workspace.update(cx, |workspace, cx| {
23658 struct OpenPermalinkToLine;
23659
23660 workspace.show_toast(
23661 Toast::new(NotificationId::unique::<OpenPermalinkToLine>(), message),
23662 cx,
23663 )
23664 });
23665 }
23666 }
23667 })
23668 .detach();
23669 }
23670
23671 pub fn insert_uuid_v4(
23672 &mut self,
23673 _: &InsertUuidV4,
23674 window: &mut Window,
23675 cx: &mut Context<Self>,
23676 ) {
23677 self.insert_uuid(UuidVersion::V4, window, cx);
23678 }
23679
23680 pub fn insert_uuid_v7(
23681 &mut self,
23682 _: &InsertUuidV7,
23683 window: &mut Window,
23684 cx: &mut Context<Self>,
23685 ) {
23686 self.insert_uuid(UuidVersion::V7, window, cx);
23687 }
23688
23689 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
23690 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
23691 self.transact(window, cx, |this, window, cx| {
23692 let edits = this
23693 .selections
23694 .all::<Point>(&this.display_snapshot(cx))
23695 .into_iter()
23696 .map(|selection| {
23697 let uuid = match version {
23698 UuidVersion::V4 => uuid::Uuid::new_v4(),
23699 UuidVersion::V7 => uuid::Uuid::now_v7(),
23700 };
23701
23702 (selection.range(), uuid.to_string())
23703 });
23704 this.edit(edits, cx);
23705 this.refresh_edit_prediction(true, false, window, cx);
23706 });
23707 }
23708
23709 pub fn open_selections_in_multibuffer(
23710 &mut self,
23711 _: &OpenSelectionsInMultibuffer,
23712 window: &mut Window,
23713 cx: &mut Context<Self>,
23714 ) {
23715 let multibuffer = self.buffer.read(cx);
23716
23717 let Some(buffer) = multibuffer.as_singleton() else {
23718 return;
23719 };
23720 let buffer_snapshot = buffer.read(cx).snapshot();
23721
23722 let Some(workspace) = self.workspace() else {
23723 return;
23724 };
23725
23726 let title = multibuffer.title(cx).to_string();
23727
23728 let locations = self
23729 .selections
23730 .all_anchors(&self.display_snapshot(cx))
23731 .iter()
23732 .map(|selection| {
23733 (
23734 buffer.clone(),
23735 (selection.start.text_anchor_in(&buffer_snapshot)
23736 ..selection.end.text_anchor_in(&buffer_snapshot))
23737 .to_point(buffer.read(cx)),
23738 )
23739 })
23740 .into_group_map();
23741
23742 cx.spawn_in(window, async move |_, cx| {
23743 workspace.update_in(cx, |workspace, window, cx| {
23744 Self::open_locations_in_multibuffer(
23745 workspace,
23746 locations,
23747 format!("Selections for '{title}'"),
23748 false,
23749 false,
23750 MultibufferSelectionMode::All,
23751 window,
23752 cx,
23753 );
23754 })
23755 })
23756 .detach();
23757 }
23758
23759 /// Adds a row highlight for the given range. If a row has multiple highlights, the
23760 /// last highlight added will be used.
23761 ///
23762 /// If the range ends at the beginning of a line, then that line will not be highlighted.
23763 pub fn highlight_rows<T: 'static>(
23764 &mut self,
23765 range: Range<Anchor>,
23766 color: Hsla,
23767 options: RowHighlightOptions,
23768 cx: &mut Context<Self>,
23769 ) {
23770 let snapshot = self.buffer().read(cx).snapshot(cx);
23771 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23772 let ix = row_highlights.binary_search_by(|highlight| {
23773 Ordering::Equal
23774 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
23775 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
23776 });
23777
23778 if let Err(mut ix) = ix {
23779 let index = post_inc(&mut self.highlight_order);
23780
23781 // If this range intersects with the preceding highlight, then merge it with
23782 // the preceding highlight. Otherwise insert a new highlight.
23783 let mut merged = false;
23784 if ix > 0 {
23785 let prev_highlight = &mut row_highlights[ix - 1];
23786 if prev_highlight
23787 .range
23788 .end
23789 .cmp(&range.start, &snapshot)
23790 .is_ge()
23791 {
23792 ix -= 1;
23793 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
23794 prev_highlight.range.end = range.end;
23795 }
23796 merged = true;
23797 prev_highlight.index = index;
23798 prev_highlight.color = color;
23799 prev_highlight.options = options;
23800 }
23801 }
23802
23803 if !merged {
23804 row_highlights.insert(
23805 ix,
23806 RowHighlight {
23807 range,
23808 index,
23809 color,
23810 options,
23811 type_id: TypeId::of::<T>(),
23812 },
23813 );
23814 }
23815
23816 // If any of the following highlights intersect with this one, merge them.
23817 while let Some(next_highlight) = row_highlights.get(ix + 1) {
23818 let highlight = &row_highlights[ix];
23819 if next_highlight
23820 .range
23821 .start
23822 .cmp(&highlight.range.end, &snapshot)
23823 .is_le()
23824 {
23825 if next_highlight
23826 .range
23827 .end
23828 .cmp(&highlight.range.end, &snapshot)
23829 .is_gt()
23830 {
23831 row_highlights[ix].range.end = next_highlight.range.end;
23832 }
23833 row_highlights.remove(ix + 1);
23834 } else {
23835 break;
23836 }
23837 }
23838 }
23839 }
23840
23841 /// Remove any highlighted row ranges of the given type that intersect the
23842 /// given ranges.
23843 pub fn remove_highlighted_rows<T: 'static>(
23844 &mut self,
23845 ranges_to_remove: Vec<Range<Anchor>>,
23846 cx: &mut Context<Self>,
23847 ) {
23848 let snapshot = self.buffer().read(cx).snapshot(cx);
23849 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23850 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23851 row_highlights.retain(|highlight| {
23852 while let Some(range_to_remove) = ranges_to_remove.peek() {
23853 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
23854 Ordering::Less | Ordering::Equal => {
23855 ranges_to_remove.next();
23856 }
23857 Ordering::Greater => {
23858 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
23859 Ordering::Less | Ordering::Equal => {
23860 return false;
23861 }
23862 Ordering::Greater => break,
23863 }
23864 }
23865 }
23866 }
23867
23868 true
23869 })
23870 }
23871
23872 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
23873 pub fn clear_row_highlights<T: 'static>(&mut self) {
23874 self.highlighted_rows.remove(&TypeId::of::<T>());
23875 }
23876
23877 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
23878 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
23879 self.highlighted_rows
23880 .get(&TypeId::of::<T>())
23881 .map_or(&[] as &[_], |vec| vec.as_slice())
23882 .iter()
23883 .map(|highlight| (highlight.range.clone(), highlight.color))
23884 }
23885
23886 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
23887 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
23888 /// Allows to ignore certain kinds of highlights.
23889 pub fn highlighted_display_rows(
23890 &self,
23891 window: &mut Window,
23892 cx: &mut App,
23893 ) -> BTreeMap<DisplayRow, LineHighlight> {
23894 let snapshot = self.snapshot(window, cx);
23895 let mut used_highlight_orders = HashMap::default();
23896 self.highlighted_rows
23897 .iter()
23898 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
23899 .fold(
23900 BTreeMap::<DisplayRow, LineHighlight>::new(),
23901 |mut unique_rows, highlight| {
23902 let start = highlight.range.start.to_display_point(&snapshot);
23903 let end = highlight.range.end.to_display_point(&snapshot);
23904 let start_row = start.row().0;
23905 let end_row = if !highlight.range.end.is_max() && end.column() == 0 {
23906 end.row().0.saturating_sub(1)
23907 } else {
23908 end.row().0
23909 };
23910 for row in start_row..=end_row {
23911 let used_index =
23912 used_highlight_orders.entry(row).or_insert(highlight.index);
23913 if highlight.index >= *used_index {
23914 *used_index = highlight.index;
23915 unique_rows.insert(
23916 DisplayRow(row),
23917 LineHighlight {
23918 include_gutter: highlight.options.include_gutter,
23919 border: None,
23920 background: highlight.color.into(),
23921 type_id: Some(highlight.type_id),
23922 },
23923 );
23924 }
23925 }
23926 unique_rows
23927 },
23928 )
23929 }
23930
23931 pub fn highlighted_display_row_for_autoscroll(
23932 &self,
23933 snapshot: &DisplaySnapshot,
23934 ) -> Option<DisplayRow> {
23935 self.highlighted_rows
23936 .values()
23937 .flat_map(|highlighted_rows| highlighted_rows.iter())
23938 .filter_map(|highlight| {
23939 if highlight.options.autoscroll {
23940 Some(highlight.range.start.to_display_point(snapshot).row())
23941 } else {
23942 None
23943 }
23944 })
23945 .min()
23946 }
23947
23948 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
23949 self.highlight_background(
23950 HighlightKey::SearchWithinRange,
23951 ranges,
23952 |_, colors| colors.colors().editor_document_highlight_read_background,
23953 cx,
23954 )
23955 }
23956
23957 pub fn set_breadcrumb_header(&mut self, new_header: String) {
23958 self.breadcrumb_header = Some(new_header);
23959 }
23960
23961 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
23962 self.clear_background_highlights(HighlightKey::SearchWithinRange, cx);
23963 }
23964
23965 pub fn highlight_background(
23966 &mut self,
23967 key: HighlightKey,
23968 ranges: &[Range<Anchor>],
23969 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
23970 cx: &mut Context<Self>,
23971 ) {
23972 self.background_highlights
23973 .insert(key, (Arc::new(color_fetcher), Arc::from(ranges)));
23974 self.scrollbar_marker_state.dirty = true;
23975 cx.notify();
23976 }
23977
23978 pub fn clear_background_highlights(
23979 &mut self,
23980 key: HighlightKey,
23981 cx: &mut Context<Self>,
23982 ) -> Option<BackgroundHighlight> {
23983 let text_highlights = self.background_highlights.remove(&key)?;
23984 if !text_highlights.1.is_empty() {
23985 self.scrollbar_marker_state.dirty = true;
23986 cx.notify();
23987 }
23988 Some(text_highlights)
23989 }
23990
23991 pub fn highlight_gutter<T: 'static>(
23992 &mut self,
23993 ranges: impl Into<Vec<Range<Anchor>>>,
23994 color_fetcher: fn(&App) -> Hsla,
23995 cx: &mut Context<Self>,
23996 ) {
23997 self.gutter_highlights
23998 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
23999 cx.notify();
24000 }
24001
24002 pub fn clear_gutter_highlights<T: 'static>(
24003 &mut self,
24004 cx: &mut Context<Self>,
24005 ) -> Option<GutterHighlight> {
24006 cx.notify();
24007 self.gutter_highlights.remove(&TypeId::of::<T>())
24008 }
24009
24010 pub fn insert_gutter_highlight<T: 'static>(
24011 &mut self,
24012 range: Range<Anchor>,
24013 color_fetcher: fn(&App) -> Hsla,
24014 cx: &mut Context<Self>,
24015 ) {
24016 let snapshot = self.buffer().read(cx).snapshot(cx);
24017 let mut highlights = self
24018 .gutter_highlights
24019 .remove(&TypeId::of::<T>())
24020 .map(|(_, highlights)| highlights)
24021 .unwrap_or_default();
24022 let ix = highlights.binary_search_by(|highlight| {
24023 Ordering::Equal
24024 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
24025 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
24026 });
24027 if let Err(ix) = ix {
24028 highlights.insert(ix, range);
24029 }
24030 self.gutter_highlights
24031 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
24032 }
24033
24034 pub fn remove_gutter_highlights<T: 'static>(
24035 &mut self,
24036 ranges_to_remove: Vec<Range<Anchor>>,
24037 cx: &mut Context<Self>,
24038 ) {
24039 let snapshot = self.buffer().read(cx).snapshot(cx);
24040 let Some((color_fetcher, mut gutter_highlights)) =
24041 self.gutter_highlights.remove(&TypeId::of::<T>())
24042 else {
24043 return;
24044 };
24045 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
24046 gutter_highlights.retain(|highlight| {
24047 while let Some(range_to_remove) = ranges_to_remove.peek() {
24048 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
24049 Ordering::Less | Ordering::Equal => {
24050 ranges_to_remove.next();
24051 }
24052 Ordering::Greater => {
24053 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
24054 Ordering::Less | Ordering::Equal => {
24055 return false;
24056 }
24057 Ordering::Greater => break,
24058 }
24059 }
24060 }
24061 }
24062
24063 true
24064 });
24065 self.gutter_highlights
24066 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
24067 }
24068
24069 #[cfg(any(test, feature = "test-support"))]
24070 pub fn all_text_highlights(
24071 &self,
24072 window: &mut Window,
24073 cx: &mut Context<Self>,
24074 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
24075 let snapshot = self.snapshot(window, cx);
24076 self.display_map.update(cx, |display_map, _| {
24077 display_map
24078 .all_text_highlights()
24079 .map(|(_, highlight)| {
24080 let (style, ranges) = highlight.as_ref();
24081 (
24082 *style,
24083 ranges
24084 .iter()
24085 .map(|range| range.clone().to_display_points(&snapshot))
24086 .collect(),
24087 )
24088 })
24089 .collect()
24090 })
24091 }
24092
24093 #[cfg(any(test, feature = "test-support"))]
24094 pub fn all_text_background_highlights(
24095 &self,
24096 window: &mut Window,
24097 cx: &mut Context<Self>,
24098 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
24099 let snapshot = self.snapshot(window, cx);
24100 let buffer = &snapshot.buffer_snapshot();
24101 let start = buffer.anchor_before(MultiBufferOffset(0));
24102 let end = buffer.anchor_after(buffer.len());
24103 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
24104 }
24105
24106 #[cfg(any(test, feature = "test-support"))]
24107 pub fn sorted_background_highlights_in_range(
24108 &self,
24109 search_range: Range<Anchor>,
24110 display_snapshot: &DisplaySnapshot,
24111 theme: &Theme,
24112 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
24113 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
24114 res.sort_by(|a, b| {
24115 a.0.start
24116 .cmp(&b.0.start)
24117 .then_with(|| a.0.end.cmp(&b.0.end))
24118 .then_with(|| a.1.cmp(&b.1))
24119 });
24120 res
24121 }
24122
24123 #[cfg(any(test, feature = "test-support"))]
24124 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
24125 let snapshot = self.buffer().read(cx).snapshot(cx);
24126
24127 let highlights = self
24128 .background_highlights
24129 .get(&HighlightKey::BufferSearchHighlights);
24130
24131 if let Some((_color, ranges)) = highlights {
24132 ranges
24133 .iter()
24134 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
24135 .collect_vec()
24136 } else {
24137 vec![]
24138 }
24139 }
24140
24141 pub fn has_background_highlights(&self, key: HighlightKey) -> bool {
24142 self.background_highlights
24143 .get(&key)
24144 .is_some_and(|(_, highlights)| !highlights.is_empty())
24145 }
24146
24147 /// Returns all background highlights for a given range.
24148 ///
24149 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
24150 pub fn background_highlights_in_range(
24151 &self,
24152 search_range: Range<Anchor>,
24153 display_snapshot: &DisplaySnapshot,
24154 theme: &Theme,
24155 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
24156 let mut results = Vec::new();
24157 for (color_fetcher, ranges) in self.background_highlights.values() {
24158 let start_ix = match ranges.binary_search_by(|probe| {
24159 let cmp = probe
24160 .end
24161 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
24162 if cmp.is_gt() {
24163 Ordering::Greater
24164 } else {
24165 Ordering::Less
24166 }
24167 }) {
24168 Ok(i) | Err(i) => i,
24169 };
24170 for (index, range) in ranges[start_ix..].iter().enumerate() {
24171 if range
24172 .start
24173 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
24174 .is_ge()
24175 {
24176 break;
24177 }
24178
24179 let color = color_fetcher(&(start_ix + index), theme);
24180 let start = range.start.to_display_point(display_snapshot);
24181 let end = range.end.to_display_point(display_snapshot);
24182 results.push((start..end, color))
24183 }
24184 }
24185 results
24186 }
24187
24188 pub fn gutter_highlights_in_range(
24189 &self,
24190 search_range: Range<Anchor>,
24191 display_snapshot: &DisplaySnapshot,
24192 cx: &App,
24193 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
24194 let mut results = Vec::new();
24195 for (color_fetcher, ranges) in self.gutter_highlights.values() {
24196 let color = color_fetcher(cx);
24197 let start_ix = match ranges.binary_search_by(|probe| {
24198 let cmp = probe
24199 .end
24200 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
24201 if cmp.is_gt() {
24202 Ordering::Greater
24203 } else {
24204 Ordering::Less
24205 }
24206 }) {
24207 Ok(i) | Err(i) => i,
24208 };
24209 for range in &ranges[start_ix..] {
24210 if range
24211 .start
24212 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
24213 .is_ge()
24214 {
24215 break;
24216 }
24217
24218 let start = range.start.to_display_point(display_snapshot);
24219 let end = range.end.to_display_point(display_snapshot);
24220 results.push((start..end, color))
24221 }
24222 }
24223 results
24224 }
24225
24226 /// Get the text ranges corresponding to the redaction query
24227 pub fn redacted_ranges(
24228 &self,
24229 search_range: Range<Anchor>,
24230 display_snapshot: &DisplaySnapshot,
24231 cx: &App,
24232 ) -> Vec<Range<DisplayPoint>> {
24233 display_snapshot
24234 .buffer_snapshot()
24235 .redacted_ranges(search_range, |file| {
24236 if let Some(file) = file {
24237 file.is_private()
24238 && EditorSettings::get(
24239 Some(SettingsLocation {
24240 worktree_id: file.worktree_id(cx),
24241 path: file.path().as_ref(),
24242 }),
24243 cx,
24244 )
24245 .redact_private_values
24246 } else {
24247 false
24248 }
24249 })
24250 .map(|range| {
24251 range.start.to_display_point(display_snapshot)
24252 ..range.end.to_display_point(display_snapshot)
24253 })
24254 .collect()
24255 }
24256
24257 pub fn highlight_text_key(
24258 &mut self,
24259 key: HighlightKey,
24260 ranges: Vec<Range<Anchor>>,
24261 style: HighlightStyle,
24262 merge: bool,
24263 cx: &mut Context<Self>,
24264 ) {
24265 self.display_map.update(cx, |map, cx| {
24266 map.highlight_text(key, ranges, style, merge, cx);
24267 });
24268 cx.notify();
24269 }
24270
24271 pub fn highlight_text(
24272 &mut self,
24273 key: HighlightKey,
24274 ranges: Vec<Range<Anchor>>,
24275 style: HighlightStyle,
24276 cx: &mut Context<Self>,
24277 ) {
24278 self.display_map.update(cx, |map, cx| {
24279 map.highlight_text(key, ranges, style, false, cx)
24280 });
24281 cx.notify();
24282 }
24283
24284 pub fn text_highlights<'a>(
24285 &'a self,
24286 key: HighlightKey,
24287 cx: &'a App,
24288 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
24289 self.display_map.read(cx).text_highlights(key)
24290 }
24291
24292 pub fn clear_highlights(&mut self, key: HighlightKey, cx: &mut Context<Self>) {
24293 let cleared = self
24294 .display_map
24295 .update(cx, |map, _| map.clear_highlights(key));
24296 if cleared {
24297 cx.notify();
24298 }
24299 }
24300
24301 pub fn clear_highlights_with(
24302 &mut self,
24303 f: &mut dyn FnMut(&HighlightKey) -> bool,
24304 cx: &mut Context<Self>,
24305 ) {
24306 let cleared = self
24307 .display_map
24308 .update(cx, |map, _| map.clear_highlights_with(f));
24309 if cleared {
24310 cx.notify();
24311 }
24312 }
24313
24314 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
24315 (self.read_only(cx) || self.blink_manager.read(cx).visible())
24316 && self.focus_handle.is_focused(window)
24317 }
24318
24319 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
24320 self.show_cursor_when_unfocused = is_enabled;
24321 cx.notify();
24322 }
24323
24324 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
24325 cx.notify();
24326 }
24327
24328 fn on_debug_session_event(
24329 &mut self,
24330 _session: Entity<Session>,
24331 event: &SessionEvent,
24332 cx: &mut Context<Self>,
24333 ) {
24334 if let SessionEvent::InvalidateInlineValue = event {
24335 self.refresh_inline_values(cx);
24336 }
24337 }
24338
24339 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
24340 let Some(semantics) = self.semantics_provider.clone() else {
24341 return;
24342 };
24343
24344 if !self.inline_value_cache.enabled {
24345 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
24346 self.splice_inlays(&inlays, Vec::new(), cx);
24347 return;
24348 }
24349
24350 let current_execution_position = self
24351 .highlighted_rows
24352 .get(&TypeId::of::<ActiveDebugLine>())
24353 .and_then(|lines| lines.last().map(|line| line.range.end));
24354
24355 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
24356 let inline_values = editor
24357 .update(cx, |editor, cx| {
24358 let Some(current_execution_position) = current_execution_position else {
24359 return Some(Task::ready(Ok(Vec::new())));
24360 };
24361
24362 let (buffer, buffer_anchor) =
24363 editor.buffer.read_with(cx, |multibuffer, cx| {
24364 let multibuffer_snapshot = multibuffer.snapshot(cx);
24365 let (buffer_anchor, _) = multibuffer_snapshot
24366 .anchor_to_buffer_anchor(current_execution_position)?;
24367 let buffer = multibuffer.buffer(buffer_anchor.buffer_id)?;
24368 Some((buffer, buffer_anchor))
24369 })?;
24370
24371 let range = buffer.read(cx).anchor_before(0)..buffer_anchor;
24372
24373 semantics.inline_values(buffer, range, cx)
24374 })
24375 .ok()
24376 .flatten()?
24377 .await
24378 .context("refreshing debugger inlays")
24379 .log_err()?;
24380
24381 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
24382
24383 for (buffer_id, inline_value) in inline_values
24384 .into_iter()
24385 .map(|hint| (hint.position.buffer_id, hint))
24386 {
24387 buffer_inline_values
24388 .entry(buffer_id)
24389 .or_default()
24390 .push(inline_value);
24391 }
24392
24393 editor
24394 .update(cx, |editor, cx| {
24395 let snapshot = editor.buffer.read(cx).snapshot(cx);
24396 let mut new_inlays = Vec::default();
24397
24398 for (_buffer_id, inline_values) in buffer_inline_values {
24399 for hint in inline_values {
24400 let Some(anchor) = snapshot.anchor_in_excerpt(hint.position) else {
24401 continue;
24402 };
24403 let inlay = Inlay::debugger(
24404 post_inc(&mut editor.next_inlay_id),
24405 anchor,
24406 hint.text(),
24407 );
24408 if !inlay.text().chars().contains(&'\n') {
24409 new_inlays.push(inlay);
24410 }
24411 }
24412 }
24413
24414 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
24415 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
24416
24417 editor.splice_inlays(&inlay_ids, new_inlays, cx);
24418 })
24419 .ok()?;
24420 Some(())
24421 });
24422 }
24423
24424 fn on_buffer_event(
24425 &mut self,
24426 multibuffer: &Entity<MultiBuffer>,
24427 event: &multi_buffer::Event,
24428 window: &mut Window,
24429 cx: &mut Context<Self>,
24430 ) {
24431 match event {
24432 multi_buffer::Event::Edited {
24433 edited_buffer,
24434 is_local,
24435 } => {
24436 self.scrollbar_marker_state.dirty = true;
24437 self.active_indent_guides_state.dirty = true;
24438 self.refresh_active_diagnostics(cx);
24439 self.refresh_code_actions(window, cx);
24440 self.refresh_single_line_folds(window, cx);
24441 let snapshot = self.snapshot(window, cx);
24442 self.refresh_matching_bracket_highlights(&snapshot, cx);
24443 self.refresh_outline_symbols_at_cursor(cx);
24444 self.refresh_sticky_headers(&snapshot, cx);
24445 if *is_local && self.has_active_edit_prediction() {
24446 self.update_visible_edit_prediction(window, cx);
24447 }
24448
24449 // Clean up orphaned review comments after edits
24450 self.cleanup_orphaned_review_comments(cx);
24451
24452 if let Some(buffer) = edited_buffer {
24453 if buffer.read(cx).file().is_none() {
24454 cx.emit(EditorEvent::TitleChanged);
24455 }
24456
24457 if self.project.is_some() {
24458 let buffer_id = buffer.read(cx).remote_id();
24459 self.register_buffer(buffer_id, cx);
24460 self.update_lsp_data(Some(buffer_id), window, cx);
24461 self.refresh_inlay_hints(
24462 InlayHintRefreshReason::BufferEdited(buffer_id),
24463 cx,
24464 );
24465 }
24466 }
24467
24468 cx.emit(EditorEvent::BufferEdited);
24469 cx.emit(SearchEvent::MatchesInvalidated);
24470
24471 let Some(project) = &self.project else { return };
24472 let (telemetry, is_via_ssh) = {
24473 let project = project.read(cx);
24474 let telemetry = project.client().telemetry().clone();
24475 let is_via_ssh = project.is_via_remote_server();
24476 (telemetry, is_via_ssh)
24477 };
24478 telemetry.log_edit_event("editor", is_via_ssh);
24479 }
24480 multi_buffer::Event::BufferRangesUpdated {
24481 buffer,
24482 ranges,
24483 path_key,
24484 } => {
24485 self.refresh_document_highlights(cx);
24486 let buffer_id = buffer.read(cx).remote_id();
24487 if self.buffer.read(cx).diff_for(buffer_id).is_none()
24488 && let Some(project) = &self.project
24489 {
24490 update_uncommitted_diff_for_buffer(
24491 cx.entity(),
24492 project,
24493 [buffer.clone()],
24494 self.buffer.clone(),
24495 cx,
24496 )
24497 .detach();
24498 }
24499 self.register_visible_buffers(cx);
24500 self.update_lsp_data(Some(buffer_id), window, cx);
24501 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
24502 self.refresh_runnables(None, window, cx);
24503 self.bracket_fetched_tree_sitter_chunks
24504 .retain(|range, _| range.start.buffer_id != buffer_id);
24505 self.colorize_brackets(false, cx);
24506 self.refresh_selected_text_highlights(&self.display_snapshot(cx), true, window, cx);
24507 self.semantic_token_state.invalidate_buffer(&buffer_id);
24508 cx.emit(EditorEvent::BufferRangesUpdated {
24509 buffer: buffer.clone(),
24510 ranges: ranges.clone(),
24511 path_key: path_key.clone(),
24512 });
24513 }
24514 multi_buffer::Event::BuffersRemoved { removed_buffer_ids } => {
24515 if let Some(inlay_hints) = &mut self.inlay_hints {
24516 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
24517 }
24518 self.refresh_inlay_hints(
24519 InlayHintRefreshReason::BuffersRemoved(removed_buffer_ids.clone()),
24520 cx,
24521 );
24522 for buffer_id in removed_buffer_ids {
24523 self.registered_buffers.remove(buffer_id);
24524 self.clear_runnables(Some(*buffer_id));
24525 self.semantic_token_state.invalidate_buffer(buffer_id);
24526 self.display_map.update(cx, |display_map, cx| {
24527 display_map.invalidate_semantic_highlights(*buffer_id);
24528 display_map.clear_lsp_folding_ranges(*buffer_id, cx);
24529 });
24530 }
24531
24532 self.display_map.update(cx, |display_map, cx| {
24533 display_map.unfold_buffers(removed_buffer_ids.iter().copied(), cx);
24534 });
24535
24536 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24537 cx.emit(EditorEvent::BuffersRemoved {
24538 removed_buffer_ids: removed_buffer_ids.clone(),
24539 });
24540 }
24541 multi_buffer::Event::BuffersEdited { buffer_ids } => {
24542 self.display_map.update(cx, |map, cx| {
24543 map.unfold_buffers(buffer_ids.iter().copied(), cx)
24544 });
24545 cx.emit(EditorEvent::BuffersEdited {
24546 buffer_ids: buffer_ids.clone(),
24547 });
24548 }
24549 multi_buffer::Event::Reparsed(buffer_id) => {
24550 self.refresh_runnables(Some(*buffer_id), window, cx);
24551 self.refresh_selected_text_highlights(&self.display_snapshot(cx), true, window, cx);
24552 self.colorize_brackets(true, cx);
24553 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24554
24555 cx.emit(EditorEvent::Reparsed(*buffer_id));
24556 }
24557 multi_buffer::Event::DiffHunksToggled => {
24558 self.refresh_runnables(None, window, cx);
24559 }
24560 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
24561 if !is_fresh_language {
24562 self.registered_buffers.remove(&buffer_id);
24563 }
24564 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24565 cx.emit(EditorEvent::Reparsed(*buffer_id));
24566 self.update_edit_prediction_settings(cx);
24567 cx.notify();
24568 }
24569 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
24570 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
24571 multi_buffer::Event::FileHandleChanged
24572 | multi_buffer::Event::Reloaded
24573 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
24574 multi_buffer::Event::DiagnosticsUpdated => {
24575 self.update_diagnostics_state(window, cx);
24576 }
24577 _ => {}
24578 };
24579 }
24580
24581 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
24582 if !self.diagnostics_enabled() {
24583 return;
24584 }
24585 self.refresh_active_diagnostics(cx);
24586 self.refresh_inline_diagnostics(true, window, cx);
24587 self.scrollbar_marker_state.dirty = true;
24588 cx.notify();
24589 }
24590
24591 pub fn start_temporary_diff_override(&mut self) {
24592 self.load_diff_task.take();
24593 self.temporary_diff_override = true;
24594 }
24595
24596 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
24597 self.temporary_diff_override = false;
24598 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
24599 self.buffer.update(cx, |buffer, cx| {
24600 buffer.set_all_diff_hunks_collapsed(cx);
24601 });
24602
24603 if let Some(project) = self.project.clone() {
24604 self.load_diff_task = Some(
24605 update_uncommitted_diff_for_buffer(
24606 cx.entity(),
24607 &project,
24608 self.buffer.read(cx).all_buffers(),
24609 self.buffer.clone(),
24610 cx,
24611 )
24612 .shared(),
24613 );
24614 }
24615 }
24616
24617 fn on_display_map_changed(
24618 &mut self,
24619 _: Entity<DisplayMap>,
24620 _: &mut Window,
24621 cx: &mut Context<Self>,
24622 ) {
24623 cx.notify();
24624 }
24625
24626 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
24627 if !self.mode.is_full() {
24628 return None;
24629 }
24630
24631 let theme_settings = theme_settings::ThemeSettings::get_global(cx);
24632 let theme = cx.theme();
24633 let accent_colors = theme.accents().clone();
24634
24635 let accent_overrides = theme_settings
24636 .theme_overrides
24637 .get(theme.name.as_ref())
24638 .map(|theme_style| &theme_style.accents)
24639 .into_iter()
24640 .flatten()
24641 .chain(
24642 theme_settings
24643 .experimental_theme_overrides
24644 .as_ref()
24645 .map(|overrides| &overrides.accents)
24646 .into_iter()
24647 .flatten(),
24648 )
24649 .flat_map(|accent| accent.0.clone().map(SharedString::from))
24650 .collect();
24651
24652 Some(AccentData {
24653 colors: accent_colors,
24654 overrides: accent_overrides,
24655 })
24656 }
24657
24658 fn fetch_applicable_language_settings(
24659 &self,
24660 cx: &App,
24661 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
24662 if !self.mode.is_full() {
24663 return HashMap::default();
24664 }
24665
24666 self.buffer().read(cx).all_buffers().into_iter().fold(
24667 HashMap::default(),
24668 |mut acc, buffer| {
24669 let buffer = buffer.read(cx);
24670 let language = buffer.language().map(|language| language.name());
24671 if let hash_map::Entry::Vacant(v) = acc.entry(language) {
24672 v.insert(LanguageSettings::for_buffer(&buffer, cx).into_owned());
24673 }
24674 acc
24675 },
24676 )
24677 }
24678
24679 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24680 let new_language_settings = self.fetch_applicable_language_settings(cx);
24681 let language_settings_changed = new_language_settings != self.applicable_language_settings;
24682 self.applicable_language_settings = new_language_settings;
24683
24684 let new_accents = self.fetch_accent_data(cx);
24685 let accents_changed = new_accents != self.accent_data;
24686 self.accent_data = new_accents;
24687
24688 if self.diagnostics_enabled() {
24689 let new_severity = EditorSettings::get_global(cx)
24690 .diagnostics_max_severity
24691 .unwrap_or(DiagnosticSeverity::Hint);
24692 self.set_max_diagnostics_severity(new_severity, cx);
24693 }
24694 self.refresh_runnables(None, window, cx);
24695 self.update_edit_prediction_settings(cx);
24696 self.refresh_edit_prediction(true, false, window, cx);
24697 self.refresh_inline_values(cx);
24698
24699 let old_cursor_shape = self.cursor_shape;
24700 let old_show_breadcrumbs = self.show_breadcrumbs;
24701
24702 {
24703 let editor_settings = EditorSettings::get_global(cx);
24704 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
24705 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
24706 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
24707 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
24708 }
24709
24710 if old_cursor_shape != self.cursor_shape {
24711 cx.emit(EditorEvent::CursorShapeChanged);
24712 }
24713
24714 if old_show_breadcrumbs != self.show_breadcrumbs {
24715 cx.emit(EditorEvent::BreadcrumbsChanged);
24716 }
24717
24718 let (restore_unsaved_buffers, show_inline_diagnostics, inline_blame_enabled) = {
24719 let project_settings = ProjectSettings::get_global(cx);
24720 (
24721 project_settings.session.restore_unsaved_buffers,
24722 project_settings.diagnostics.inline.enabled,
24723 project_settings.git.inline_blame.enabled,
24724 )
24725 };
24726 self.buffer_serialization = self
24727 .should_serialize_buffer()
24728 .then(|| BufferSerialization::new(restore_unsaved_buffers));
24729
24730 if self.mode.is_full() {
24731 if self.show_inline_diagnostics != show_inline_diagnostics {
24732 self.show_inline_diagnostics = show_inline_diagnostics;
24733 self.refresh_inline_diagnostics(false, window, cx);
24734 }
24735
24736 if self.git_blame_inline_enabled != inline_blame_enabled {
24737 self.toggle_git_blame_inline_internal(false, window, cx);
24738 }
24739
24740 let minimap_settings = EditorSettings::get_global(cx).minimap;
24741 if self.minimap_visibility != MinimapVisibility::Disabled {
24742 if self.minimap_visibility.settings_visibility()
24743 != minimap_settings.minimap_enabled()
24744 {
24745 self.set_minimap_visibility(
24746 MinimapVisibility::for_mode(self.mode(), cx),
24747 window,
24748 cx,
24749 );
24750 } else if let Some(minimap_entity) = self.minimap.as_ref() {
24751 minimap_entity.update(cx, |minimap_editor, cx| {
24752 minimap_editor.update_minimap_configuration(minimap_settings, cx)
24753 })
24754 }
24755 }
24756
24757 if language_settings_changed || accents_changed {
24758 self.colorize_brackets(true, cx);
24759 }
24760
24761 if language_settings_changed {
24762 self.clear_disabled_lsp_folding_ranges(window, cx);
24763 self.refresh_document_symbols(None, cx);
24764 }
24765
24766 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
24767 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
24768 }) {
24769 if !inlay_splice.is_empty() {
24770 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
24771 }
24772 self.refresh_document_colors(None, window, cx);
24773 }
24774
24775 self.refresh_inlay_hints(
24776 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
24777 self.selections.newest_anchor().head(),
24778 &self.buffer.read(cx).snapshot(cx),
24779 cx,
24780 )),
24781 cx,
24782 );
24783
24784 let new_semantic_token_rules = ProjectSettings::get_global(cx)
24785 .global_lsp_settings
24786 .semantic_token_rules
24787 .clone();
24788 let semantic_token_rules_changed = self
24789 .semantic_token_state
24790 .update_rules(new_semantic_token_rules);
24791 if language_settings_changed || semantic_token_rules_changed {
24792 self.invalidate_semantic_tokens(None);
24793 self.refresh_semantic_tokens(None, None, cx);
24794 }
24795 }
24796
24797 cx.notify();
24798 }
24799
24800 fn theme_changed(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24801 if !self.mode.is_full() {
24802 return;
24803 }
24804
24805 let new_accents = self.fetch_accent_data(cx);
24806 if new_accents != self.accent_data {
24807 self.accent_data = new_accents;
24808 self.colorize_brackets(true, cx);
24809 }
24810
24811 self.invalidate_semantic_tokens(None);
24812 self.refresh_semantic_tokens(None, None, cx);
24813 }
24814
24815 pub fn set_searchable(&mut self, searchable: bool) {
24816 self.searchable = searchable;
24817 }
24818
24819 pub fn searchable(&self) -> bool {
24820 self.searchable
24821 }
24822
24823 pub fn open_excerpts_in_split(
24824 &mut self,
24825 _: &OpenExcerptsSplit,
24826 window: &mut Window,
24827 cx: &mut Context<Self>,
24828 ) {
24829 self.open_excerpts_common(None, true, window, cx)
24830 }
24831
24832 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
24833 self.open_excerpts_common(None, false, window, cx)
24834 }
24835
24836 pub(crate) fn open_excerpts_common(
24837 &mut self,
24838 jump_data: Option<JumpData>,
24839 split: bool,
24840 window: &mut Window,
24841 cx: &mut Context<Self>,
24842 ) {
24843 if self.buffer.read(cx).is_singleton() {
24844 cx.propagate();
24845 return;
24846 }
24847
24848 let mut new_selections_by_buffer = HashMap::default();
24849 match &jump_data {
24850 Some(JumpData::MultiBufferPoint {
24851 anchor,
24852 position,
24853 line_offset_from_top,
24854 }) => {
24855 if let Some(buffer) = self.buffer.read(cx).buffer(anchor.buffer_id) {
24856 let buffer_snapshot = buffer.read(cx).snapshot();
24857 let jump_to_point = if buffer_snapshot.can_resolve(&anchor) {
24858 language::ToPoint::to_point(anchor, &buffer_snapshot)
24859 } else {
24860 buffer_snapshot.clip_point(*position, Bias::Left)
24861 };
24862 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
24863 new_selections_by_buffer.insert(
24864 buffer,
24865 (
24866 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
24867 Some(*line_offset_from_top),
24868 ),
24869 );
24870 }
24871 }
24872 Some(JumpData::MultiBufferRow {
24873 row,
24874 line_offset_from_top,
24875 }) => {
24876 let point = MultiBufferPoint::new(row.0, 0);
24877 if let Some((buffer, buffer_point)) =
24878 self.buffer.read(cx).point_to_buffer_point(point, cx)
24879 {
24880 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
24881 new_selections_by_buffer
24882 .entry(buffer)
24883 .or_insert((Vec::new(), Some(*line_offset_from_top)))
24884 .0
24885 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
24886 }
24887 }
24888 None => {
24889 let selections = self
24890 .selections
24891 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
24892 let multi_buffer = self.buffer.read(cx);
24893 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
24894 for selection in selections {
24895 for (snapshot, range, anchor) in multi_buffer_snapshot
24896 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
24897 {
24898 if let Some((text_anchor, _)) = anchor.and_then(|anchor| {
24899 multi_buffer_snapshot.anchor_to_buffer_anchor(anchor)
24900 }) {
24901 let Some(buffer_handle) = multi_buffer.buffer(text_anchor.buffer_id)
24902 else {
24903 continue;
24904 };
24905 let offset = text::ToOffset::to_offset(
24906 &text_anchor,
24907 &buffer_handle.read(cx).snapshot(),
24908 );
24909 let range = BufferOffset(offset)..BufferOffset(offset);
24910 new_selections_by_buffer
24911 .entry(buffer_handle)
24912 .or_insert((Vec::new(), None))
24913 .0
24914 .push(range)
24915 } else {
24916 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
24917 else {
24918 continue;
24919 };
24920 new_selections_by_buffer
24921 .entry(buffer_handle)
24922 .or_insert((Vec::new(), None))
24923 .0
24924 .push(range)
24925 }
24926 }
24927 }
24928 }
24929 }
24930
24931 if self.delegate_open_excerpts {
24932 let selections_by_buffer: HashMap<_, _> = new_selections_by_buffer
24933 .into_iter()
24934 .map(|(buffer, value)| (buffer.read(cx).remote_id(), value))
24935 .collect();
24936 if !selections_by_buffer.is_empty() {
24937 cx.emit(EditorEvent::OpenExcerptsRequested {
24938 selections_by_buffer,
24939 split,
24940 });
24941 }
24942 return;
24943 }
24944
24945 let Some(workspace) = self.workspace() else {
24946 cx.propagate();
24947 return;
24948 };
24949
24950 new_selections_by_buffer
24951 .retain(|buffer, _| buffer.read(cx).file().is_none_or(|file| file.can_open()));
24952
24953 if new_selections_by_buffer.is_empty() {
24954 return;
24955 }
24956
24957 Self::open_buffers_in_workspace(
24958 workspace.downgrade(),
24959 new_selections_by_buffer,
24960 split,
24961 window,
24962 cx,
24963 );
24964 }
24965
24966 pub(crate) fn open_buffers_in_workspace(
24967 workspace: WeakEntity<Workspace>,
24968 new_selections_by_buffer: HashMap<
24969 Entity<language::Buffer>,
24970 (Vec<Range<BufferOffset>>, Option<u32>),
24971 >,
24972 split: bool,
24973 window: &mut Window,
24974 cx: &mut App,
24975 ) {
24976 // We defer the pane interaction because we ourselves are a workspace item
24977 // and activating a new item causes the pane to call a method on us reentrantly,
24978 // which panics if we're on the stack.
24979 window.defer(cx, move |window, cx| {
24980 workspace
24981 .update(cx, |workspace, cx| {
24982 let pane = if split {
24983 workspace.adjacent_pane(window, cx)
24984 } else {
24985 workspace.active_pane().clone()
24986 };
24987
24988 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
24989 let buffer_read = buffer.read(cx);
24990 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
24991 (true, project::File::from_dyn(Some(file)).is_some())
24992 } else {
24993 (false, false)
24994 };
24995
24996 // If project file is none workspace.open_project_item will fail to open the excerpt
24997 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
24998 // so we check if there's a tab match in that case first
24999 let editor = (!has_file || !is_project_file)
25000 .then(|| {
25001 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
25002 // so `workspace.open_project_item` will never find them, always opening a new editor.
25003 // Instead, we try to activate the existing editor in the pane first.
25004 let (editor, pane_item_index, pane_item_id) =
25005 pane.read(cx).items().enumerate().find_map(|(i, item)| {
25006 let editor = item.downcast::<Editor>()?;
25007 let singleton_buffer =
25008 editor.read(cx).buffer().read(cx).as_singleton()?;
25009 if singleton_buffer == buffer {
25010 Some((editor, i, item.item_id()))
25011 } else {
25012 None
25013 }
25014 })?;
25015 pane.update(cx, |pane, cx| {
25016 pane.activate_item(pane_item_index, true, true, window, cx);
25017 if !PreviewTabsSettings::get_global(cx)
25018 .enable_preview_from_multibuffer
25019 {
25020 pane.unpreview_item_if_preview(pane_item_id);
25021 }
25022 });
25023 Some(editor)
25024 })
25025 .flatten()
25026 .unwrap_or_else(|| {
25027 let keep_old_preview = PreviewTabsSettings::get_global(cx)
25028 .enable_keep_preview_on_code_navigation;
25029 let allow_new_preview = PreviewTabsSettings::get_global(cx)
25030 .enable_preview_from_multibuffer;
25031 workspace.open_project_item::<Self>(
25032 pane.clone(),
25033 buffer,
25034 true,
25035 true,
25036 keep_old_preview,
25037 allow_new_preview,
25038 window,
25039 cx,
25040 )
25041 });
25042
25043 editor.update(cx, |editor, cx| {
25044 if has_file && !is_project_file {
25045 editor.set_read_only(true);
25046 }
25047 let autoscroll = match scroll_offset {
25048 Some(scroll_offset) => {
25049 Autoscroll::top_relative(scroll_offset as usize)
25050 }
25051 None => Autoscroll::newest(),
25052 };
25053 let nav_history = editor.nav_history.take();
25054 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
25055 let Some(buffer_snapshot) = multibuffer_snapshot.as_singleton() else {
25056 return;
25057 };
25058 editor.change_selections(
25059 SelectionEffects::scroll(autoscroll),
25060 window,
25061 cx,
25062 |s| {
25063 s.select_ranges(ranges.into_iter().map(|range| {
25064 let range = buffer_snapshot.anchor_before(range.start)
25065 ..buffer_snapshot.anchor_after(range.end);
25066 multibuffer_snapshot
25067 .buffer_anchor_range_to_anchor_range(range)
25068 .unwrap()
25069 }));
25070 },
25071 );
25072 editor.nav_history = nav_history;
25073 });
25074 }
25075 })
25076 .ok();
25077 });
25078 }
25079
25080 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
25081 let snapshot = self.buffer.read(cx).read(cx);
25082 let (_, ranges) = self.text_highlights(HighlightKey::InputComposition, cx)?;
25083 Some(
25084 ranges
25085 .iter()
25086 .map(move |range| {
25087 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
25088 })
25089 .collect(),
25090 )
25091 }
25092
25093 fn selection_replacement_ranges(
25094 &self,
25095 range: Range<MultiBufferOffsetUtf16>,
25096 cx: &mut App,
25097 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
25098 let selections = self
25099 .selections
25100 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
25101 let newest_selection = selections
25102 .iter()
25103 .max_by_key(|selection| selection.id)
25104 .unwrap();
25105 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
25106 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
25107 let snapshot = self.buffer.read(cx).read(cx);
25108 selections
25109 .into_iter()
25110 .map(|mut selection| {
25111 selection.start.0.0 =
25112 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
25113 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
25114 snapshot.clip_offset_utf16(selection.start, Bias::Left)
25115 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
25116 })
25117 .collect()
25118 }
25119
25120 fn report_editor_event(
25121 &self,
25122 reported_event: ReportEditorEvent,
25123 file_extension: Option<String>,
25124 cx: &App,
25125 ) {
25126 if cfg!(any(test, feature = "test-support")) {
25127 return;
25128 }
25129
25130 let Some(project) = &self.project else { return };
25131
25132 // If None, we are in a file without an extension
25133 let file = self
25134 .buffer
25135 .read(cx)
25136 .as_singleton()
25137 .and_then(|b| b.read(cx).file());
25138 let file_extension = file_extension.or(file
25139 .as_ref()
25140 .and_then(|file| Path::new(file.file_name(cx)).extension())
25141 .and_then(|e| e.to_str())
25142 .map(|a| a.to_string()));
25143
25144 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
25145 .map(|vim_mode| vim_mode.0)
25146 .unwrap_or(false);
25147
25148 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
25149 let copilot_enabled = edit_predictions_provider
25150 == language::language_settings::EditPredictionProvider::Copilot;
25151 let copilot_enabled_for_language = self
25152 .buffer
25153 .read(cx)
25154 .language_settings(cx)
25155 .show_edit_predictions;
25156
25157 let project = project.read(cx);
25158 let event_type = reported_event.event_type();
25159
25160 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
25161 telemetry::event!(
25162 event_type,
25163 type = if auto_saved {"autosave"} else {"manual"},
25164 file_extension,
25165 vim_mode,
25166 copilot_enabled,
25167 copilot_enabled_for_language,
25168 edit_predictions_provider,
25169 is_via_ssh = project.is_via_remote_server(),
25170 );
25171 } else {
25172 telemetry::event!(
25173 event_type,
25174 file_extension,
25175 vim_mode,
25176 copilot_enabled,
25177 copilot_enabled_for_language,
25178 edit_predictions_provider,
25179 is_via_ssh = project.is_via_remote_server(),
25180 );
25181 };
25182 }
25183
25184 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
25185 /// with each line being an array of {text, highlight} objects.
25186 fn copy_highlight_json(
25187 &mut self,
25188 _: &CopyHighlightJson,
25189 _: &mut Window,
25190 cx: &mut Context<Self>,
25191 ) {
25192 #[derive(Serialize)]
25193 struct Chunk<'a> {
25194 text: String,
25195 highlight: Option<&'a str>,
25196 }
25197
25198 let snapshot = self.buffer.read(cx).snapshot(cx);
25199 let mut selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
25200 let max_point = snapshot.max_point();
25201
25202 let range = if self.selections.line_mode() {
25203 selection.start = Point::new(selection.start.row, 0);
25204 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
25205 selection.goal = SelectionGoal::None;
25206 selection.range()
25207 } else if selection.is_empty() {
25208 Point::new(0, 0)..max_point
25209 } else {
25210 selection.range()
25211 };
25212
25213 let chunks = snapshot.chunks(
25214 range,
25215 LanguageAwareStyling {
25216 tree_sitter: true,
25217 diagnostics: true,
25218 },
25219 );
25220 let mut lines = Vec::new();
25221 let mut line: VecDeque<Chunk> = VecDeque::new();
25222
25223 let Some(style) = self.style.as_ref() else {
25224 return;
25225 };
25226
25227 for chunk in chunks {
25228 let highlight = chunk
25229 .syntax_highlight_id
25230 .and_then(|id| style.syntax.get_capture_name(id));
25231
25232 let mut chunk_lines = chunk.text.split('\n').peekable();
25233 while let Some(text) = chunk_lines.next() {
25234 let mut merged_with_last_token = false;
25235 if let Some(last_token) = line.back_mut()
25236 && last_token.highlight == highlight
25237 {
25238 last_token.text.push_str(text);
25239 merged_with_last_token = true;
25240 }
25241
25242 if !merged_with_last_token {
25243 line.push_back(Chunk {
25244 text: text.into(),
25245 highlight,
25246 });
25247 }
25248
25249 if chunk_lines.peek().is_some() {
25250 if line.len() > 1 && line.front().unwrap().text.is_empty() {
25251 line.pop_front();
25252 }
25253 if line.len() > 1 && line.back().unwrap().text.is_empty() {
25254 line.pop_back();
25255 }
25256
25257 lines.push(mem::take(&mut line));
25258 }
25259 }
25260 }
25261
25262 if line.iter().any(|chunk| !chunk.text.is_empty()) {
25263 lines.push(line);
25264 }
25265
25266 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
25267 return;
25268 };
25269 cx.write_to_clipboard(ClipboardItem::new_string(lines));
25270 }
25271
25272 pub fn open_context_menu(
25273 &mut self,
25274 _: &OpenContextMenu,
25275 window: &mut Window,
25276 cx: &mut Context<Self>,
25277 ) {
25278 self.request_autoscroll(Autoscroll::newest(), cx);
25279 let position = self
25280 .selections
25281 .newest_display(&self.display_snapshot(cx))
25282 .start;
25283 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
25284 }
25285
25286 pub fn replay_insert_event(
25287 &mut self,
25288 text: &str,
25289 relative_utf16_range: Option<Range<isize>>,
25290 window: &mut Window,
25291 cx: &mut Context<Self>,
25292 ) {
25293 if !self.input_enabled {
25294 cx.emit(EditorEvent::InputIgnored { text: text.into() });
25295 return;
25296 }
25297 if let Some(relative_utf16_range) = relative_utf16_range {
25298 let selections = self
25299 .selections
25300 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
25301 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25302 let new_ranges = selections.into_iter().map(|range| {
25303 let start = MultiBufferOffsetUtf16(OffsetUtf16(
25304 range
25305 .head()
25306 .0
25307 .0
25308 .saturating_add_signed(relative_utf16_range.start),
25309 ));
25310 let end = MultiBufferOffsetUtf16(OffsetUtf16(
25311 range
25312 .head()
25313 .0
25314 .0
25315 .saturating_add_signed(relative_utf16_range.end),
25316 ));
25317 start..end
25318 });
25319 s.select_ranges(new_ranges);
25320 });
25321 }
25322
25323 self.handle_input(text, window, cx);
25324 }
25325
25326 pub fn is_focused(&self, window: &Window) -> bool {
25327 self.focus_handle.is_focused(window)
25328 }
25329
25330 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25331 cx.emit(EditorEvent::Focused);
25332
25333 if let Some(descendant) = self
25334 .last_focused_descendant
25335 .take()
25336 .and_then(|descendant| descendant.upgrade())
25337 {
25338 window.focus(&descendant, cx);
25339 } else {
25340 if let Some(blame) = self.blame.as_ref() {
25341 blame.update(cx, GitBlame::focus)
25342 }
25343
25344 self.blink_manager.update(cx, BlinkManager::enable);
25345 self.show_cursor_names(window, cx);
25346 self.buffer.update(cx, |buffer, cx| {
25347 buffer.finalize_last_transaction(cx);
25348 if self.leader_id.is_none() {
25349 buffer.set_active_selections(
25350 &self.selections.disjoint_anchors_arc(),
25351 self.selections.line_mode(),
25352 self.cursor_shape,
25353 cx,
25354 );
25355 }
25356 });
25357
25358 if let Some(position_map) = self.last_position_map.clone()
25359 && !self.mouse_cursor_hidden
25360 {
25361 EditorElement::mouse_moved(
25362 self,
25363 &MouseMoveEvent {
25364 position: window.mouse_position(),
25365 pressed_button: None,
25366 modifiers: window.modifiers(),
25367 },
25368 &position_map,
25369 None,
25370 window,
25371 cx,
25372 );
25373 }
25374 }
25375 }
25376
25377 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
25378 cx.emit(EditorEvent::FocusedIn)
25379 }
25380
25381 fn handle_focus_out(
25382 &mut self,
25383 event: FocusOutEvent,
25384 _window: &mut Window,
25385 cx: &mut Context<Self>,
25386 ) {
25387 if event.blurred != self.focus_handle {
25388 self.last_focused_descendant = Some(event.blurred);
25389 }
25390 self.selection_drag_state = SelectionDragState::None;
25391 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
25392 }
25393
25394 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25395 self.blink_manager.update(cx, BlinkManager::disable);
25396 self.buffer
25397 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
25398
25399 if let Some(blame) = self.blame.as_ref() {
25400 blame.update(cx, GitBlame::blur)
25401 }
25402 if !self.hover_state.focused(window, cx) {
25403 hide_hover(self, cx);
25404 }
25405 if !self
25406 .context_menu
25407 .borrow()
25408 .as_ref()
25409 .is_some_and(|context_menu| context_menu.focused(window, cx))
25410 {
25411 self.hide_context_menu(window, cx);
25412 }
25413 self.take_active_edit_prediction(true, cx);
25414 cx.emit(EditorEvent::Blurred);
25415 cx.notify();
25416 }
25417
25418 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25419 let mut pending: String = window
25420 .pending_input_keystrokes()
25421 .into_iter()
25422 .flatten()
25423 .filter_map(|keystroke| keystroke.key_char.clone())
25424 .collect();
25425
25426 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
25427 pending = "".to_string();
25428 }
25429
25430 let existing_pending = self
25431 .text_highlights(HighlightKey::PendingInput, cx)
25432 .map(|(_, ranges)| ranges.to_vec());
25433 if existing_pending.is_none() && pending.is_empty() {
25434 return;
25435 }
25436 let transaction =
25437 self.transact(window, cx, |this, window, cx| {
25438 let selections = this
25439 .selections
25440 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
25441 let edits = selections
25442 .iter()
25443 .map(|selection| (selection.end..selection.end, pending.clone()));
25444 this.edit(edits, cx);
25445 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25446 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
25447 sel.start + ix * pending.len()..sel.end + ix * pending.len()
25448 }));
25449 });
25450 if let Some(existing_ranges) = existing_pending {
25451 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
25452 this.edit(edits, cx);
25453 }
25454 });
25455
25456 let snapshot = self.snapshot(window, cx);
25457 let ranges = self
25458 .selections
25459 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
25460 .into_iter()
25461 .map(|selection| {
25462 snapshot.buffer_snapshot().anchor_after(selection.end)
25463 ..snapshot
25464 .buffer_snapshot()
25465 .anchor_before(selection.end + pending.len())
25466 })
25467 .collect();
25468
25469 if pending.is_empty() {
25470 self.clear_highlights(HighlightKey::PendingInput, cx);
25471 } else {
25472 self.highlight_text(
25473 HighlightKey::PendingInput,
25474 ranges,
25475 HighlightStyle {
25476 underline: Some(UnderlineStyle {
25477 thickness: px(1.),
25478 color: None,
25479 wavy: false,
25480 }),
25481 ..Default::default()
25482 },
25483 cx,
25484 );
25485 }
25486
25487 self.ime_transaction = self.ime_transaction.or(transaction);
25488 if let Some(transaction) = self.ime_transaction {
25489 self.buffer.update(cx, |buffer, cx| {
25490 buffer.group_until_transaction(transaction, cx);
25491 });
25492 }
25493
25494 if self
25495 .text_highlights(HighlightKey::PendingInput, cx)
25496 .is_none()
25497 {
25498 self.ime_transaction.take();
25499 }
25500 }
25501
25502 pub fn register_action_renderer(
25503 &mut self,
25504 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
25505 ) -> Subscription {
25506 let id = self.next_editor_action_id.post_inc();
25507 self.editor_actions
25508 .borrow_mut()
25509 .insert(id, Box::new(listener));
25510
25511 let editor_actions = self.editor_actions.clone();
25512 Subscription::new(move || {
25513 editor_actions.borrow_mut().remove(&id);
25514 })
25515 }
25516
25517 pub fn register_action<A: Action>(
25518 &mut self,
25519 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
25520 ) -> Subscription {
25521 let id = self.next_editor_action_id.post_inc();
25522 let listener = Arc::new(listener);
25523 self.editor_actions.borrow_mut().insert(
25524 id,
25525 Box::new(move |_, window, _| {
25526 let listener = listener.clone();
25527 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
25528 let action = action.downcast_ref().unwrap();
25529 if phase == DispatchPhase::Bubble {
25530 listener(action, window, cx)
25531 }
25532 })
25533 }),
25534 );
25535
25536 let editor_actions = self.editor_actions.clone();
25537 Subscription::new(move || {
25538 editor_actions.borrow_mut().remove(&id);
25539 })
25540 }
25541
25542 pub fn file_header_size(&self) -> u32 {
25543 FILE_HEADER_HEIGHT
25544 }
25545
25546 pub fn restore(
25547 &mut self,
25548 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
25549 window: &mut Window,
25550 cx: &mut Context<Self>,
25551 ) {
25552 self.buffer().update(cx, |multi_buffer, cx| {
25553 for (buffer_id, changes) in revert_changes {
25554 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
25555 buffer.update(cx, |buffer, cx| {
25556 buffer.edit(
25557 changes
25558 .into_iter()
25559 .map(|(range, text)| (range, text.to_string())),
25560 None,
25561 cx,
25562 );
25563 });
25564 }
25565 }
25566 });
25567 let selections = self
25568 .selections
25569 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
25570 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25571 s.select(selections);
25572 });
25573 }
25574
25575 pub fn to_pixel_point(
25576 &mut self,
25577 source: Anchor,
25578 editor_snapshot: &EditorSnapshot,
25579 window: &mut Window,
25580 cx: &mut App,
25581 ) -> Option<gpui::Point<Pixels>> {
25582 let source_point = source.to_display_point(editor_snapshot);
25583 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
25584 }
25585
25586 pub fn display_to_pixel_point(
25587 &mut self,
25588 source: DisplayPoint,
25589 editor_snapshot: &EditorSnapshot,
25590 window: &mut Window,
25591 cx: &mut App,
25592 ) -> Option<gpui::Point<Pixels>> {
25593 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
25594 let text_layout_details = self.text_layout_details(window, cx);
25595 let scroll_top = text_layout_details
25596 .scroll_anchor
25597 .scroll_position(editor_snapshot)
25598 .y;
25599
25600 if source.row().as_f64() < scroll_top.floor() {
25601 return None;
25602 }
25603 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
25604 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
25605 Some(gpui::Point::new(source_x, source_y))
25606 }
25607
25608 pub fn has_visible_completions_menu(&self) -> bool {
25609 !self.edit_prediction_preview_is_active()
25610 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
25611 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
25612 })
25613 }
25614
25615 pub fn register_addon<T: Addon>(&mut self, instance: T) {
25616 if self.mode.is_minimap() {
25617 return;
25618 }
25619 self.addons
25620 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
25621 }
25622
25623 pub fn unregister_addon<T: Addon>(&mut self) {
25624 self.addons.remove(&std::any::TypeId::of::<T>());
25625 }
25626
25627 pub fn addon<T: Addon>(&self) -> Option<&T> {
25628 let type_id = std::any::TypeId::of::<T>();
25629 self.addons
25630 .get(&type_id)
25631 .and_then(|item| item.to_any().downcast_ref::<T>())
25632 }
25633
25634 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
25635 let type_id = std::any::TypeId::of::<T>();
25636 self.addons
25637 .get_mut(&type_id)
25638 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
25639 }
25640
25641 fn character_dimensions(&self, window: &mut Window, cx: &mut App) -> CharacterDimensions {
25642 let text_layout_details = self.text_layout_details(window, cx);
25643 let style = &text_layout_details.editor_style;
25644 let font_id = window.text_system().resolve_font(&style.text.font());
25645 let font_size = style.text.font_size.to_pixels(window.rem_size());
25646 let line_height = style.text.line_height_in_pixels(window.rem_size());
25647 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
25648 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
25649
25650 CharacterDimensions {
25651 em_width,
25652 em_advance,
25653 line_height,
25654 }
25655 }
25656
25657 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
25658 self.load_diff_task.clone()
25659 }
25660
25661 fn read_metadata_from_db(
25662 &mut self,
25663 item_id: u64,
25664 workspace_id: WorkspaceId,
25665 window: &mut Window,
25666 cx: &mut Context<Editor>,
25667 ) {
25668 if self.buffer_kind(cx) == ItemBufferKind::Singleton
25669 && !self.mode.is_minimap()
25670 && WorkspaceSettings::get(None, cx).restore_on_startup
25671 != RestoreOnStartupBehavior::EmptyTab
25672 {
25673 let buffer_snapshot = OnceCell::new();
25674
25675 // Get file path for path-based fold lookup
25676 let file_path: Option<Arc<Path>> =
25677 self.buffer().read(cx).as_singleton().and_then(|buffer| {
25678 project::File::from_dyn(buffer.read(cx).file())
25679 .map(|file| Arc::from(file.abs_path(cx)))
25680 });
25681
25682 // Try file_folds (path-based) first, fallback to editor_folds (migration)
25683 let db = EditorDb::global(cx);
25684 let (folds, needs_migration) = if let Some(ref path) = file_path {
25685 if let Some(folds) = db.get_file_folds(workspace_id, path).log_err()
25686 && !folds.is_empty()
25687 {
25688 (Some(folds), false)
25689 } else if let Some(folds) = db.get_editor_folds(item_id, workspace_id).log_err()
25690 && !folds.is_empty()
25691 {
25692 // Found old editor_folds data, will migrate to file_folds
25693 (Some(folds), true)
25694 } else {
25695 (None, false)
25696 }
25697 } else {
25698 // No file path, try editor_folds as fallback
25699 let folds = db.get_editor_folds(item_id, workspace_id).log_err();
25700 (folds.filter(|f| !f.is_empty()), false)
25701 };
25702
25703 if let Some(folds) = folds {
25704 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25705 let snapshot_len = snapshot.len().0;
25706
25707 // Helper: search for fingerprint in buffer, return offset if found
25708 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
25709 // Ensure we start at a character boundary (defensive)
25710 let search_start = snapshot
25711 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
25712 .0;
25713 let search_end = snapshot_len.saturating_sub(fingerprint.len());
25714
25715 let mut byte_offset = search_start;
25716 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
25717 if byte_offset > search_end {
25718 break;
25719 }
25720 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
25721 return Some(byte_offset);
25722 }
25723 byte_offset += ch.len_utf8();
25724 }
25725 None
25726 };
25727
25728 // Track search position to handle duplicate fingerprints correctly.
25729 // Folds are stored in document order, so we advance after each match.
25730 let mut search_start = 0usize;
25731
25732 // Collect db_folds for migration (only folds with valid fingerprints)
25733 let mut db_folds_for_migration: Vec<(usize, usize, String, String)> = Vec::new();
25734
25735 let valid_folds: Vec<_> = folds
25736 .into_iter()
25737 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
25738 // Skip folds without fingerprints (old data before migration)
25739 let sfp = start_fp?;
25740 let efp = end_fp?;
25741 let efp_len = efp.len();
25742
25743 // Fast path: check if fingerprints match at stored offsets
25744 // Note: end_fp is content BEFORE fold end, so check at (stored_end - efp_len)
25745 let start_matches = stored_start < snapshot_len
25746 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
25747 let efp_check_pos = stored_end.saturating_sub(efp_len);
25748 let end_matches = efp_check_pos >= stored_start
25749 && stored_end <= snapshot_len
25750 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
25751
25752 let (new_start, new_end) = if start_matches && end_matches {
25753 // Offsets unchanged, use stored values
25754 (stored_start, stored_end)
25755 } else if sfp == efp {
25756 // Short fold: identical fingerprints can only match once per search
25757 // Use stored fold length to compute new_end
25758 let new_start = find_fingerprint(&sfp, search_start)?;
25759 let fold_len = stored_end - stored_start;
25760 let new_end = new_start + fold_len;
25761 (new_start, new_end)
25762 } else {
25763 // Slow path: search for fingerprints in buffer
25764 let new_start = find_fingerprint(&sfp, search_start)?;
25765 // Search for end_fp after start, then add efp_len to get actual fold end
25766 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
25767 let new_end = efp_pos + efp_len;
25768 (new_start, new_end)
25769 };
25770
25771 // Advance search position for next fold
25772 search_start = new_end;
25773
25774 // Validate fold makes sense (end must be after start)
25775 if new_end <= new_start {
25776 return None;
25777 }
25778
25779 // Collect for migration if needed
25780 if needs_migration {
25781 db_folds_for_migration.push((new_start, new_end, sfp, efp));
25782 }
25783
25784 Some(
25785 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
25786 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
25787 )
25788 })
25789 .collect();
25790
25791 if !valid_folds.is_empty() {
25792 self.fold_ranges(valid_folds, false, window, cx);
25793
25794 // Migrate from editor_folds to file_folds if we loaded from old table
25795 if needs_migration {
25796 if let Some(ref path) = file_path {
25797 let path = path.clone();
25798 let db = EditorDb::global(cx);
25799 cx.spawn(async move |_, _| {
25800 db.save_file_folds(workspace_id, path, db_folds_for_migration)
25801 .await
25802 .log_err();
25803 })
25804 .detach();
25805 }
25806 }
25807 }
25808 }
25809
25810 if let Some(selections) = db.get_editor_selections(item_id, workspace_id).log_err()
25811 && !selections.is_empty()
25812 {
25813 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25814 // skip adding the initial selection to selection history
25815 self.selection_history.mode = SelectionHistoryMode::Skipping;
25816 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25817 s.select_ranges(selections.into_iter().map(|(start, end)| {
25818 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
25819 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
25820 }));
25821 });
25822 self.selection_history.mode = SelectionHistoryMode::Normal;
25823 };
25824 }
25825
25826 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
25827 }
25828
25829 /// Load folds from the file_folds database table by file path.
25830 /// Used when manually opening a file that was previously closed.
25831 fn load_folds_from_db(
25832 &mut self,
25833 workspace_id: WorkspaceId,
25834 file_path: PathBuf,
25835 window: &mut Window,
25836 cx: &mut Context<Editor>,
25837 ) {
25838 if self.mode.is_minimap()
25839 || WorkspaceSettings::get(None, cx).restore_on_startup
25840 == RestoreOnStartupBehavior::EmptyTab
25841 {
25842 return;
25843 }
25844
25845 let Some(folds) = EditorDb::global(cx)
25846 .get_file_folds(workspace_id, &file_path)
25847 .log_err()
25848 else {
25849 return;
25850 };
25851 if folds.is_empty() {
25852 return;
25853 }
25854
25855 let snapshot = self.buffer.read(cx).snapshot(cx);
25856 let snapshot_len = snapshot.len().0;
25857
25858 // Helper: search for fingerprint in buffer, return offset if found
25859 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
25860 let search_start = snapshot
25861 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
25862 .0;
25863 let search_end = snapshot_len.saturating_sub(fingerprint.len());
25864
25865 let mut byte_offset = search_start;
25866 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
25867 if byte_offset > search_end {
25868 break;
25869 }
25870 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
25871 return Some(byte_offset);
25872 }
25873 byte_offset += ch.len_utf8();
25874 }
25875 None
25876 };
25877
25878 let mut search_start = 0usize;
25879
25880 let valid_folds: Vec<_> = folds
25881 .into_iter()
25882 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
25883 let sfp = start_fp?;
25884 let efp = end_fp?;
25885 let efp_len = efp.len();
25886
25887 let start_matches = stored_start < snapshot_len
25888 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
25889 let efp_check_pos = stored_end.saturating_sub(efp_len);
25890 let end_matches = efp_check_pos >= stored_start
25891 && stored_end <= snapshot_len
25892 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
25893
25894 let (new_start, new_end) = if start_matches && end_matches {
25895 (stored_start, stored_end)
25896 } else if sfp == efp {
25897 let new_start = find_fingerprint(&sfp, search_start)?;
25898 let fold_len = stored_end - stored_start;
25899 let new_end = new_start + fold_len;
25900 (new_start, new_end)
25901 } else {
25902 let new_start = find_fingerprint(&sfp, search_start)?;
25903 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
25904 let new_end = efp_pos + efp_len;
25905 (new_start, new_end)
25906 };
25907
25908 search_start = new_end;
25909
25910 if new_end <= new_start {
25911 return None;
25912 }
25913
25914 Some(
25915 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
25916 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
25917 )
25918 })
25919 .collect();
25920
25921 if !valid_folds.is_empty() {
25922 self.fold_ranges(valid_folds, false, window, cx);
25923 }
25924 }
25925
25926 fn lsp_data_enabled(&self) -> bool {
25927 self.enable_lsp_data && self.mode().is_full()
25928 }
25929
25930 fn update_lsp_data(
25931 &mut self,
25932 for_buffer: Option<BufferId>,
25933 window: &mut Window,
25934 cx: &mut Context<'_, Self>,
25935 ) {
25936 if !self.lsp_data_enabled() {
25937 return;
25938 }
25939
25940 if let Some(buffer_id) = for_buffer {
25941 self.pull_diagnostics(buffer_id, window, cx);
25942 }
25943 self.refresh_semantic_tokens(for_buffer, None, cx);
25944 self.refresh_document_colors(for_buffer, window, cx);
25945 self.refresh_folding_ranges(for_buffer, window, cx);
25946 self.refresh_document_symbols(for_buffer, cx);
25947 }
25948
25949 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
25950 if !self.lsp_data_enabled() {
25951 return;
25952 }
25953 let visible_buffers: Vec<_> = self
25954 .visible_buffers(cx)
25955 .into_iter()
25956 .filter(|buffer| self.is_lsp_relevant(buffer.read(cx).file(), cx))
25957 .collect();
25958 for visible_buffer in visible_buffers {
25959 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
25960 }
25961 }
25962
25963 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
25964 if !self.lsp_data_enabled() {
25965 return;
25966 }
25967
25968 if !self.registered_buffers.contains_key(&buffer_id)
25969 && let Some(project) = self.project.as_ref()
25970 {
25971 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
25972 project.update(cx, |project, cx| {
25973 self.registered_buffers.insert(
25974 buffer_id,
25975 project.register_buffer_with_language_servers(&buffer, cx),
25976 );
25977 });
25978 } else {
25979 self.registered_buffers.remove(&buffer_id);
25980 }
25981 }
25982 }
25983
25984 fn create_style(&self, cx: &App) -> EditorStyle {
25985 let settings = ThemeSettings::get_global(cx);
25986
25987 let mut text_style = match self.mode {
25988 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
25989 color: cx.theme().colors().editor_foreground,
25990 font_family: settings.ui_font.family.clone(),
25991 font_features: settings.ui_font.features.clone(),
25992 font_fallbacks: settings.ui_font.fallbacks.clone(),
25993 font_size: rems(0.875).into(),
25994 font_weight: settings.ui_font.weight,
25995 line_height: relative(settings.buffer_line_height.value()),
25996 ..Default::default()
25997 },
25998 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
25999 color: cx.theme().colors().editor_foreground,
26000 font_family: settings.buffer_font.family.clone(),
26001 font_features: settings.buffer_font.features.clone(),
26002 font_fallbacks: settings.buffer_font.fallbacks.clone(),
26003 font_size: settings.buffer_font_size(cx).into(),
26004 font_weight: settings.buffer_font.weight,
26005 line_height: relative(settings.buffer_line_height.value()),
26006 ..Default::default()
26007 },
26008 };
26009 if let Some(text_style_refinement) = &self.text_style_refinement {
26010 text_style.refine(text_style_refinement)
26011 }
26012
26013 let background = match self.mode {
26014 EditorMode::SingleLine => cx.theme().system().transparent,
26015 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
26016 EditorMode::Full { .. } => cx.theme().colors().editor_background,
26017 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
26018 };
26019
26020 EditorStyle {
26021 background,
26022 border: cx.theme().colors().border,
26023 local_player: cx.theme().players().local(),
26024 text: text_style,
26025 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
26026 syntax: cx.theme().syntax().clone(),
26027 status: cx.theme().status().clone(),
26028 inlay_hints_style: make_inlay_hints_style(cx),
26029 edit_prediction_styles: make_suggestion_styles(cx),
26030 unnecessary_code_fade: settings.unnecessary_code_fade,
26031 show_underlines: self.diagnostics_enabled(),
26032 }
26033 }
26034
26035 fn breadcrumbs_inner(&self, cx: &App) -> Option<Vec<HighlightedText>> {
26036 let multibuffer = self.buffer().read(cx);
26037 let is_singleton = multibuffer.is_singleton();
26038 let (buffer_id, symbols) = self.outline_symbols_at_cursor.as_ref()?;
26039 let buffer = multibuffer.buffer(*buffer_id)?;
26040
26041 let buffer = buffer.read(cx);
26042 // In a multi-buffer layout, we don't want to include the filename in the breadcrumbs
26043 let mut breadcrumbs = if is_singleton {
26044 let text = self.breadcrumb_header.clone().unwrap_or_else(|| {
26045 buffer
26046 .snapshot()
26047 .resolve_file_path(
26048 self.project
26049 .as_ref()
26050 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
26051 .unwrap_or_default(),
26052 cx,
26053 )
26054 .unwrap_or_else(|| {
26055 if multibuffer.is_singleton() {
26056 multibuffer.title(cx).to_string()
26057 } else {
26058 "untitled".to_string()
26059 }
26060 })
26061 });
26062 vec![HighlightedText {
26063 text: text.into(),
26064 highlights: vec![],
26065 }]
26066 } else {
26067 vec![]
26068 };
26069
26070 breadcrumbs.extend(symbols.iter().map(|symbol| HighlightedText {
26071 text: symbol.text.clone().into(),
26072 highlights: symbol.highlight_ranges.clone(),
26073 }));
26074 Some(breadcrumbs)
26075 }
26076
26077 fn disable_lsp_data(&mut self) {
26078 self.enable_lsp_data = false;
26079 }
26080
26081 fn disable_runnables(&mut self) {
26082 self.enable_runnables = false;
26083 }
26084
26085 fn update_data_on_scroll(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) {
26086 self.register_visible_buffers(cx);
26087 self.colorize_brackets(false, cx);
26088 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
26089 if !self.buffer().read(cx).is_singleton() {
26090 self.update_lsp_data(None, window, cx);
26091 self.refresh_runnables(None, window, cx);
26092 }
26093 }
26094}
26095
26096fn edit_for_markdown_paste<'a>(
26097 buffer: &MultiBufferSnapshot,
26098 range: Range<MultiBufferOffset>,
26099 to_insert: &'a str,
26100 url: Option<url::Url>,
26101) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
26102 if url.is_none() {
26103 return (range, Cow::Borrowed(to_insert));
26104 };
26105
26106 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
26107
26108 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
26109 Cow::Borrowed(to_insert)
26110 } else {
26111 Cow::Owned(format!("[{old_text}]({to_insert})"))
26112 };
26113 (range, new_text)
26114}
26115
26116fn process_completion_for_edit(
26117 completion: &Completion,
26118 intent: CompletionIntent,
26119 buffer: &Entity<Buffer>,
26120 cursor_position: &text::Anchor,
26121 cx: &mut Context<Editor>,
26122) -> CompletionEdit {
26123 let buffer = buffer.read(cx);
26124 let buffer_snapshot = buffer.snapshot();
26125 let (snippet, new_text) = if completion.is_snippet() {
26126 let mut snippet_source = completion.new_text.clone();
26127 // Workaround for typescript language server issues so that methods don't expand within
26128 // strings and functions with type expressions. The previous point is used because the query
26129 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
26130 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
26131 let previous_point = if previous_point.column > 0 {
26132 cursor_position.to_previous_offset(&buffer_snapshot)
26133 } else {
26134 cursor_position.to_offset(&buffer_snapshot)
26135 };
26136 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
26137 && scope.prefers_label_for_snippet_in_completion()
26138 && let Some(label) = completion.label()
26139 && matches!(
26140 completion.kind(),
26141 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
26142 )
26143 {
26144 snippet_source = label;
26145 }
26146 match Snippet::parse(&snippet_source).log_err() {
26147 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
26148 None => (None, completion.new_text.clone()),
26149 }
26150 } else {
26151 (None, completion.new_text.clone())
26152 };
26153
26154 let mut range_to_replace = {
26155 let replace_range = &completion.replace_range;
26156 if let CompletionSource::Lsp {
26157 insert_range: Some(insert_range),
26158 ..
26159 } = &completion.source
26160 {
26161 debug_assert_eq!(
26162 insert_range.start, replace_range.start,
26163 "insert_range and replace_range should start at the same position"
26164 );
26165 debug_assert!(
26166 insert_range
26167 .start
26168 .cmp(cursor_position, &buffer_snapshot)
26169 .is_le(),
26170 "insert_range should start before or at cursor position"
26171 );
26172 debug_assert!(
26173 replace_range
26174 .start
26175 .cmp(cursor_position, &buffer_snapshot)
26176 .is_le(),
26177 "replace_range should start before or at cursor position"
26178 );
26179
26180 let should_replace = match intent {
26181 CompletionIntent::CompleteWithInsert => false,
26182 CompletionIntent::CompleteWithReplace => true,
26183 CompletionIntent::Complete | CompletionIntent::Compose => {
26184 let insert_mode = LanguageSettings::for_buffer(&buffer, cx)
26185 .completions
26186 .lsp_insert_mode;
26187 match insert_mode {
26188 LspInsertMode::Insert => false,
26189 LspInsertMode::Replace => true,
26190 LspInsertMode::ReplaceSubsequence => {
26191 let mut text_to_replace = buffer.chars_for_range(
26192 buffer.anchor_before(replace_range.start)
26193 ..buffer.anchor_after(replace_range.end),
26194 );
26195 let mut current_needle = text_to_replace.next();
26196 for haystack_ch in completion.label.text.chars() {
26197 if let Some(needle_ch) = current_needle
26198 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
26199 {
26200 current_needle = text_to_replace.next();
26201 }
26202 }
26203 current_needle.is_none()
26204 }
26205 LspInsertMode::ReplaceSuffix => {
26206 if replace_range
26207 .end
26208 .cmp(cursor_position, &buffer_snapshot)
26209 .is_gt()
26210 {
26211 let range_after_cursor = *cursor_position..replace_range.end;
26212 let text_after_cursor = buffer
26213 .text_for_range(
26214 buffer.anchor_before(range_after_cursor.start)
26215 ..buffer.anchor_after(range_after_cursor.end),
26216 )
26217 .collect::<String>()
26218 .to_ascii_lowercase();
26219 completion
26220 .label
26221 .text
26222 .to_ascii_lowercase()
26223 .ends_with(&text_after_cursor)
26224 } else {
26225 true
26226 }
26227 }
26228 }
26229 }
26230 };
26231
26232 if should_replace {
26233 replace_range.clone()
26234 } else {
26235 insert_range.clone()
26236 }
26237 } else {
26238 replace_range.clone()
26239 }
26240 };
26241
26242 if range_to_replace
26243 .end
26244 .cmp(cursor_position, &buffer_snapshot)
26245 .is_lt()
26246 {
26247 range_to_replace.end = *cursor_position;
26248 }
26249
26250 CompletionEdit {
26251 new_text,
26252 replace_range: range_to_replace,
26253 snippet,
26254 }
26255}
26256
26257struct CompletionEdit {
26258 new_text: String,
26259 replace_range: Range<text::Anchor>,
26260 snippet: Option<Snippet>,
26261}
26262
26263fn comment_delimiter_for_newline(
26264 start_point: &Point,
26265 buffer: &MultiBufferSnapshot,
26266 language: &LanguageScope,
26267) -> Option<Arc<str>> {
26268 let delimiters = language.line_comment_prefixes();
26269 let max_len_of_delimiter = delimiters.iter().map(|delimiter| delimiter.len()).max()?;
26270 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
26271
26272 let num_of_whitespaces = snapshot
26273 .chars_for_range(range.clone())
26274 .take_while(|c| c.is_whitespace())
26275 .count();
26276 let comment_candidate = snapshot
26277 .chars_for_range(range.clone())
26278 .skip(num_of_whitespaces)
26279 .take(max_len_of_delimiter + 2)
26280 .collect::<String>();
26281 let (delimiter, trimmed_len, is_repl) = delimiters
26282 .iter()
26283 .filter_map(|delimiter| {
26284 let prefix = delimiter.trim_end();
26285 if comment_candidate.starts_with(prefix) {
26286 let is_repl = if let Some(stripped_comment) = comment_candidate.strip_prefix(prefix)
26287 {
26288 stripped_comment.starts_with(" %%")
26289 } else {
26290 false
26291 };
26292 Some((delimiter, prefix.len(), is_repl))
26293 } else {
26294 None
26295 }
26296 })
26297 .max_by_key(|(_, len, _)| *len)?;
26298
26299 if let Some(BlockCommentConfig {
26300 start: block_start, ..
26301 }) = language.block_comment()
26302 {
26303 let block_start_trimmed = block_start.trim_end();
26304 if block_start_trimmed.starts_with(delimiter.trim_end()) {
26305 let line_content = snapshot
26306 .chars_for_range(range.clone())
26307 .skip(num_of_whitespaces)
26308 .take(block_start_trimmed.len())
26309 .collect::<String>();
26310
26311 if line_content.starts_with(block_start_trimmed) {
26312 return None;
26313 }
26314 }
26315 }
26316
26317 let cursor_is_placed_after_comment_marker =
26318 num_of_whitespaces + trimmed_len <= start_point.column as usize;
26319 if cursor_is_placed_after_comment_marker {
26320 if !is_repl {
26321 return Some(delimiter.clone());
26322 }
26323
26324 let line_content_after_cursor: String = snapshot
26325 .chars_for_range(range)
26326 .skip(start_point.column as usize)
26327 .collect();
26328
26329 if line_content_after_cursor.trim().is_empty() {
26330 return None;
26331 } else {
26332 return Some(delimiter.clone());
26333 }
26334 } else {
26335 None
26336 }
26337}
26338
26339fn documentation_delimiter_for_newline(
26340 start_point: &Point,
26341 buffer: &MultiBufferSnapshot,
26342 language: &LanguageScope,
26343 newline_config: &mut NewlineConfig,
26344) -> Option<Arc<str>> {
26345 let BlockCommentConfig {
26346 start: start_tag,
26347 end: end_tag,
26348 prefix: delimiter,
26349 tab_size: len,
26350 } = language.documentation_comment()?;
26351 let is_within_block_comment = buffer
26352 .language_scope_at(*start_point)
26353 .is_some_and(|scope| scope.override_name() == Some("comment"));
26354 if !is_within_block_comment {
26355 return None;
26356 }
26357
26358 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
26359
26360 let num_of_whitespaces = snapshot
26361 .chars_for_range(range.clone())
26362 .take_while(|c| c.is_whitespace())
26363 .count();
26364
26365 // 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.
26366 let column = start_point.column;
26367 let cursor_is_after_start_tag = {
26368 let start_tag_len = start_tag.len();
26369 let start_tag_line = snapshot
26370 .chars_for_range(range.clone())
26371 .skip(num_of_whitespaces)
26372 .take(start_tag_len)
26373 .collect::<String>();
26374 if start_tag_line.starts_with(start_tag.as_ref()) {
26375 num_of_whitespaces + start_tag_len <= column as usize
26376 } else {
26377 false
26378 }
26379 };
26380
26381 let cursor_is_after_delimiter = {
26382 let delimiter_trim = delimiter.trim_end();
26383 let delimiter_line = snapshot
26384 .chars_for_range(range.clone())
26385 .skip(num_of_whitespaces)
26386 .take(delimiter_trim.len())
26387 .collect::<String>();
26388 if delimiter_line.starts_with(delimiter_trim) {
26389 num_of_whitespaces + delimiter_trim.len() <= column as usize
26390 } else {
26391 false
26392 }
26393 };
26394
26395 let mut needs_extra_line = false;
26396 let mut extra_line_additional_indent = IndentSize::spaces(0);
26397
26398 let cursor_is_before_end_tag_if_exists = {
26399 let mut char_position = 0u32;
26400 let mut end_tag_offset = None;
26401
26402 'outer: for chunk in snapshot.text_for_range(range) {
26403 if let Some(byte_pos) = chunk.find(&**end_tag) {
26404 let chars_before_match = chunk[..byte_pos].chars().count() as u32;
26405 end_tag_offset = Some(char_position + chars_before_match);
26406 break 'outer;
26407 }
26408 char_position += chunk.chars().count() as u32;
26409 }
26410
26411 if let Some(end_tag_offset) = end_tag_offset {
26412 let cursor_is_before_end_tag = column <= end_tag_offset;
26413 if cursor_is_after_start_tag {
26414 if cursor_is_before_end_tag {
26415 needs_extra_line = true;
26416 }
26417 let cursor_is_at_start_of_end_tag = column == end_tag_offset;
26418 if cursor_is_at_start_of_end_tag {
26419 extra_line_additional_indent.len = *len;
26420 }
26421 }
26422 cursor_is_before_end_tag
26423 } else {
26424 true
26425 }
26426 };
26427
26428 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
26429 && cursor_is_before_end_tag_if_exists
26430 {
26431 let additional_indent = if cursor_is_after_start_tag {
26432 IndentSize::spaces(*len)
26433 } else {
26434 IndentSize::spaces(0)
26435 };
26436
26437 *newline_config = NewlineConfig::Newline {
26438 additional_indent,
26439 extra_line_additional_indent: if needs_extra_line {
26440 Some(extra_line_additional_indent)
26441 } else {
26442 None
26443 },
26444 prevent_auto_indent: true,
26445 };
26446 Some(delimiter.clone())
26447 } else {
26448 None
26449 }
26450}
26451
26452const ORDERED_LIST_MAX_MARKER_LEN: usize = 16;
26453
26454fn list_delimiter_for_newline(
26455 start_point: &Point,
26456 buffer: &MultiBufferSnapshot,
26457 language: &LanguageScope,
26458 newline_config: &mut NewlineConfig,
26459) -> Option<Arc<str>> {
26460 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
26461
26462 let num_of_whitespaces = snapshot
26463 .chars_for_range(range.clone())
26464 .take_while(|c| c.is_whitespace())
26465 .count();
26466
26467 let task_list_entries: Vec<_> = language
26468 .task_list()
26469 .into_iter()
26470 .flat_map(|config| {
26471 config
26472 .prefixes
26473 .iter()
26474 .map(|prefix| (prefix.as_ref(), config.continuation.as_ref()))
26475 })
26476 .collect();
26477 let unordered_list_entries: Vec<_> = language
26478 .unordered_list()
26479 .iter()
26480 .map(|marker| (marker.as_ref(), marker.as_ref()))
26481 .collect();
26482
26483 let all_entries: Vec<_> = task_list_entries
26484 .into_iter()
26485 .chain(unordered_list_entries)
26486 .collect();
26487
26488 if let Some(max_prefix_len) = all_entries.iter().map(|(p, _)| p.len()).max() {
26489 let candidate: String = snapshot
26490 .chars_for_range(range.clone())
26491 .skip(num_of_whitespaces)
26492 .take(max_prefix_len)
26493 .collect();
26494
26495 if let Some((prefix, continuation)) = all_entries
26496 .iter()
26497 .filter(|(prefix, _)| candidate.starts_with(*prefix))
26498 .max_by_key(|(prefix, _)| prefix.len())
26499 {
26500 let end_of_prefix = num_of_whitespaces + prefix.len();
26501 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
26502 let has_content_after_marker = snapshot
26503 .chars_for_range(range)
26504 .skip(end_of_prefix)
26505 .any(|c| !c.is_whitespace());
26506
26507 if has_content_after_marker && cursor_is_after_prefix {
26508 return Some((*continuation).into());
26509 }
26510
26511 if start_point.column as usize == end_of_prefix {
26512 if num_of_whitespaces == 0 {
26513 *newline_config = NewlineConfig::ClearCurrentLine;
26514 } else {
26515 *newline_config = NewlineConfig::UnindentCurrentLine {
26516 continuation: (*continuation).into(),
26517 };
26518 }
26519 }
26520
26521 return None;
26522 }
26523 }
26524
26525 let candidate: String = snapshot
26526 .chars_for_range(range.clone())
26527 .skip(num_of_whitespaces)
26528 .take(ORDERED_LIST_MAX_MARKER_LEN)
26529 .collect();
26530
26531 for ordered_config in language.ordered_list() {
26532 let regex = match Regex::new(&ordered_config.pattern) {
26533 Ok(r) => r,
26534 Err(_) => continue,
26535 };
26536
26537 if let Some(captures) = regex.captures(&candidate) {
26538 let full_match = captures.get(0)?;
26539 let marker_len = full_match.len();
26540 let end_of_prefix = num_of_whitespaces + marker_len;
26541 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
26542
26543 let has_content_after_marker = snapshot
26544 .chars_for_range(range)
26545 .skip(end_of_prefix)
26546 .any(|c| !c.is_whitespace());
26547
26548 if has_content_after_marker && cursor_is_after_prefix {
26549 let number: u32 = captures.get(1)?.as_str().parse().ok()?;
26550 let continuation = ordered_config
26551 .format
26552 .replace("{1}", &(number + 1).to_string());
26553 return Some(continuation.into());
26554 }
26555
26556 if start_point.column as usize == end_of_prefix {
26557 let continuation = ordered_config.format.replace("{1}", "1");
26558 if num_of_whitespaces == 0 {
26559 *newline_config = NewlineConfig::ClearCurrentLine;
26560 } else {
26561 *newline_config = NewlineConfig::UnindentCurrentLine {
26562 continuation: continuation.into(),
26563 };
26564 }
26565 }
26566
26567 return None;
26568 }
26569 }
26570
26571 None
26572}
26573
26574fn is_list_prefix_row(
26575 row: MultiBufferRow,
26576 buffer: &MultiBufferSnapshot,
26577 language: &LanguageScope,
26578) -> bool {
26579 let Some((snapshot, range)) = buffer.buffer_line_for_row(row) else {
26580 return false;
26581 };
26582
26583 let num_of_whitespaces = snapshot
26584 .chars_for_range(range.clone())
26585 .take_while(|c| c.is_whitespace())
26586 .count();
26587
26588 let task_list_prefixes: Vec<_> = language
26589 .task_list()
26590 .into_iter()
26591 .flat_map(|config| {
26592 config
26593 .prefixes
26594 .iter()
26595 .map(|p| p.as_ref())
26596 .collect::<Vec<_>>()
26597 })
26598 .collect();
26599 let unordered_list_markers: Vec<_> = language
26600 .unordered_list()
26601 .iter()
26602 .map(|marker| marker.as_ref())
26603 .collect();
26604 let all_prefixes: Vec<_> = task_list_prefixes
26605 .into_iter()
26606 .chain(unordered_list_markers)
26607 .collect();
26608 if let Some(max_prefix_len) = all_prefixes.iter().map(|p| p.len()).max() {
26609 let candidate: String = snapshot
26610 .chars_for_range(range.clone())
26611 .skip(num_of_whitespaces)
26612 .take(max_prefix_len)
26613 .collect();
26614 if all_prefixes
26615 .iter()
26616 .any(|prefix| candidate.starts_with(*prefix))
26617 {
26618 return true;
26619 }
26620 }
26621
26622 let ordered_list_candidate: String = snapshot
26623 .chars_for_range(range)
26624 .skip(num_of_whitespaces)
26625 .take(ORDERED_LIST_MAX_MARKER_LEN)
26626 .collect();
26627 for ordered_config in language.ordered_list() {
26628 let regex = match Regex::new(&ordered_config.pattern) {
26629 Ok(r) => r,
26630 Err(_) => continue,
26631 };
26632 if let Some(captures) = regex.captures(&ordered_list_candidate) {
26633 return captures.get(0).is_some();
26634 }
26635 }
26636
26637 false
26638}
26639
26640#[derive(Debug)]
26641enum NewlineConfig {
26642 /// Insert newline with optional additional indent and optional extra blank line
26643 Newline {
26644 additional_indent: IndentSize,
26645 extra_line_additional_indent: Option<IndentSize>,
26646 prevent_auto_indent: bool,
26647 },
26648 /// Clear the current line
26649 ClearCurrentLine,
26650 /// Unindent the current line and add continuation
26651 UnindentCurrentLine { continuation: Arc<str> },
26652}
26653
26654impl NewlineConfig {
26655 fn has_extra_line(&self) -> bool {
26656 matches!(
26657 self,
26658 Self::Newline {
26659 extra_line_additional_indent: Some(_),
26660 ..
26661 }
26662 )
26663 }
26664
26665 fn insert_extra_newline_brackets(
26666 buffer: &MultiBufferSnapshot,
26667 range: Range<MultiBufferOffset>,
26668 language: &language::LanguageScope,
26669 ) -> bool {
26670 let leading_whitespace_len = buffer
26671 .reversed_chars_at(range.start)
26672 .take_while(|c| c.is_whitespace() && *c != '\n')
26673 .map(|c| c.len_utf8())
26674 .sum::<usize>();
26675 let trailing_whitespace_len = buffer
26676 .chars_at(range.end)
26677 .take_while(|c| c.is_whitespace() && *c != '\n')
26678 .map(|c| c.len_utf8())
26679 .sum::<usize>();
26680 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
26681
26682 language.brackets().any(|(pair, enabled)| {
26683 let pair_start = pair.start.trim_end();
26684 let pair_end = pair.end.trim_start();
26685
26686 enabled
26687 && pair.newline
26688 && buffer.contains_str_at(range.end, pair_end)
26689 && buffer.contains_str_at(
26690 range.start.saturating_sub_usize(pair_start.len()),
26691 pair_start,
26692 )
26693 })
26694 }
26695
26696 fn insert_extra_newline_tree_sitter(
26697 buffer: &MultiBufferSnapshot,
26698 range: Range<MultiBufferOffset>,
26699 ) -> bool {
26700 let (buffer, range) = match buffer
26701 .range_to_buffer_ranges(range.start..range.end)
26702 .as_slice()
26703 {
26704 [(buffer_snapshot, range, _)] => (buffer_snapshot.clone(), range.clone()),
26705 _ => return false,
26706 };
26707 let pair = {
26708 let mut result: Option<BracketMatch<usize>> = None;
26709
26710 for pair in buffer
26711 .all_bracket_ranges(range.start.0..range.end.0)
26712 .filter(move |pair| {
26713 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
26714 })
26715 {
26716 let len = pair.close_range.end - pair.open_range.start;
26717
26718 if let Some(existing) = &result {
26719 let existing_len = existing.close_range.end - existing.open_range.start;
26720 if len > existing_len {
26721 continue;
26722 }
26723 }
26724
26725 result = Some(pair);
26726 }
26727
26728 result
26729 };
26730 let Some(pair) = pair else {
26731 return false;
26732 };
26733 pair.newline_only
26734 && buffer
26735 .chars_for_range(pair.open_range.end..range.start.0)
26736 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
26737 .all(|c| c.is_whitespace() && c != '\n')
26738 }
26739}
26740
26741fn update_uncommitted_diff_for_buffer(
26742 editor: Entity<Editor>,
26743 project: &Entity<Project>,
26744 buffers: impl IntoIterator<Item = Entity<Buffer>>,
26745 buffer: Entity<MultiBuffer>,
26746 cx: &mut App,
26747) -> Task<()> {
26748 let mut tasks = Vec::new();
26749 project.update(cx, |project, cx| {
26750 for buffer in buffers {
26751 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
26752 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
26753 }
26754 }
26755 });
26756 cx.spawn(async move |cx| {
26757 let diffs = future::join_all(tasks).await;
26758 if editor.read_with(cx, |editor, _cx| editor.temporary_diff_override) {
26759 return;
26760 }
26761
26762 buffer.update(cx, |buffer, cx| {
26763 for diff in diffs.into_iter().flatten() {
26764 buffer.add_diff(diff, cx);
26765 }
26766 });
26767 })
26768}
26769
26770fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
26771 let tab_size = tab_size.get() as usize;
26772 let mut width = offset;
26773
26774 for ch in text.chars() {
26775 width += if ch == '\t' {
26776 tab_size - (width % tab_size)
26777 } else {
26778 1
26779 };
26780 }
26781
26782 width - offset
26783}
26784
26785#[cfg(test)]
26786mod tests {
26787 use super::*;
26788
26789 #[test]
26790 fn test_string_size_with_expanded_tabs() {
26791 let nz = |val| NonZeroU32::new(val).unwrap();
26792 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
26793 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
26794 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
26795 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
26796 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
26797 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
26798 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
26799 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
26800 }
26801}
26802
26803/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
26804struct WordBreakingTokenizer<'a> {
26805 input: &'a str,
26806}
26807
26808impl<'a> WordBreakingTokenizer<'a> {
26809 fn new(input: &'a str) -> Self {
26810 Self { input }
26811 }
26812}
26813
26814fn is_char_ideographic(ch: char) -> bool {
26815 use unicode_script::Script::*;
26816 use unicode_script::UnicodeScript;
26817 matches!(ch.script(), Han | Tangut | Yi)
26818}
26819
26820fn is_grapheme_ideographic(text: &str) -> bool {
26821 text.chars().any(is_char_ideographic)
26822}
26823
26824fn is_grapheme_whitespace(text: &str) -> bool {
26825 text.chars().any(|x| x.is_whitespace())
26826}
26827
26828fn should_stay_with_preceding_ideograph(text: &str) -> bool {
26829 text.chars()
26830 .next()
26831 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
26832}
26833
26834#[derive(PartialEq, Eq, Debug, Clone, Copy)]
26835enum WordBreakToken<'a> {
26836 Word { token: &'a str, grapheme_len: usize },
26837 InlineWhitespace { token: &'a str, grapheme_len: usize },
26838 Newline,
26839}
26840
26841impl<'a> Iterator for WordBreakingTokenizer<'a> {
26842 /// Yields a span, the count of graphemes in the token, and whether it was
26843 /// whitespace. Note that it also breaks at word boundaries.
26844 type Item = WordBreakToken<'a>;
26845
26846 fn next(&mut self) -> Option<Self::Item> {
26847 use unicode_segmentation::UnicodeSegmentation;
26848 if self.input.is_empty() {
26849 return None;
26850 }
26851
26852 let mut iter = self.input.graphemes(true).peekable();
26853 let mut offset = 0;
26854 let mut grapheme_len = 0;
26855 if let Some(first_grapheme) = iter.next() {
26856 let is_newline = first_grapheme == "\n";
26857 let is_whitespace = is_grapheme_whitespace(first_grapheme);
26858 offset += first_grapheme.len();
26859 grapheme_len += 1;
26860 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
26861 if let Some(grapheme) = iter.peek().copied()
26862 && should_stay_with_preceding_ideograph(grapheme)
26863 {
26864 offset += grapheme.len();
26865 grapheme_len += 1;
26866 }
26867 } else {
26868 let mut words = self.input[offset..].split_word_bound_indices().peekable();
26869 let mut next_word_bound = words.peek().copied();
26870 if next_word_bound.is_some_and(|(i, _)| i == 0) {
26871 next_word_bound = words.next();
26872 }
26873 while let Some(grapheme) = iter.peek().copied() {
26874 if next_word_bound.is_some_and(|(i, _)| i == offset) {
26875 break;
26876 };
26877 if is_grapheme_whitespace(grapheme) != is_whitespace
26878 || (grapheme == "\n") != is_newline
26879 {
26880 break;
26881 };
26882 offset += grapheme.len();
26883 grapheme_len += 1;
26884 iter.next();
26885 }
26886 }
26887 let token = &self.input[..offset];
26888 self.input = &self.input[offset..];
26889 if token == "\n" {
26890 Some(WordBreakToken::Newline)
26891 } else if is_whitespace {
26892 Some(WordBreakToken::InlineWhitespace {
26893 token,
26894 grapheme_len,
26895 })
26896 } else {
26897 Some(WordBreakToken::Word {
26898 token,
26899 grapheme_len,
26900 })
26901 }
26902 } else {
26903 None
26904 }
26905 }
26906}
26907
26908#[test]
26909fn test_word_breaking_tokenizer() {
26910 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
26911 ("", &[]),
26912 (" ", &[whitespace(" ", 2)]),
26913 ("Ʒ", &[word("Ʒ", 1)]),
26914 ("Ǽ", &[word("Ǽ", 1)]),
26915 ("⋑", &[word("⋑", 1)]),
26916 ("⋑⋑", &[word("⋑⋑", 2)]),
26917 (
26918 "原理,进而",
26919 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
26920 ),
26921 (
26922 "hello world",
26923 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
26924 ),
26925 (
26926 "hello, world",
26927 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
26928 ),
26929 (
26930 " hello world",
26931 &[
26932 whitespace(" ", 2),
26933 word("hello", 5),
26934 whitespace(" ", 1),
26935 word("world", 5),
26936 ],
26937 ),
26938 (
26939 "这是什么 \n 钢笔",
26940 &[
26941 word("这", 1),
26942 word("是", 1),
26943 word("什", 1),
26944 word("么", 1),
26945 whitespace(" ", 1),
26946 newline(),
26947 whitespace(" ", 1),
26948 word("钢", 1),
26949 word("笔", 1),
26950 ],
26951 ),
26952 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
26953 ];
26954
26955 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26956 WordBreakToken::Word {
26957 token,
26958 grapheme_len,
26959 }
26960 }
26961
26962 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26963 WordBreakToken::InlineWhitespace {
26964 token,
26965 grapheme_len,
26966 }
26967 }
26968
26969 fn newline() -> WordBreakToken<'static> {
26970 WordBreakToken::Newline
26971 }
26972
26973 for (input, result) in tests {
26974 assert_eq!(
26975 WordBreakingTokenizer::new(input)
26976 .collect::<Vec<_>>()
26977 .as_slice(),
26978 *result,
26979 );
26980 }
26981}
26982
26983fn wrap_with_prefix(
26984 first_line_prefix: String,
26985 subsequent_lines_prefix: String,
26986 unwrapped_text: String,
26987 wrap_column: usize,
26988 tab_size: NonZeroU32,
26989 preserve_existing_whitespace: bool,
26990) -> String {
26991 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
26992 let subsequent_lines_prefix_len =
26993 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
26994 let mut wrapped_text = String::new();
26995 let mut current_line = first_line_prefix;
26996 let mut is_first_line = true;
26997
26998 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
26999 let mut current_line_len = first_line_prefix_len;
27000 let mut in_whitespace = false;
27001 for token in tokenizer {
27002 let have_preceding_whitespace = in_whitespace;
27003 match token {
27004 WordBreakToken::Word {
27005 token,
27006 grapheme_len,
27007 } => {
27008 in_whitespace = false;
27009 let current_prefix_len = if is_first_line {
27010 first_line_prefix_len
27011 } else {
27012 subsequent_lines_prefix_len
27013 };
27014 if current_line_len + grapheme_len > wrap_column
27015 && current_line_len != current_prefix_len
27016 {
27017 wrapped_text.push_str(current_line.trim_end());
27018 wrapped_text.push('\n');
27019 is_first_line = false;
27020 current_line = subsequent_lines_prefix.clone();
27021 current_line_len = subsequent_lines_prefix_len;
27022 }
27023 current_line.push_str(token);
27024 current_line_len += grapheme_len;
27025 }
27026 WordBreakToken::InlineWhitespace {
27027 mut token,
27028 mut grapheme_len,
27029 } => {
27030 in_whitespace = true;
27031 if have_preceding_whitespace && !preserve_existing_whitespace {
27032 continue;
27033 }
27034 if !preserve_existing_whitespace {
27035 // Keep a single whitespace grapheme as-is
27036 if let Some(first) =
27037 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
27038 {
27039 token = first;
27040 } else {
27041 token = " ";
27042 }
27043 grapheme_len = 1;
27044 }
27045 let current_prefix_len = if is_first_line {
27046 first_line_prefix_len
27047 } else {
27048 subsequent_lines_prefix_len
27049 };
27050 if current_line_len + grapheme_len > wrap_column {
27051 wrapped_text.push_str(current_line.trim_end());
27052 wrapped_text.push('\n');
27053 is_first_line = false;
27054 current_line = subsequent_lines_prefix.clone();
27055 current_line_len = subsequent_lines_prefix_len;
27056 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
27057 current_line.push_str(token);
27058 current_line_len += grapheme_len;
27059 }
27060 }
27061 WordBreakToken::Newline => {
27062 in_whitespace = true;
27063 let current_prefix_len = if is_first_line {
27064 first_line_prefix_len
27065 } else {
27066 subsequent_lines_prefix_len
27067 };
27068 if preserve_existing_whitespace {
27069 wrapped_text.push_str(current_line.trim_end());
27070 wrapped_text.push('\n');
27071 is_first_line = false;
27072 current_line = subsequent_lines_prefix.clone();
27073 current_line_len = subsequent_lines_prefix_len;
27074 } else if have_preceding_whitespace {
27075 continue;
27076 } else if current_line_len + 1 > wrap_column
27077 && current_line_len != current_prefix_len
27078 {
27079 wrapped_text.push_str(current_line.trim_end());
27080 wrapped_text.push('\n');
27081 is_first_line = false;
27082 current_line = subsequent_lines_prefix.clone();
27083 current_line_len = subsequent_lines_prefix_len;
27084 } else if current_line_len != current_prefix_len {
27085 current_line.push(' ');
27086 current_line_len += 1;
27087 }
27088 }
27089 }
27090 }
27091
27092 if !current_line.is_empty() {
27093 wrapped_text.push_str(¤t_line);
27094 }
27095 wrapped_text
27096}
27097
27098#[test]
27099fn test_wrap_with_prefix() {
27100 assert_eq!(
27101 wrap_with_prefix(
27102 "# ".to_string(),
27103 "# ".to_string(),
27104 "abcdefg".to_string(),
27105 4,
27106 NonZeroU32::new(4).unwrap(),
27107 false,
27108 ),
27109 "# abcdefg"
27110 );
27111 assert_eq!(
27112 wrap_with_prefix(
27113 "".to_string(),
27114 "".to_string(),
27115 "\thello world".to_string(),
27116 8,
27117 NonZeroU32::new(4).unwrap(),
27118 false,
27119 ),
27120 "hello\nworld"
27121 );
27122 assert_eq!(
27123 wrap_with_prefix(
27124 "// ".to_string(),
27125 "// ".to_string(),
27126 "xx \nyy zz aa bb cc".to_string(),
27127 12,
27128 NonZeroU32::new(4).unwrap(),
27129 false,
27130 ),
27131 "// xx yy zz\n// aa bb cc"
27132 );
27133 assert_eq!(
27134 wrap_with_prefix(
27135 String::new(),
27136 String::new(),
27137 "这是什么 \n 钢笔".to_string(),
27138 3,
27139 NonZeroU32::new(4).unwrap(),
27140 false,
27141 ),
27142 "这是什\n么 钢\n笔"
27143 );
27144 assert_eq!(
27145 wrap_with_prefix(
27146 String::new(),
27147 String::new(),
27148 format!("foo{}bar", '\u{2009}'), // thin space
27149 80,
27150 NonZeroU32::new(4).unwrap(),
27151 false,
27152 ),
27153 format!("foo{}bar", '\u{2009}')
27154 );
27155}
27156
27157pub trait CollaborationHub {
27158 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
27159 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
27160 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
27161}
27162
27163impl CollaborationHub for Entity<Project> {
27164 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
27165 self.read(cx).collaborators()
27166 }
27167
27168 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
27169 self.read(cx).user_store().read(cx).participant_indices()
27170 }
27171
27172 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
27173 let this = self.read(cx);
27174 let user_ids = this.collaborators().values().map(|c| c.user_id);
27175 this.user_store().read(cx).participant_names(user_ids, cx)
27176 }
27177}
27178
27179pub trait SemanticsProvider {
27180 fn hover(
27181 &self,
27182 buffer: &Entity<Buffer>,
27183 position: text::Anchor,
27184 cx: &mut App,
27185 ) -> Option<Task<Option<Vec<project::Hover>>>>;
27186
27187 fn inline_values(
27188 &self,
27189 buffer_handle: Entity<Buffer>,
27190 range: Range<text::Anchor>,
27191 cx: &mut App,
27192 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
27193
27194 fn applicable_inlay_chunks(
27195 &self,
27196 buffer: &Entity<Buffer>,
27197 ranges: &[Range<text::Anchor>],
27198 cx: &mut App,
27199 ) -> Vec<Range<BufferRow>>;
27200
27201 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
27202
27203 fn inlay_hints(
27204 &self,
27205 invalidate: InvalidationStrategy,
27206 buffer: Entity<Buffer>,
27207 ranges: Vec<Range<text::Anchor>>,
27208 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
27209 cx: &mut App,
27210 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
27211
27212 fn semantic_tokens(
27213 &self,
27214 buffer: Entity<Buffer>,
27215 refresh: Option<RefreshForServer>,
27216 cx: &mut App,
27217 ) -> Option<Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>>>;
27218
27219 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
27220
27221 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
27222
27223 fn document_highlights(
27224 &self,
27225 buffer: &Entity<Buffer>,
27226 position: text::Anchor,
27227 cx: &mut App,
27228 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
27229
27230 fn definitions(
27231 &self,
27232 buffer: &Entity<Buffer>,
27233 position: text::Anchor,
27234 kind: GotoDefinitionKind,
27235 cx: &mut App,
27236 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
27237
27238 fn range_for_rename(
27239 &self,
27240 buffer: &Entity<Buffer>,
27241 position: text::Anchor,
27242 cx: &mut App,
27243 ) -> Task<Result<Option<Range<text::Anchor>>>>;
27244
27245 fn perform_rename(
27246 &self,
27247 buffer: &Entity<Buffer>,
27248 position: text::Anchor,
27249 new_name: String,
27250 cx: &mut App,
27251 ) -> Option<Task<Result<ProjectTransaction>>>;
27252}
27253
27254pub trait CompletionProvider {
27255 fn completions(
27256 &self,
27257 buffer: &Entity<Buffer>,
27258 buffer_position: text::Anchor,
27259 trigger: CompletionContext,
27260 window: &mut Window,
27261 cx: &mut Context<Editor>,
27262 ) -> Task<Result<Vec<CompletionResponse>>>;
27263
27264 fn resolve_completions(
27265 &self,
27266 _buffer: Entity<Buffer>,
27267 _completion_indices: Vec<usize>,
27268 _completions: Rc<RefCell<Box<[Completion]>>>,
27269 _cx: &mut Context<Editor>,
27270 ) -> Task<Result<bool>> {
27271 Task::ready(Ok(false))
27272 }
27273
27274 fn apply_additional_edits_for_completion(
27275 &self,
27276 _buffer: Entity<Buffer>,
27277 _completions: Rc<RefCell<Box<[Completion]>>>,
27278 _completion_index: usize,
27279 _push_to_history: bool,
27280 _all_commit_ranges: Vec<Range<language::Anchor>>,
27281 _cx: &mut Context<Editor>,
27282 ) -> Task<Result<Option<language::Transaction>>> {
27283 Task::ready(Ok(None))
27284 }
27285
27286 fn is_completion_trigger(
27287 &self,
27288 buffer: &Entity<Buffer>,
27289 position: language::Anchor,
27290 text: &str,
27291 trigger_in_words: bool,
27292 cx: &mut Context<Editor>,
27293 ) -> bool;
27294
27295 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
27296
27297 fn sort_completions(&self) -> bool {
27298 true
27299 }
27300
27301 fn filter_completions(&self) -> bool {
27302 true
27303 }
27304
27305 fn show_snippets(&self) -> bool {
27306 false
27307 }
27308}
27309
27310pub trait CodeActionProvider {
27311 fn id(&self) -> Arc<str>;
27312
27313 fn code_actions(
27314 &self,
27315 buffer: &Entity<Buffer>,
27316 range: Range<text::Anchor>,
27317 window: &mut Window,
27318 cx: &mut App,
27319 ) -> Task<Result<Vec<CodeAction>>>;
27320
27321 fn apply_code_action(
27322 &self,
27323 buffer_handle: Entity<Buffer>,
27324 action: CodeAction,
27325 push_to_history: bool,
27326 window: &mut Window,
27327 cx: &mut App,
27328 ) -> Task<Result<ProjectTransaction>>;
27329}
27330
27331impl CodeActionProvider for Entity<Project> {
27332 fn id(&self) -> Arc<str> {
27333 "project".into()
27334 }
27335
27336 fn code_actions(
27337 &self,
27338 buffer: &Entity<Buffer>,
27339 range: Range<text::Anchor>,
27340 _window: &mut Window,
27341 cx: &mut App,
27342 ) -> Task<Result<Vec<CodeAction>>> {
27343 self.update(cx, |project, cx| {
27344 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
27345 let code_actions = project.code_actions(buffer, range, None, cx);
27346 cx.background_spawn(async move {
27347 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
27348 Ok(code_lens_actions
27349 .context("code lens fetch")?
27350 .into_iter()
27351 .flatten()
27352 .chain(
27353 code_actions
27354 .context("code action fetch")?
27355 .into_iter()
27356 .flatten(),
27357 )
27358 .collect())
27359 })
27360 })
27361 }
27362
27363 fn apply_code_action(
27364 &self,
27365 buffer_handle: Entity<Buffer>,
27366 action: CodeAction,
27367 push_to_history: bool,
27368 _window: &mut Window,
27369 cx: &mut App,
27370 ) -> Task<Result<ProjectTransaction>> {
27371 self.update(cx, |project, cx| {
27372 project.apply_code_action(buffer_handle, action, push_to_history, cx)
27373 })
27374 }
27375}
27376
27377fn snippet_completions(
27378 project: &Project,
27379 buffer: &Entity<Buffer>,
27380 buffer_anchor: text::Anchor,
27381 classifier: CharClassifier,
27382 cx: &mut App,
27383) -> Task<Result<CompletionResponse>> {
27384 let languages = buffer.read(cx).languages_at(buffer_anchor);
27385 let snippet_store = project.snippets().read(cx);
27386
27387 let scopes: Vec<_> = languages
27388 .iter()
27389 .filter_map(|language| {
27390 let language_name = language.lsp_id();
27391 let snippets = snippet_store.snippets_for(Some(language_name), cx);
27392
27393 if snippets.is_empty() {
27394 None
27395 } else {
27396 Some((language.default_scope(), snippets))
27397 }
27398 })
27399 .collect();
27400
27401 if scopes.is_empty() {
27402 return Task::ready(Ok(CompletionResponse {
27403 completions: vec![],
27404 display_options: CompletionDisplayOptions::default(),
27405 is_incomplete: false,
27406 }));
27407 }
27408
27409 let snapshot = buffer.read(cx).text_snapshot();
27410 let executor = cx.background_executor().clone();
27411
27412 cx.background_spawn(async move {
27413 let is_word_char = |c| classifier.is_word(c);
27414
27415 let mut is_incomplete = false;
27416 let mut completions: Vec<Completion> = Vec::new();
27417
27418 const MAX_PREFIX_LEN: usize = 128;
27419 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
27420 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
27421 let window_start = snapshot.clip_offset(window_start, Bias::Left);
27422
27423 let max_buffer_window: String = snapshot
27424 .text_for_range(window_start..buffer_offset)
27425 .collect();
27426
27427 if max_buffer_window.is_empty() {
27428 return Ok(CompletionResponse {
27429 completions: vec![],
27430 display_options: CompletionDisplayOptions::default(),
27431 is_incomplete: true,
27432 });
27433 }
27434
27435 for (_scope, snippets) in scopes.into_iter() {
27436 // Sort snippets by word count to match longer snippet prefixes first.
27437 let mut sorted_snippet_candidates = snippets
27438 .iter()
27439 .enumerate()
27440 .flat_map(|(snippet_ix, snippet)| {
27441 snippet
27442 .prefix
27443 .iter()
27444 .enumerate()
27445 .map(move |(prefix_ix, prefix)| {
27446 let word_count =
27447 snippet_candidate_suffixes(prefix, &is_word_char).count();
27448 ((snippet_ix, prefix_ix), prefix, word_count)
27449 })
27450 })
27451 .collect_vec();
27452 sorted_snippet_candidates
27453 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
27454
27455 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
27456
27457 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, &is_word_char)
27458 .take(
27459 sorted_snippet_candidates
27460 .first()
27461 .map(|(_, _, word_count)| *word_count)
27462 .unwrap_or_default(),
27463 )
27464 .collect_vec();
27465
27466 const MAX_RESULTS: usize = 100;
27467 // Each match also remembers how many characters from the buffer it consumed
27468 let mut matches: Vec<(StringMatch, usize)> = vec![];
27469
27470 let mut snippet_list_cutoff_index = 0;
27471 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
27472 let word_count = buffer_index + 1;
27473 // Increase `snippet_list_cutoff_index` until we have all of the
27474 // snippets with sufficiently many words.
27475 while sorted_snippet_candidates
27476 .get(snippet_list_cutoff_index)
27477 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
27478 *snippet_word_count >= word_count
27479 })
27480 {
27481 snippet_list_cutoff_index += 1;
27482 }
27483
27484 // Take only the candidates with at least `word_count` many words
27485 let snippet_candidates_at_word_len =
27486 &sorted_snippet_candidates[..snippet_list_cutoff_index];
27487
27488 let candidates = snippet_candidates_at_word_len
27489 .iter()
27490 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
27491 .enumerate() // index in `sorted_snippet_candidates`
27492 // First char must match
27493 .filter(|(_ix, prefix)| {
27494 itertools::equal(
27495 prefix
27496 .chars()
27497 .next()
27498 .into_iter()
27499 .flat_map(|c| c.to_lowercase()),
27500 buffer_window
27501 .chars()
27502 .next()
27503 .into_iter()
27504 .flat_map(|c| c.to_lowercase()),
27505 )
27506 })
27507 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
27508 .collect::<Vec<StringMatchCandidate>>();
27509
27510 matches.extend(
27511 fuzzy::match_strings(
27512 &candidates,
27513 &buffer_window,
27514 buffer_window.chars().any(|c| c.is_uppercase()),
27515 true,
27516 MAX_RESULTS - matches.len(), // always prioritize longer snippets
27517 &Default::default(),
27518 executor.clone(),
27519 )
27520 .await
27521 .into_iter()
27522 .map(|string_match| (string_match, buffer_window.len())),
27523 );
27524
27525 if matches.len() >= MAX_RESULTS {
27526 break;
27527 }
27528 }
27529
27530 let to_lsp = |point: &text::Anchor| {
27531 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
27532 point_to_lsp(end)
27533 };
27534 let lsp_end = to_lsp(&buffer_anchor);
27535
27536 if matches.len() >= MAX_RESULTS {
27537 is_incomplete = true;
27538 }
27539
27540 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
27541 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
27542 sorted_snippet_candidates[string_match.candidate_id];
27543 let snippet = &snippets[snippet_index];
27544 let start = buffer_offset - buffer_window_len;
27545 let start = snapshot.anchor_before(start);
27546 let range = start..buffer_anchor;
27547 let lsp_start = to_lsp(&start);
27548 let lsp_range = lsp::Range {
27549 start: lsp_start,
27550 end: lsp_end,
27551 };
27552 Completion {
27553 replace_range: range,
27554 new_text: snippet.body.clone(),
27555 source: CompletionSource::Lsp {
27556 insert_range: None,
27557 server_id: LanguageServerId(usize::MAX),
27558 resolved: true,
27559 lsp_completion: Box::new(lsp::CompletionItem {
27560 label: snippet.prefix.first().unwrap().clone(),
27561 kind: Some(CompletionItemKind::SNIPPET),
27562 label_details: snippet.description.as_ref().map(|description| {
27563 lsp::CompletionItemLabelDetails {
27564 detail: Some(description.clone()),
27565 description: None,
27566 }
27567 }),
27568 insert_text_format: Some(InsertTextFormat::SNIPPET),
27569 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
27570 lsp::InsertReplaceEdit {
27571 new_text: snippet.body.clone(),
27572 insert: lsp_range,
27573 replace: lsp_range,
27574 },
27575 )),
27576 filter_text: Some(snippet.body.clone()),
27577 sort_text: Some(char::MAX.to_string()),
27578 ..lsp::CompletionItem::default()
27579 }),
27580 lsp_defaults: None,
27581 },
27582 label: CodeLabel {
27583 text: matching_prefix.clone(),
27584 runs: Vec::new(),
27585 filter_range: 0..matching_prefix.len(),
27586 },
27587 icon_path: None,
27588 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
27589 single_line: snippet.name.clone().into(),
27590 plain_text: snippet
27591 .description
27592 .clone()
27593 .map(|description| description.into()),
27594 }),
27595 insert_text_mode: None,
27596 confirm: None,
27597 match_start: Some(start),
27598 snippet_deduplication_key: Some((snippet_index, prefix_index)),
27599 }
27600 }));
27601 }
27602
27603 Ok(CompletionResponse {
27604 completions,
27605 display_options: CompletionDisplayOptions::default(),
27606 is_incomplete,
27607 })
27608 })
27609}
27610
27611impl CompletionProvider for Entity<Project> {
27612 fn completions(
27613 &self,
27614 buffer: &Entity<Buffer>,
27615 buffer_position: text::Anchor,
27616 options: CompletionContext,
27617 _window: &mut Window,
27618 cx: &mut Context<Editor>,
27619 ) -> Task<Result<Vec<CompletionResponse>>> {
27620 self.update(cx, |project, cx| {
27621 let task = project.completions(buffer, buffer_position, options, cx);
27622 cx.background_spawn(task)
27623 })
27624 }
27625
27626 fn resolve_completions(
27627 &self,
27628 buffer: Entity<Buffer>,
27629 completion_indices: Vec<usize>,
27630 completions: Rc<RefCell<Box<[Completion]>>>,
27631 cx: &mut Context<Editor>,
27632 ) -> Task<Result<bool>> {
27633 self.update(cx, |project, cx| {
27634 project.lsp_store().update(cx, |lsp_store, cx| {
27635 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
27636 })
27637 })
27638 }
27639
27640 fn apply_additional_edits_for_completion(
27641 &self,
27642 buffer: Entity<Buffer>,
27643 completions: Rc<RefCell<Box<[Completion]>>>,
27644 completion_index: usize,
27645 push_to_history: bool,
27646 all_commit_ranges: Vec<Range<language::Anchor>>,
27647 cx: &mut Context<Editor>,
27648 ) -> Task<Result<Option<language::Transaction>>> {
27649 self.update(cx, |project, cx| {
27650 project.lsp_store().update(cx, |lsp_store, cx| {
27651 lsp_store.apply_additional_edits_for_completion(
27652 buffer,
27653 completions,
27654 completion_index,
27655 push_to_history,
27656 all_commit_ranges,
27657 cx,
27658 )
27659 })
27660 })
27661 }
27662
27663 fn is_completion_trigger(
27664 &self,
27665 buffer: &Entity<Buffer>,
27666 position: language::Anchor,
27667 text: &str,
27668 trigger_in_words: bool,
27669 cx: &mut Context<Editor>,
27670 ) -> bool {
27671 let mut chars = text.chars();
27672 let char = if let Some(char) = chars.next() {
27673 char
27674 } else {
27675 return false;
27676 };
27677 if chars.next().is_some() {
27678 return false;
27679 }
27680
27681 let buffer = buffer.read(cx);
27682 let snapshot = buffer.snapshot();
27683 let classifier = snapshot
27684 .char_classifier_at(position)
27685 .scope_context(Some(CharScopeContext::Completion));
27686 if trigger_in_words && classifier.is_word(char) {
27687 return true;
27688 }
27689
27690 buffer.completion_triggers().contains(text)
27691 }
27692
27693 fn show_snippets(&self) -> bool {
27694 true
27695 }
27696}
27697
27698impl SemanticsProvider for WeakEntity<Project> {
27699 fn hover(
27700 &self,
27701 buffer: &Entity<Buffer>,
27702 position: text::Anchor,
27703 cx: &mut App,
27704 ) -> Option<Task<Option<Vec<project::Hover>>>> {
27705 self.update(cx, |project, cx| project.hover(buffer, position, cx))
27706 .ok()
27707 }
27708
27709 fn document_highlights(
27710 &self,
27711 buffer: &Entity<Buffer>,
27712 position: text::Anchor,
27713 cx: &mut App,
27714 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
27715 self.update(cx, |project, cx| {
27716 project.document_highlights(buffer, position, cx)
27717 })
27718 .ok()
27719 }
27720
27721 fn definitions(
27722 &self,
27723 buffer: &Entity<Buffer>,
27724 position: text::Anchor,
27725 kind: GotoDefinitionKind,
27726 cx: &mut App,
27727 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
27728 self.update(cx, |project, cx| match kind {
27729 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
27730 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
27731 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
27732 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
27733 })
27734 .ok()
27735 }
27736
27737 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27738 self.update(cx, |project, cx| {
27739 if project
27740 .active_debug_session(cx)
27741 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
27742 {
27743 return true;
27744 }
27745
27746 buffer.update(cx, |buffer, cx| {
27747 project.any_language_server_supports_inlay_hints(buffer, cx)
27748 })
27749 })
27750 .unwrap_or(false)
27751 }
27752
27753 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27754 self.update(cx, |project, cx| {
27755 buffer.update(cx, |buffer, cx| {
27756 project.any_language_server_supports_semantic_tokens(buffer, cx)
27757 })
27758 })
27759 .unwrap_or(false)
27760 }
27761
27762 fn inline_values(
27763 &self,
27764 buffer_handle: Entity<Buffer>,
27765 range: Range<text::Anchor>,
27766 cx: &mut App,
27767 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
27768 self.update(cx, |project, cx| {
27769 let (session, active_stack_frame) = project.active_debug_session(cx)?;
27770
27771 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
27772 })
27773 .ok()
27774 .flatten()
27775 }
27776
27777 fn applicable_inlay_chunks(
27778 &self,
27779 buffer: &Entity<Buffer>,
27780 ranges: &[Range<text::Anchor>],
27781 cx: &mut App,
27782 ) -> Vec<Range<BufferRow>> {
27783 self.update(cx, |project, cx| {
27784 project.lsp_store().update(cx, |lsp_store, cx| {
27785 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
27786 })
27787 })
27788 .unwrap_or_default()
27789 }
27790
27791 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
27792 self.update(cx, |project, cx| {
27793 project.lsp_store().update(cx, |lsp_store, _| {
27794 lsp_store.invalidate_inlay_hints(for_buffers)
27795 })
27796 })
27797 .ok();
27798 }
27799
27800 fn inlay_hints(
27801 &self,
27802 invalidate: InvalidationStrategy,
27803 buffer: Entity<Buffer>,
27804 ranges: Vec<Range<text::Anchor>>,
27805 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
27806 cx: &mut App,
27807 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
27808 self.update(cx, |project, cx| {
27809 project.lsp_store().update(cx, |lsp_store, cx| {
27810 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
27811 })
27812 })
27813 .ok()
27814 }
27815
27816 fn semantic_tokens(
27817 &self,
27818 buffer: Entity<Buffer>,
27819 refresh: Option<RefreshForServer>,
27820 cx: &mut App,
27821 ) -> Option<Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>>> {
27822 self.update(cx, |this, cx| {
27823 this.lsp_store().update(cx, |lsp_store, cx| {
27824 lsp_store.semantic_tokens(buffer, refresh, cx)
27825 })
27826 })
27827 .ok()
27828 }
27829
27830 fn range_for_rename(
27831 &self,
27832 buffer: &Entity<Buffer>,
27833 position: text::Anchor,
27834 cx: &mut App,
27835 ) -> Task<Result<Option<Range<text::Anchor>>>> {
27836 let Some(this) = self.upgrade() else {
27837 return Task::ready(Ok(None));
27838 };
27839
27840 this.update(cx, |project, cx| {
27841 let buffer = buffer.clone();
27842 let task = project.prepare_rename(buffer.clone(), position, cx);
27843 cx.spawn(async move |_, cx| {
27844 Ok(match task.await? {
27845 PrepareRenameResponse::Success(range) => Some(range),
27846 PrepareRenameResponse::InvalidPosition => None,
27847 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
27848 // Fallback on using TreeSitter info to determine identifier range
27849 buffer.read_with(cx, |buffer, _| {
27850 let snapshot = buffer.snapshot();
27851 let (range, kind) = snapshot.surrounding_word(position, None);
27852 if kind != Some(CharKind::Word) {
27853 return None;
27854 }
27855 Some(
27856 snapshot.anchor_before(range.start)
27857 ..snapshot.anchor_after(range.end),
27858 )
27859 })
27860 }
27861 })
27862 })
27863 })
27864 }
27865
27866 fn perform_rename(
27867 &self,
27868 buffer: &Entity<Buffer>,
27869 position: text::Anchor,
27870 new_name: String,
27871 cx: &mut App,
27872 ) -> Option<Task<Result<ProjectTransaction>>> {
27873 self.update(cx, |project, cx| {
27874 project.perform_rename(buffer.clone(), position, new_name, cx)
27875 })
27876 .ok()
27877 }
27878}
27879
27880fn consume_contiguous_rows(
27881 contiguous_row_selections: &mut Vec<Selection<Point>>,
27882 selection: &Selection<Point>,
27883 display_map: &DisplaySnapshot,
27884 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
27885) -> (MultiBufferRow, MultiBufferRow) {
27886 contiguous_row_selections.push(selection.clone());
27887 let start_row = starting_row(selection, display_map);
27888 let mut end_row = ending_row(selection, display_map);
27889
27890 while let Some(next_selection) = selections.peek() {
27891 if next_selection.start.row <= end_row.0 {
27892 end_row = ending_row(next_selection, display_map);
27893 contiguous_row_selections.push(selections.next().unwrap().clone());
27894 } else {
27895 break;
27896 }
27897 }
27898 (start_row, end_row)
27899}
27900
27901fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27902 if selection.start.column > 0 {
27903 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
27904 } else {
27905 MultiBufferRow(selection.start.row)
27906 }
27907}
27908
27909fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27910 if next_selection.end.column > 0 || next_selection.is_empty() {
27911 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
27912 } else {
27913 MultiBufferRow(next_selection.end.row)
27914 }
27915}
27916
27917impl EditorSnapshot {
27918 pub fn remote_selections_in_range<'a>(
27919 &'a self,
27920 range: &'a Range<Anchor>,
27921 collaboration_hub: &dyn CollaborationHub,
27922 cx: &'a App,
27923 ) -> impl 'a + Iterator<Item = RemoteSelection> {
27924 let participant_names = collaboration_hub.user_names(cx);
27925 let participant_indices = collaboration_hub.user_participant_indices(cx);
27926 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
27927 let collaborators_by_replica_id = collaborators_by_peer_id
27928 .values()
27929 .map(|collaborator| (collaborator.replica_id, collaborator))
27930 .collect::<HashMap<_, _>>();
27931 self.buffer_snapshot()
27932 .selections_in_range(range, false)
27933 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
27934 if replica_id == ReplicaId::AGENT {
27935 Some(RemoteSelection {
27936 replica_id,
27937 selection,
27938 cursor_shape,
27939 line_mode,
27940 collaborator_id: CollaboratorId::Agent,
27941 user_name: Some("Agent".into()),
27942 color: cx.theme().players().agent(),
27943 })
27944 } else {
27945 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
27946 let participant_index = participant_indices.get(&collaborator.user_id).copied();
27947 let user_name = participant_names.get(&collaborator.user_id).cloned();
27948 Some(RemoteSelection {
27949 replica_id,
27950 selection,
27951 cursor_shape,
27952 line_mode,
27953 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
27954 user_name,
27955 color: if let Some(index) = participant_index {
27956 cx.theme().players().color_for_participant(index.0)
27957 } else {
27958 cx.theme().players().absent()
27959 },
27960 })
27961 }
27962 })
27963 }
27964
27965 pub fn hunks_for_ranges(
27966 &self,
27967 ranges: impl IntoIterator<Item = Range<Point>>,
27968 ) -> Vec<MultiBufferDiffHunk> {
27969 let mut hunks = Vec::new();
27970 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
27971 HashMap::default();
27972 for query_range in ranges {
27973 let query_rows =
27974 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
27975 for hunk in self.buffer_snapshot().diff_hunks_in_range(
27976 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
27977 ) {
27978 // Include deleted hunks that are adjacent to the query range, because
27979 // otherwise they would be missed.
27980 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
27981 if hunk.status().is_deleted() {
27982 intersects_range |= hunk.row_range.start == query_rows.end;
27983 intersects_range |= hunk.row_range.end == query_rows.start;
27984 }
27985 if intersects_range {
27986 if !processed_buffer_rows
27987 .entry(hunk.buffer_id)
27988 .or_default()
27989 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
27990 {
27991 continue;
27992 }
27993 hunks.push(hunk);
27994 }
27995 }
27996 }
27997
27998 hunks
27999 }
28000
28001 fn display_diff_hunks_for_rows<'a>(
28002 &'a self,
28003 display_rows: Range<DisplayRow>,
28004 folded_buffers: &'a HashSet<BufferId>,
28005 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
28006 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
28007 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
28008
28009 self.buffer_snapshot()
28010 .diff_hunks_in_range(buffer_start..buffer_end)
28011 .filter_map(|hunk| {
28012 if folded_buffers.contains(&hunk.buffer_id)
28013 || (hunk.row_range.is_empty() && self.buffer.all_diff_hunks_expanded())
28014 {
28015 return None;
28016 }
28017
28018 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
28019 let hunk_end_point = if hunk.row_range.end > hunk.row_range.start {
28020 let last_row = MultiBufferRow(hunk.row_range.end.0 - 1);
28021 let line_len = self.buffer_snapshot().line_len(last_row);
28022 Point::new(last_row.0, line_len)
28023 } else {
28024 Point::new(hunk.row_range.end.0, 0)
28025 };
28026
28027 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
28028 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
28029
28030 let display_hunk = if hunk_display_start.column() != 0 {
28031 DisplayDiffHunk::Folded {
28032 display_row: hunk_display_start.row(),
28033 }
28034 } else {
28035 let mut end_row = hunk_display_end.row();
28036 if hunk.row_range.end > hunk.row_range.start || hunk_display_end.column() > 0 {
28037 end_row.0 += 1;
28038 }
28039 let is_created_file = hunk.is_created_file();
28040 let multi_buffer_range = hunk.multi_buffer_range.clone();
28041
28042 DisplayDiffHunk::Unfolded {
28043 status: hunk.status(),
28044 diff_base_byte_range: hunk.diff_base_byte_range.start.0
28045 ..hunk.diff_base_byte_range.end.0,
28046 word_diffs: hunk.word_diffs,
28047 display_row_range: hunk_display_start.row()..end_row,
28048 multi_buffer_range,
28049 is_created_file,
28050 }
28051 };
28052
28053 Some(display_hunk)
28054 })
28055 }
28056
28057 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
28058 self.display_snapshot
28059 .buffer_snapshot()
28060 .language_at(position)
28061 }
28062
28063 pub fn is_focused(&self) -> bool {
28064 self.is_focused
28065 }
28066
28067 pub fn placeholder_text(&self) -> Option<String> {
28068 self.placeholder_display_snapshot
28069 .as_ref()
28070 .map(|display_map| display_map.text())
28071 }
28072
28073 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
28074 self.scroll_anchor.scroll_position(&self.display_snapshot)
28075 }
28076
28077 pub fn gutter_dimensions(
28078 &self,
28079 font_id: FontId,
28080 font_size: Pixels,
28081 style: &EditorStyle,
28082 window: &mut Window,
28083 cx: &App,
28084 ) -> GutterDimensions {
28085 if self.show_gutter
28086 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
28087 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
28088 {
28089 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
28090 matches!(
28091 ProjectSettings::get_global(cx).git.git_gutter,
28092 GitGutterSetting::TrackedFiles
28093 )
28094 });
28095 let gutter_settings = EditorSettings::get_global(cx).gutter;
28096 let show_line_numbers = self
28097 .show_line_numbers
28098 .unwrap_or(gutter_settings.line_numbers);
28099 let line_gutter_width = if show_line_numbers {
28100 // Avoid flicker-like gutter resizes when the line number gains another digit by
28101 // only resizing the gutter on files with > 10**min_line_number_digits lines.
28102 let min_width_for_number_on_gutter =
28103 ch_advance * gutter_settings.min_line_number_digits as f32;
28104 self.max_line_number_width(style, window)
28105 .max(min_width_for_number_on_gutter)
28106 } else {
28107 0.0.into()
28108 };
28109
28110 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
28111 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
28112
28113 let git_blame_entries_width =
28114 self.git_blame_gutter_max_author_length
28115 .map(|max_author_length| {
28116 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
28117 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
28118
28119 /// The number of characters to dedicate to gaps and margins.
28120 const SPACING_WIDTH: usize = 4;
28121
28122 let max_char_count = max_author_length.min(renderer.max_author_length())
28123 + ::git::SHORT_SHA_LENGTH
28124 + MAX_RELATIVE_TIMESTAMP.len()
28125 + SPACING_WIDTH;
28126
28127 ch_advance * max_char_count
28128 });
28129
28130 let is_singleton = self.buffer_snapshot().is_singleton();
28131
28132 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
28133 left_padding += if !is_singleton {
28134 ch_width * 4.0
28135 } else if show_runnables || show_breakpoints {
28136 ch_width * 3.0
28137 } else if show_git_gutter && show_line_numbers {
28138 ch_width * 2.0
28139 } else if show_git_gutter || show_line_numbers {
28140 ch_width
28141 } else {
28142 px(0.)
28143 };
28144
28145 let shows_folds = is_singleton && gutter_settings.folds;
28146
28147 let right_padding = if shows_folds && show_line_numbers {
28148 ch_width * 4.0
28149 } else if shows_folds || (!is_singleton && show_line_numbers) {
28150 ch_width * 3.0
28151 } else if show_line_numbers {
28152 ch_width
28153 } else {
28154 px(0.)
28155 };
28156
28157 GutterDimensions {
28158 left_padding,
28159 right_padding,
28160 width: line_gutter_width + left_padding + right_padding,
28161 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
28162 git_blame_entries_width,
28163 }
28164 } else if self.offset_content {
28165 GutterDimensions::default_with_margin(font_id, font_size, cx)
28166 } else {
28167 GutterDimensions::default()
28168 }
28169 }
28170
28171 pub fn render_crease_toggle(
28172 &self,
28173 buffer_row: MultiBufferRow,
28174 row_contains_cursor: bool,
28175 editor: Entity<Editor>,
28176 window: &mut Window,
28177 cx: &mut App,
28178 ) -> Option<AnyElement> {
28179 let folded = self.is_line_folded(buffer_row);
28180 let mut is_foldable = false;
28181
28182 if let Some(crease) = self
28183 .crease_snapshot
28184 .query_row(buffer_row, self.buffer_snapshot())
28185 {
28186 is_foldable = true;
28187 match crease {
28188 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
28189 if let Some(render_toggle) = render_toggle {
28190 let toggle_callback =
28191 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
28192 if folded {
28193 editor.update(cx, |editor, cx| {
28194 editor.fold_at(buffer_row, window, cx)
28195 });
28196 } else {
28197 editor.update(cx, |editor, cx| {
28198 editor.unfold_at(buffer_row, window, cx)
28199 });
28200 }
28201 });
28202 return Some((render_toggle)(
28203 buffer_row,
28204 folded,
28205 toggle_callback,
28206 window,
28207 cx,
28208 ));
28209 }
28210 }
28211 }
28212 }
28213
28214 is_foldable |= !self.use_lsp_folding_ranges && self.starts_indent(buffer_row);
28215
28216 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
28217 Some(
28218 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
28219 .toggle_state(folded)
28220 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
28221 if folded {
28222 this.unfold_at(buffer_row, window, cx);
28223 } else {
28224 this.fold_at(buffer_row, window, cx);
28225 }
28226 }))
28227 .into_any_element(),
28228 )
28229 } else {
28230 None
28231 }
28232 }
28233
28234 pub fn render_crease_trailer(
28235 &self,
28236 buffer_row: MultiBufferRow,
28237 window: &mut Window,
28238 cx: &mut App,
28239 ) -> Option<AnyElement> {
28240 let folded = self.is_line_folded(buffer_row);
28241 if let Crease::Inline { render_trailer, .. } = self
28242 .crease_snapshot
28243 .query_row(buffer_row, self.buffer_snapshot())?
28244 {
28245 let render_trailer = render_trailer.as_ref()?;
28246 Some(render_trailer(buffer_row, folded, window, cx))
28247 } else {
28248 None
28249 }
28250 }
28251
28252 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
28253 let digit_count = self.widest_line_number().ilog10() + 1;
28254 column_pixels(style, digit_count as usize, window)
28255 }
28256
28257 /// Returns the line delta from `base` to `line` in the multibuffer, ignoring wrapped lines.
28258 ///
28259 /// This is positive if `base` is before `line`.
28260 fn relative_line_delta(
28261 &self,
28262 current_selection_head: DisplayRow,
28263 first_visible_row: DisplayRow,
28264 consider_wrapped_lines: bool,
28265 ) -> i64 {
28266 let current_selection_head = current_selection_head.as_display_point().to_point(self);
28267 let first_visible_row = first_visible_row.as_display_point().to_point(self);
28268
28269 if consider_wrapped_lines {
28270 let wrap_snapshot = self.wrap_snapshot();
28271 let base_wrap_row = wrap_snapshot
28272 .make_wrap_point(current_selection_head, Bias::Left)
28273 .row();
28274 let wrap_row = wrap_snapshot
28275 .make_wrap_point(first_visible_row, Bias::Left)
28276 .row();
28277
28278 wrap_row.0 as i64 - base_wrap_row.0 as i64
28279 } else {
28280 let fold_snapshot = self.fold_snapshot();
28281 let base_fold_row = fold_snapshot
28282 .to_fold_point(self.to_inlay_point(current_selection_head), Bias::Left)
28283 .row();
28284 let fold_row = fold_snapshot
28285 .to_fold_point(self.to_inlay_point(first_visible_row), Bias::Left)
28286 .row();
28287
28288 fold_row as i64 - base_fold_row as i64
28289 }
28290 }
28291
28292 /// Returns the unsigned relative line number to display for each row in `rows`.
28293 ///
28294 /// Wrapped rows are excluded from the hashmap if `count_relative_lines` is `false`.
28295 pub fn calculate_relative_line_numbers(
28296 &self,
28297 rows: &Range<DisplayRow>,
28298 current_selection_head: DisplayRow,
28299 count_wrapped_lines: bool,
28300 ) -> HashMap<DisplayRow, u32> {
28301 let initial_offset =
28302 self.relative_line_delta(current_selection_head, rows.start, count_wrapped_lines);
28303
28304 self.row_infos(rows.start)
28305 .take(rows.len())
28306 .enumerate()
28307 .map(|(i, row_info)| (DisplayRow(rows.start.0 + i as u32), row_info))
28308 .filter(|(_row, row_info)| {
28309 row_info.buffer_row.is_some()
28310 || (count_wrapped_lines && row_info.wrapped_buffer_row.is_some())
28311 })
28312 .enumerate()
28313 .filter_map(|(i, (row, row_info))| {
28314 // We want to ensure here that the current line has absolute
28315 // numbering, even if we are in a soft-wrapped line. With the
28316 // exception that if we are in a deleted line, we should number this
28317 // relative with 0, as otherwise it would have no line number at all
28318 let relative_line_number = (initial_offset + i as i64).unsigned_abs() as u32;
28319
28320 (relative_line_number != 0
28321 || row_info
28322 .diff_status
28323 .is_some_and(|status| status.is_deleted()))
28324 .then_some((row, relative_line_number))
28325 })
28326 .collect()
28327 }
28328}
28329
28330pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
28331 let font_size = style.text.font_size.to_pixels(window.rem_size());
28332 let layout = window.text_system().shape_line(
28333 SharedString::from(" ".repeat(column)),
28334 font_size,
28335 &[TextRun {
28336 len: column,
28337 font: style.text.font(),
28338 color: Hsla::default(),
28339 ..Default::default()
28340 }],
28341 None,
28342 );
28343
28344 layout.width
28345}
28346
28347impl Deref for EditorSnapshot {
28348 type Target = DisplaySnapshot;
28349
28350 fn deref(&self) -> &Self::Target {
28351 &self.display_snapshot
28352 }
28353}
28354
28355#[derive(Clone, Debug, PartialEq, Eq)]
28356pub enum EditorEvent {
28357 /// Emitted when the stored review comments change (added, removed, or updated).
28358 ReviewCommentsChanged {
28359 /// The new total count of review comments.
28360 total_count: usize,
28361 },
28362 InputIgnored {
28363 text: Arc<str>,
28364 },
28365 InputHandled {
28366 utf16_range_to_replace: Option<Range<isize>>,
28367 text: Arc<str>,
28368 },
28369 BufferRangesUpdated {
28370 buffer: Entity<Buffer>,
28371 path_key: PathKey,
28372 ranges: Vec<ExcerptRange<text::Anchor>>,
28373 },
28374 BuffersRemoved {
28375 removed_buffer_ids: Vec<BufferId>,
28376 },
28377 BuffersEdited {
28378 buffer_ids: Vec<BufferId>,
28379 },
28380 BufferFoldToggled {
28381 ids: Vec<BufferId>,
28382 folded: bool,
28383 },
28384 ExpandExcerptsRequested {
28385 excerpt_anchors: Vec<Anchor>,
28386 lines: u32,
28387 direction: ExpandExcerptDirection,
28388 },
28389 StageOrUnstageRequested {
28390 stage: bool,
28391 hunks: Vec<MultiBufferDiffHunk>,
28392 },
28393 OpenExcerptsRequested {
28394 selections_by_buffer: HashMap<BufferId, (Vec<Range<BufferOffset>>, Option<u32>)>,
28395 split: bool,
28396 },
28397 RestoreRequested {
28398 hunks: Vec<MultiBufferDiffHunk>,
28399 },
28400 BufferEdited,
28401 Edited {
28402 transaction_id: clock::Lamport,
28403 },
28404 Reparsed(BufferId),
28405 Focused,
28406 FocusedIn,
28407 Blurred,
28408 DirtyChanged,
28409 Saved,
28410 TitleChanged,
28411 SelectionsChanged {
28412 local: bool,
28413 },
28414 ScrollPositionChanged {
28415 local: bool,
28416 autoscroll: bool,
28417 },
28418 TransactionUndone {
28419 transaction_id: clock::Lamport,
28420 },
28421 TransactionBegun {
28422 transaction_id: clock::Lamport,
28423 },
28424 CursorShapeChanged,
28425 BreadcrumbsChanged,
28426 OutlineSymbolsChanged,
28427 PushedToNavHistory {
28428 anchor: Anchor,
28429 is_deactivate: bool,
28430 },
28431}
28432
28433impl EventEmitter<EditorEvent> for Editor {}
28434
28435impl Focusable for Editor {
28436 fn focus_handle(&self, _cx: &App) -> FocusHandle {
28437 self.focus_handle.clone()
28438 }
28439}
28440
28441impl Render for Editor {
28442 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28443 EditorElement::new(&cx.entity(), self.create_style(cx))
28444 }
28445}
28446
28447impl EntityInputHandler for Editor {
28448 fn text_for_range(
28449 &mut self,
28450 range_utf16: Range<usize>,
28451 adjusted_range: &mut Option<Range<usize>>,
28452 _: &mut Window,
28453 cx: &mut Context<Self>,
28454 ) -> Option<String> {
28455 let snapshot = self.buffer.read(cx).read(cx);
28456 let start = snapshot.clip_offset_utf16(
28457 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
28458 Bias::Left,
28459 );
28460 let end = snapshot.clip_offset_utf16(
28461 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
28462 Bias::Right,
28463 );
28464 if (start.0.0..end.0.0) != range_utf16 {
28465 adjusted_range.replace(start.0.0..end.0.0);
28466 }
28467 Some(snapshot.text_for_range(start..end).collect())
28468 }
28469
28470 fn selected_text_range(
28471 &mut self,
28472 ignore_disabled_input: bool,
28473 _: &mut Window,
28474 cx: &mut Context<Self>,
28475 ) -> Option<UTF16Selection> {
28476 // Prevent the IME menu from appearing when holding down an alphabetic key
28477 // while input is disabled.
28478 if !ignore_disabled_input && !self.input_enabled {
28479 return None;
28480 }
28481
28482 let selection = self
28483 .selections
28484 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
28485 let range = selection.range();
28486
28487 Some(UTF16Selection {
28488 range: range.start.0.0..range.end.0.0,
28489 reversed: selection.reversed,
28490 })
28491 }
28492
28493 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
28494 let snapshot = self.buffer.read(cx).read(cx);
28495 let range = self
28496 .text_highlights(HighlightKey::InputComposition, cx)?
28497 .1
28498 .first()?;
28499 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
28500 }
28501
28502 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
28503 self.clear_highlights(HighlightKey::InputComposition, cx);
28504 self.ime_transaction.take();
28505 }
28506
28507 fn replace_text_in_range(
28508 &mut self,
28509 range_utf16: Option<Range<usize>>,
28510 text: &str,
28511 window: &mut Window,
28512 cx: &mut Context<Self>,
28513 ) {
28514 if !self.input_enabled {
28515 cx.emit(EditorEvent::InputIgnored { text: text.into() });
28516 return;
28517 }
28518
28519 self.transact(window, cx, |this, window, cx| {
28520 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
28521 if let Some(marked_ranges) = this.marked_text_ranges(cx) {
28522 // During IME composition, macOS reports the replacement range
28523 // relative to the first marked region (the only one visible via
28524 // marked_text_range). The correct targets for replacement are the
28525 // marked ranges themselves — one per cursor — so use them directly.
28526 Some(marked_ranges)
28527 } else if range_utf16.start == range_utf16.end {
28528 // An empty replacement range means "insert at cursor" with no text
28529 // to replace. macOS reports the cursor position from its own
28530 // (single-cursor) view of the buffer, which diverges from our actual
28531 // cursor positions after multi-cursor edits have shifted offsets.
28532 // Treating this as range_utf16=None lets each cursor insert in place.
28533 None
28534 } else {
28535 // Outside of IME composition (e.g. Accessibility Keyboard word
28536 // completion), the range is an absolute document offset for the
28537 // newest cursor. Fan it out to all cursors via
28538 // selection_replacement_ranges, which applies the delta relative
28539 // to the newest selection to every cursor.
28540 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
28541 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
28542 Some(this.selection_replacement_ranges(range_utf16, cx))
28543 }
28544 } else {
28545 this.marked_text_ranges(cx)
28546 };
28547
28548 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
28549 let newest_selection_id = this.selections.newest_anchor().id;
28550 this.selections
28551 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
28552 .iter()
28553 .zip(ranges_to_replace.iter())
28554 .find_map(|(selection, range)| {
28555 if selection.id == newest_selection_id {
28556 Some(
28557 (range.start.0.0 as isize - selection.head().0.0 as isize)
28558 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
28559 )
28560 } else {
28561 None
28562 }
28563 })
28564 });
28565
28566 cx.emit(EditorEvent::InputHandled {
28567 utf16_range_to_replace: range_to_replace,
28568 text: text.into(),
28569 });
28570
28571 if let Some(new_selected_ranges) = new_selected_ranges {
28572 // Only backspace if at least one range covers actual text. When all
28573 // ranges are empty (e.g. a trailing-space insertion from Accessibility
28574 // Keyboard sends replacementRange=cursor..cursor), backspace would
28575 // incorrectly delete the character just before the cursor.
28576 let should_backspace = new_selected_ranges.iter().any(|r| r.start != r.end);
28577 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
28578 selections.select_ranges(new_selected_ranges)
28579 });
28580 if should_backspace {
28581 this.backspace(&Default::default(), window, cx);
28582 }
28583 }
28584
28585 this.handle_input(text, window, cx);
28586 });
28587
28588 if let Some(transaction) = self.ime_transaction {
28589 self.buffer.update(cx, |buffer, cx| {
28590 buffer.group_until_transaction(transaction, cx);
28591 });
28592 }
28593
28594 self.unmark_text(window, cx);
28595 }
28596
28597 fn replace_and_mark_text_in_range(
28598 &mut self,
28599 range_utf16: Option<Range<usize>>,
28600 text: &str,
28601 new_selected_range_utf16: Option<Range<usize>>,
28602 window: &mut Window,
28603 cx: &mut Context<Self>,
28604 ) {
28605 if !self.input_enabled {
28606 return;
28607 }
28608
28609 let transaction = self.transact(window, cx, |this, window, cx| {
28610 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
28611 let snapshot = this.buffer.read(cx).read(cx);
28612 if let Some(relative_range_utf16) = range_utf16.as_ref() {
28613 for marked_range in &mut marked_ranges {
28614 marked_range.end = marked_range.start + relative_range_utf16.end;
28615 marked_range.start += relative_range_utf16.start;
28616 marked_range.start =
28617 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
28618 marked_range.end =
28619 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
28620 }
28621 }
28622 Some(marked_ranges)
28623 } else if let Some(range_utf16) = range_utf16 {
28624 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
28625 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
28626 Some(this.selection_replacement_ranges(range_utf16, cx))
28627 } else {
28628 None
28629 };
28630
28631 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
28632 let newest_selection_id = this.selections.newest_anchor().id;
28633 this.selections
28634 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
28635 .iter()
28636 .zip(ranges_to_replace.iter())
28637 .find_map(|(selection, range)| {
28638 if selection.id == newest_selection_id {
28639 Some(
28640 (range.start.0.0 as isize - selection.head().0.0 as isize)
28641 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
28642 )
28643 } else {
28644 None
28645 }
28646 })
28647 });
28648
28649 cx.emit(EditorEvent::InputHandled {
28650 utf16_range_to_replace: range_to_replace,
28651 text: text.into(),
28652 });
28653
28654 if let Some(ranges) = ranges_to_replace {
28655 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28656 s.select_ranges(ranges)
28657 });
28658 }
28659
28660 let marked_ranges = {
28661 let snapshot = this.buffer.read(cx).read(cx);
28662 this.selections
28663 .disjoint_anchors_arc()
28664 .iter()
28665 .map(|selection| {
28666 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
28667 })
28668 .collect::<Vec<_>>()
28669 };
28670
28671 if text.is_empty() {
28672 this.unmark_text(window, cx);
28673 } else {
28674 this.highlight_text(
28675 HighlightKey::InputComposition,
28676 marked_ranges.clone(),
28677 HighlightStyle {
28678 underline: Some(UnderlineStyle {
28679 thickness: px(1.),
28680 color: None,
28681 wavy: false,
28682 }),
28683 ..Default::default()
28684 },
28685 cx,
28686 );
28687 }
28688
28689 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
28690 let use_autoclose = this.use_autoclose;
28691 let use_auto_surround = this.use_auto_surround;
28692 this.set_use_autoclose(false);
28693 this.set_use_auto_surround(false);
28694 this.handle_input(text, window, cx);
28695 this.set_use_autoclose(use_autoclose);
28696 this.set_use_auto_surround(use_auto_surround);
28697
28698 if let Some(new_selected_range) = new_selected_range_utf16 {
28699 let snapshot = this.buffer.read(cx).read(cx);
28700 let new_selected_ranges = marked_ranges
28701 .into_iter()
28702 .map(|marked_range| {
28703 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
28704 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
28705 insertion_start.0 + new_selected_range.start,
28706 ));
28707 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
28708 insertion_start.0 + new_selected_range.end,
28709 ));
28710 snapshot.clip_offset_utf16(new_start, Bias::Left)
28711 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
28712 })
28713 .collect::<Vec<_>>();
28714
28715 drop(snapshot);
28716 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
28717 selections.select_ranges(new_selected_ranges)
28718 });
28719 }
28720 });
28721
28722 self.ime_transaction = self.ime_transaction.or(transaction);
28723 if let Some(transaction) = self.ime_transaction {
28724 self.buffer.update(cx, |buffer, cx| {
28725 buffer.group_until_transaction(transaction, cx);
28726 });
28727 }
28728
28729 if self
28730 .text_highlights(HighlightKey::InputComposition, cx)
28731 .is_none()
28732 {
28733 self.ime_transaction.take();
28734 }
28735 }
28736
28737 fn bounds_for_range(
28738 &mut self,
28739 range_utf16: Range<usize>,
28740 element_bounds: gpui::Bounds<Pixels>,
28741 window: &mut Window,
28742 cx: &mut Context<Self>,
28743 ) -> Option<gpui::Bounds<Pixels>> {
28744 let text_layout_details = self.text_layout_details(window, cx);
28745 let CharacterDimensions {
28746 em_width,
28747 em_advance,
28748 line_height,
28749 } = self.character_dimensions(window, cx);
28750
28751 let snapshot = self.snapshot(window, cx);
28752 let scroll_position = snapshot.scroll_position();
28753 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
28754
28755 let start =
28756 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
28757 let x = Pixels::from(
28758 ScrollOffset::from(
28759 snapshot.x_for_display_point(start, &text_layout_details)
28760 + self.gutter_dimensions.full_width(),
28761 ) - scroll_left,
28762 );
28763 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
28764
28765 Some(Bounds {
28766 origin: element_bounds.origin + point(x, y),
28767 size: size(em_width, line_height),
28768 })
28769 }
28770
28771 fn character_index_for_point(
28772 &mut self,
28773 point: gpui::Point<Pixels>,
28774 _window: &mut Window,
28775 _cx: &mut Context<Self>,
28776 ) -> Option<usize> {
28777 let position_map = self.last_position_map.as_ref()?;
28778 if !position_map.text_hitbox.contains(&point) {
28779 return None;
28780 }
28781 let display_point = position_map.point_for_position(point).previous_valid;
28782 let anchor = position_map
28783 .snapshot
28784 .display_point_to_anchor(display_point, Bias::Left);
28785 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
28786 Some(utf16_offset.0.0)
28787 }
28788
28789 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
28790 self.expects_character_input
28791 }
28792}
28793
28794trait SelectionExt {
28795 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
28796 fn spanned_rows(
28797 &self,
28798 include_end_if_at_line_start: bool,
28799 map: &DisplaySnapshot,
28800 ) -> Range<MultiBufferRow>;
28801}
28802
28803impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
28804 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
28805 let start = self
28806 .start
28807 .to_point(map.buffer_snapshot())
28808 .to_display_point(map);
28809 let end = self
28810 .end
28811 .to_point(map.buffer_snapshot())
28812 .to_display_point(map);
28813 if self.reversed {
28814 end..start
28815 } else {
28816 start..end
28817 }
28818 }
28819
28820 fn spanned_rows(
28821 &self,
28822 include_end_if_at_line_start: bool,
28823 map: &DisplaySnapshot,
28824 ) -> Range<MultiBufferRow> {
28825 let start = self.start.to_point(map.buffer_snapshot());
28826 let mut end = self.end.to_point(map.buffer_snapshot());
28827 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
28828 end.row -= 1;
28829 }
28830
28831 let buffer_start = map.prev_line_boundary(start).0;
28832 let buffer_end = map.next_line_boundary(end).0;
28833 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
28834 }
28835}
28836
28837impl<T: InvalidationRegion> InvalidationStack<T> {
28838 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
28839 where
28840 S: Clone + ToOffset,
28841 {
28842 while let Some(region) = self.last() {
28843 let all_selections_inside_invalidation_ranges =
28844 if selections.len() == region.ranges().len() {
28845 selections
28846 .iter()
28847 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
28848 .all(|(selection, invalidation_range)| {
28849 let head = selection.head().to_offset(buffer);
28850 invalidation_range.start <= head && invalidation_range.end >= head
28851 })
28852 } else {
28853 false
28854 };
28855
28856 if all_selections_inside_invalidation_ranges {
28857 break;
28858 } else {
28859 self.pop();
28860 }
28861 }
28862 }
28863}
28864
28865#[derive(Clone)]
28866struct ErasedEditorImpl(Entity<Editor>);
28867
28868impl ui_input::ErasedEditor for ErasedEditorImpl {
28869 fn text(&self, cx: &App) -> String {
28870 self.0.read(cx).text(cx)
28871 }
28872
28873 fn set_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28874 self.0.update(cx, |this, cx| {
28875 this.set_text(text, window, cx);
28876 })
28877 }
28878
28879 fn clear(&self, window: &mut Window, cx: &mut App) {
28880 self.0.update(cx, |this, cx| this.clear(window, cx));
28881 }
28882
28883 fn set_placeholder_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28884 self.0.update(cx, |this, cx| {
28885 this.set_placeholder_text(text, window, cx);
28886 });
28887 }
28888
28889 fn focus_handle(&self, cx: &App) -> FocusHandle {
28890 self.0.read(cx).focus_handle(cx)
28891 }
28892
28893 fn render(&self, _: &mut Window, cx: &App) -> AnyElement {
28894 let settings = ThemeSettings::get_global(cx);
28895 let theme_color = cx.theme().colors();
28896
28897 let text_style = TextStyle {
28898 font_family: settings.ui_font.family.clone(),
28899 font_features: settings.ui_font.features.clone(),
28900 font_size: rems(0.875).into(),
28901 font_weight: settings.ui_font.weight,
28902 font_style: FontStyle::Normal,
28903 line_height: relative(1.2),
28904 color: theme_color.text,
28905 ..Default::default()
28906 };
28907 let editor_style = EditorStyle {
28908 background: theme_color.ghost_element_background,
28909 local_player: cx.theme().players().local(),
28910 syntax: cx.theme().syntax().clone(),
28911 text: text_style,
28912 ..Default::default()
28913 };
28914 EditorElement::new(&self.0, editor_style).into_any()
28915 }
28916
28917 fn as_any(&self) -> &dyn Any {
28918 &self.0
28919 }
28920
28921 fn move_selection_to_end(&self, window: &mut Window, cx: &mut App) {
28922 self.0.update(cx, |editor, cx| {
28923 let editor_offset = editor.buffer().read(cx).len(cx);
28924 editor.change_selections(
28925 SelectionEffects::scroll(Autoscroll::Next),
28926 window,
28927 cx,
28928 |s| s.select_ranges(Some(editor_offset..editor_offset)),
28929 );
28930 });
28931 }
28932
28933 fn subscribe(
28934 &self,
28935 mut callback: Box<dyn FnMut(ui_input::ErasedEditorEvent, &mut Window, &mut App) + 'static>,
28936 window: &mut Window,
28937 cx: &mut App,
28938 ) -> Subscription {
28939 window.subscribe(&self.0, cx, move |_, event: &EditorEvent, window, cx| {
28940 let event = match event {
28941 EditorEvent::BufferEdited => ui_input::ErasedEditorEvent::BufferEdited,
28942 EditorEvent::Blurred => ui_input::ErasedEditorEvent::Blurred,
28943 _ => return,
28944 };
28945 (callback)(event, window, cx);
28946 })
28947 }
28948
28949 fn set_masked(&self, masked: bool, _window: &mut Window, cx: &mut App) {
28950 self.0.update(cx, |editor, cx| {
28951 editor.set_masked(masked, cx);
28952 });
28953 }
28954}
28955impl<T> Default for InvalidationStack<T> {
28956 fn default() -> Self {
28957 Self(Default::default())
28958 }
28959}
28960
28961impl<T> Deref for InvalidationStack<T> {
28962 type Target = Vec<T>;
28963
28964 fn deref(&self) -> &Self::Target {
28965 &self.0
28966 }
28967}
28968
28969impl<T> DerefMut for InvalidationStack<T> {
28970 fn deref_mut(&mut self) -> &mut Self::Target {
28971 &mut self.0
28972 }
28973}
28974
28975impl InvalidationRegion for SnippetState {
28976 fn ranges(&self) -> &[Range<Anchor>] {
28977 &self.ranges[self.active_index]
28978 }
28979}
28980
28981fn edit_prediction_edit_text(
28982 current_snapshot: &BufferSnapshot,
28983 edits: &[(Range<Anchor>, impl AsRef<str>)],
28984 edit_preview: &EditPreview,
28985 include_deletions: bool,
28986 multibuffer_snapshot: &MultiBufferSnapshot,
28987 cx: &App,
28988) -> HighlightedText {
28989 let edits = edits
28990 .iter()
28991 .filter_map(|(anchor, text)| {
28992 Some((
28993 multibuffer_snapshot
28994 .anchor_range_to_buffer_anchor_range(anchor.clone())?
28995 .1,
28996 text,
28997 ))
28998 })
28999 .collect::<Vec<_>>();
29000
29001 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
29002}
29003
29004fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
29005 // Fallback for providers that don't provide edit_preview (like Copilot)
29006 // Just show the raw edit text with basic styling
29007 let mut text = String::new();
29008 let mut highlights = Vec::new();
29009
29010 let insertion_highlight_style = HighlightStyle {
29011 color: Some(cx.theme().colors().text),
29012 ..Default::default()
29013 };
29014
29015 for (_, edit_text) in edits {
29016 let start_offset = text.len();
29017 text.push_str(edit_text);
29018 let end_offset = text.len();
29019
29020 if start_offset < end_offset {
29021 highlights.push((start_offset..end_offset, insertion_highlight_style));
29022 }
29023 }
29024
29025 HighlightedText {
29026 text: text.into(),
29027 highlights,
29028 }
29029}
29030
29031pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
29032 match severity {
29033 lsp::DiagnosticSeverity::ERROR => colors.error,
29034 lsp::DiagnosticSeverity::WARNING => colors.warning,
29035 lsp::DiagnosticSeverity::INFORMATION => colors.info,
29036 lsp::DiagnosticSeverity::HINT => colors.info,
29037 _ => colors.ignored,
29038 }
29039}
29040
29041pub fn styled_runs_for_code_label<'a>(
29042 label: &'a CodeLabel,
29043 syntax_theme: &'a theme::SyntaxTheme,
29044 local_player: &'a theme::PlayerColor,
29045) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
29046 let fade_out = HighlightStyle {
29047 fade_out: Some(0.35),
29048 ..Default::default()
29049 };
29050
29051 if label.runs.is_empty() {
29052 let desc_start = label.filter_range.end;
29053 let fade_run =
29054 (desc_start < label.text.len()).then(|| (desc_start..label.text.len(), fade_out));
29055 return Either::Left(fade_run.into_iter());
29056 }
29057
29058 let mut prev_end = label.filter_range.end;
29059 Either::Right(
29060 label
29061 .runs
29062 .iter()
29063 .enumerate()
29064 .flat_map(move |(ix, (range, highlight_id))| {
29065 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
29066 HighlightStyle {
29067 color: Some(local_player.cursor),
29068 ..Default::default()
29069 }
29070 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
29071 HighlightStyle {
29072 background_color: Some(local_player.selection),
29073 ..Default::default()
29074 }
29075 } else if let Some(style) = syntax_theme.get(*highlight_id).cloned() {
29076 style
29077 } else {
29078 return Default::default();
29079 };
29080
29081 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
29082 let muted_style = style.highlight(fade_out);
29083 if range.start >= label.filter_range.end {
29084 if range.start > prev_end {
29085 runs.push((prev_end..range.start, fade_out));
29086 }
29087 runs.push((range.clone(), muted_style));
29088 } else if range.end <= label.filter_range.end {
29089 runs.push((range.clone(), style));
29090 } else {
29091 runs.push((range.start..label.filter_range.end, style));
29092 runs.push((label.filter_range.end..range.end, muted_style));
29093 }
29094 prev_end = cmp::max(prev_end, range.end);
29095
29096 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
29097 runs.push((prev_end..label.text.len(), fade_out));
29098 }
29099
29100 runs
29101 }),
29102 )
29103}
29104
29105pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
29106 let mut prev_index = 0;
29107 let mut prev_codepoint: Option<char> = None;
29108 text.char_indices()
29109 .chain([(text.len(), '\0')])
29110 .filter_map(move |(index, codepoint)| {
29111 let prev_codepoint = prev_codepoint.replace(codepoint)?;
29112 let is_boundary = index == text.len()
29113 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
29114 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
29115 if is_boundary {
29116 let chunk = &text[prev_index..index];
29117 prev_index = index;
29118 Some(chunk)
29119 } else {
29120 None
29121 }
29122 })
29123}
29124
29125/// Given a string of text immediately before the cursor, iterates over possible
29126/// strings a snippet could match to. More precisely: returns an iterator over
29127/// suffixes of `text` created by splitting at word boundaries (before & after
29128/// every non-word character).
29129///
29130/// Shorter suffixes are returned first.
29131pub(crate) fn snippet_candidate_suffixes<'a>(
29132 text: &'a str,
29133 is_word_char: &'a dyn Fn(char) -> bool,
29134) -> impl std::iter::Iterator<Item = &'a str> + 'a {
29135 let mut prev_index = text.len();
29136 let mut prev_codepoint = None;
29137 text.char_indices()
29138 .rev()
29139 .chain([(0, '\0')])
29140 .filter_map(move |(index, codepoint)| {
29141 let prev_index = std::mem::replace(&mut prev_index, index);
29142 let prev_codepoint = prev_codepoint.replace(codepoint)?;
29143 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
29144 None
29145 } else {
29146 let chunk = &text[prev_index..]; // go to end of string
29147 Some(chunk)
29148 }
29149 })
29150}
29151
29152pub trait RangeToAnchorExt: Sized {
29153 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
29154
29155 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
29156 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
29157 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
29158 }
29159}
29160
29161impl<T: ToOffset> RangeToAnchorExt for Range<T> {
29162 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
29163 let start_offset = self.start.to_offset(snapshot);
29164 let end_offset = self.end.to_offset(snapshot);
29165 if start_offset == end_offset {
29166 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
29167 } else {
29168 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
29169 }
29170 }
29171}
29172
29173pub trait RowExt {
29174 fn as_f64(&self) -> f64;
29175
29176 fn next_row(&self) -> Self;
29177
29178 fn previous_row(&self) -> Self;
29179
29180 fn minus(&self, other: Self) -> u32;
29181}
29182
29183impl RowExt for DisplayRow {
29184 fn as_f64(&self) -> f64 {
29185 self.0 as _
29186 }
29187
29188 fn next_row(&self) -> Self {
29189 Self(self.0 + 1)
29190 }
29191
29192 fn previous_row(&self) -> Self {
29193 Self(self.0.saturating_sub(1))
29194 }
29195
29196 fn minus(&self, other: Self) -> u32 {
29197 self.0 - other.0
29198 }
29199}
29200
29201impl RowExt for MultiBufferRow {
29202 fn as_f64(&self) -> f64 {
29203 self.0 as _
29204 }
29205
29206 fn next_row(&self) -> Self {
29207 Self(self.0 + 1)
29208 }
29209
29210 fn previous_row(&self) -> Self {
29211 Self(self.0.saturating_sub(1))
29212 }
29213
29214 fn minus(&self, other: Self) -> u32 {
29215 self.0 - other.0
29216 }
29217}
29218
29219trait RowRangeExt {
29220 type Row;
29221
29222 fn len(&self) -> usize;
29223
29224 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
29225}
29226
29227impl RowRangeExt for Range<MultiBufferRow> {
29228 type Row = MultiBufferRow;
29229
29230 fn len(&self) -> usize {
29231 (self.end.0 - self.start.0) as usize
29232 }
29233
29234 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
29235 (self.start.0..self.end.0).map(MultiBufferRow)
29236 }
29237}
29238
29239impl RowRangeExt for Range<DisplayRow> {
29240 type Row = DisplayRow;
29241
29242 fn len(&self) -> usize {
29243 (self.end.0 - self.start.0) as usize
29244 }
29245
29246 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
29247 (self.start.0..self.end.0).map(DisplayRow)
29248 }
29249}
29250
29251/// If select range has more than one line, we
29252/// just point the cursor to range.start.
29253fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
29254 if range.start.row == range.end.row {
29255 range
29256 } else {
29257 range.start..range.start
29258 }
29259}
29260pub struct KillRing(ClipboardItem);
29261impl Global for KillRing {}
29262
29263const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
29264
29265enum BreakpointPromptEditAction {
29266 Log,
29267 Condition,
29268 HitCondition,
29269}
29270
29271struct BreakpointPromptEditor {
29272 pub(crate) prompt: Entity<Editor>,
29273 editor: WeakEntity<Editor>,
29274 breakpoint_anchor: Anchor,
29275 breakpoint: Breakpoint,
29276 edit_action: BreakpointPromptEditAction,
29277 block_ids: HashSet<CustomBlockId>,
29278 editor_margins: Arc<Mutex<EditorMargins>>,
29279 _subscriptions: Vec<Subscription>,
29280}
29281
29282impl BreakpointPromptEditor {
29283 const MAX_LINES: u8 = 4;
29284
29285 fn new(
29286 editor: WeakEntity<Editor>,
29287 breakpoint_anchor: Anchor,
29288 breakpoint: Breakpoint,
29289 edit_action: BreakpointPromptEditAction,
29290 window: &mut Window,
29291 cx: &mut Context<Self>,
29292 ) -> Self {
29293 let base_text = match edit_action {
29294 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
29295 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
29296 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
29297 }
29298 .map(|msg| msg.to_string())
29299 .unwrap_or_default();
29300
29301 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
29302 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
29303
29304 let prompt = cx.new(|cx| {
29305 let mut prompt = Editor::new(
29306 EditorMode::AutoHeight {
29307 min_lines: 1,
29308 max_lines: Some(Self::MAX_LINES as usize),
29309 },
29310 buffer,
29311 None,
29312 window,
29313 cx,
29314 );
29315 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
29316 prompt.set_show_cursor_when_unfocused(false, cx);
29317 prompt.set_placeholder_text(
29318 match edit_action {
29319 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
29320 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
29321 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
29322 },
29323 window,
29324 cx,
29325 );
29326
29327 prompt
29328 });
29329
29330 Self {
29331 prompt,
29332 editor,
29333 breakpoint_anchor,
29334 breakpoint,
29335 edit_action,
29336 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
29337 block_ids: Default::default(),
29338 _subscriptions: vec![],
29339 }
29340 }
29341
29342 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
29343 self.block_ids.extend(block_ids)
29344 }
29345
29346 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
29347 if let Some(editor) = self.editor.upgrade() {
29348 let message = self
29349 .prompt
29350 .read(cx)
29351 .buffer
29352 .read(cx)
29353 .as_singleton()
29354 .expect("A multi buffer in breakpoint prompt isn't possible")
29355 .read(cx)
29356 .as_rope()
29357 .to_string();
29358
29359 editor.update(cx, |editor, cx| {
29360 editor.edit_breakpoint_at_anchor(
29361 self.breakpoint_anchor,
29362 self.breakpoint.clone(),
29363 match self.edit_action {
29364 BreakpointPromptEditAction::Log => {
29365 BreakpointEditAction::EditLogMessage(message.into())
29366 }
29367 BreakpointPromptEditAction::Condition => {
29368 BreakpointEditAction::EditCondition(message.into())
29369 }
29370 BreakpointPromptEditAction::HitCondition => {
29371 BreakpointEditAction::EditHitCondition(message.into())
29372 }
29373 },
29374 cx,
29375 );
29376
29377 editor.remove_blocks(self.block_ids.clone(), None, cx);
29378 cx.focus_self(window);
29379 });
29380 }
29381 }
29382
29383 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
29384 self.editor
29385 .update(cx, |editor, cx| {
29386 editor.remove_blocks(self.block_ids.clone(), None, cx);
29387 window.focus(&editor.focus_handle, cx);
29388 })
29389 .log_err();
29390 }
29391
29392 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
29393 let settings = ThemeSettings::get_global(cx);
29394 let text_style = TextStyle {
29395 color: if self.prompt.read(cx).read_only(cx) {
29396 cx.theme().colors().text_disabled
29397 } else {
29398 cx.theme().colors().text
29399 },
29400 font_family: settings.buffer_font.family.clone(),
29401 font_fallbacks: settings.buffer_font.fallbacks.clone(),
29402 font_size: settings.buffer_font_size(cx).into(),
29403 font_weight: settings.buffer_font.weight,
29404 line_height: relative(settings.buffer_line_height.value()),
29405 ..Default::default()
29406 };
29407 EditorElement::new(
29408 &self.prompt,
29409 EditorStyle {
29410 background: cx.theme().colors().editor_background,
29411 local_player: cx.theme().players().local(),
29412 text: text_style,
29413 ..Default::default()
29414 },
29415 )
29416 }
29417
29418 fn render_close_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
29419 let focus_handle = self.prompt.focus_handle(cx);
29420 IconButton::new("cancel", IconName::Close)
29421 .icon_color(Color::Muted)
29422 .shape(IconButtonShape::Square)
29423 .tooltip(move |_window, cx| {
29424 Tooltip::for_action_in("Cancel", &menu::Cancel, &focus_handle, cx)
29425 })
29426 .on_click(cx.listener(|this, _, window, cx| {
29427 this.cancel(&menu::Cancel, window, cx);
29428 }))
29429 }
29430
29431 fn render_confirm_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
29432 let focus_handle = self.prompt.focus_handle(cx);
29433 IconButton::new("confirm", IconName::Return)
29434 .icon_color(Color::Muted)
29435 .shape(IconButtonShape::Square)
29436 .tooltip(move |_window, cx| {
29437 Tooltip::for_action_in("Confirm", &menu::Confirm, &focus_handle, cx)
29438 })
29439 .on_click(cx.listener(|this, _, window, cx| {
29440 this.confirm(&menu::Confirm, window, cx);
29441 }))
29442 }
29443}
29444
29445impl Render for BreakpointPromptEditor {
29446 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
29447 let ui_font_size = ThemeSettings::get_global(cx).ui_font_size(cx);
29448 let editor_margins = *self.editor_margins.lock();
29449 let gutter_dimensions = editor_margins.gutter;
29450 let left_gutter_width = gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0);
29451 let right_padding = editor_margins.right + px(9.);
29452 h_flex()
29453 .key_context("Editor")
29454 .bg(cx.theme().colors().editor_background)
29455 .border_y_1()
29456 .border_color(cx.theme().status().info_border)
29457 .size_full()
29458 .py(window.line_height() / 2.5)
29459 .pr(right_padding)
29460 .on_action(cx.listener(Self::confirm))
29461 .on_action(cx.listener(Self::cancel))
29462 .child(
29463 WithRemSize::new(ui_font_size)
29464 .h_full()
29465 .w(left_gutter_width)
29466 .flex()
29467 .flex_row()
29468 .flex_shrink_0()
29469 .items_center()
29470 .justify_center()
29471 .gap_1()
29472 .child(self.render_close_button(cx)),
29473 )
29474 .child(
29475 h_flex()
29476 .w_full()
29477 .justify_between()
29478 .child(div().flex_1().child(self.render_prompt_editor(cx)))
29479 .child(
29480 WithRemSize::new(ui_font_size)
29481 .flex()
29482 .flex_row()
29483 .items_center()
29484 .child(self.render_confirm_button(cx)),
29485 ),
29486 )
29487 }
29488}
29489
29490impl Focusable for BreakpointPromptEditor {
29491 fn focus_handle(&self, cx: &App) -> FocusHandle {
29492 self.prompt.focus_handle(cx)
29493 }
29494}
29495
29496fn all_edits_insertions_or_deletions(
29497 edits: &Vec<(Range<Anchor>, Arc<str>)>,
29498 snapshot: &MultiBufferSnapshot,
29499) -> bool {
29500 let mut all_insertions = true;
29501 let mut all_deletions = true;
29502
29503 for (range, new_text) in edits.iter() {
29504 let range_is_empty = range.to_offset(snapshot).is_empty();
29505 let text_is_empty = new_text.is_empty();
29506
29507 if range_is_empty != text_is_empty {
29508 if range_is_empty {
29509 all_deletions = false;
29510 } else {
29511 all_insertions = false;
29512 }
29513 } else {
29514 return false;
29515 }
29516
29517 if !all_insertions && !all_deletions {
29518 return false;
29519 }
29520 }
29521 all_insertions || all_deletions
29522}
29523
29524struct MissingEditPredictionKeybindingTooltip;
29525
29526impl Render for MissingEditPredictionKeybindingTooltip {
29527 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
29528 ui::tooltip_container(cx, |container, cx| {
29529 container
29530 .flex_shrink_0()
29531 .max_w_80()
29532 .min_h(rems_from_px(124.))
29533 .justify_between()
29534 .child(
29535 v_flex()
29536 .flex_1()
29537 .text_ui_sm(cx)
29538 .child(Label::new("Conflict with Accept Keybinding"))
29539 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
29540 )
29541 .child(
29542 h_flex()
29543 .pb_1()
29544 .gap_1()
29545 .items_end()
29546 .w_full()
29547 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
29548 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
29549 }))
29550 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
29551 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
29552 })),
29553 )
29554 })
29555 }
29556}
29557
29558#[derive(Debug, Clone, Copy, PartialEq)]
29559pub struct LineHighlight {
29560 pub background: Background,
29561 pub border: Option<gpui::Hsla>,
29562 pub include_gutter: bool,
29563 pub type_id: Option<TypeId>,
29564}
29565
29566struct LineManipulationResult {
29567 pub new_text: String,
29568 pub line_count_before: usize,
29569 pub line_count_after: usize,
29570}
29571
29572fn render_diff_hunk_controls(
29573 row: u32,
29574 status: &DiffHunkStatus,
29575 hunk_range: Range<Anchor>,
29576 is_created_file: bool,
29577 line_height: Pixels,
29578 editor: &Entity<Editor>,
29579 _window: &mut Window,
29580 cx: &mut App,
29581) -> AnyElement {
29582 h_flex()
29583 .h(line_height)
29584 .mr_1()
29585 .gap_1()
29586 .px_0p5()
29587 .pb_1()
29588 .border_x_1()
29589 .border_b_1()
29590 .border_color(cx.theme().colors().border_variant)
29591 .rounded_b_lg()
29592 .bg(cx.theme().colors().editor_background)
29593 .gap_1()
29594 .block_mouse_except_scroll()
29595 .shadow_md()
29596 .child(if status.has_secondary_hunk() {
29597 Button::new(("stage", row as u64), "Stage")
29598 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
29599 .tooltip({
29600 let focus_handle = editor.focus_handle(cx);
29601 move |_window, cx| {
29602 Tooltip::for_action_in(
29603 "Stage Hunk",
29604 &::git::ToggleStaged,
29605 &focus_handle,
29606 cx,
29607 )
29608 }
29609 })
29610 .on_click({
29611 let editor = editor.clone();
29612 move |_event, _window, cx| {
29613 editor.update(cx, |editor, cx| {
29614 editor.stage_or_unstage_diff_hunks(
29615 true,
29616 vec![hunk_range.start..hunk_range.start],
29617 cx,
29618 );
29619 });
29620 }
29621 })
29622 } else {
29623 Button::new(("unstage", row as u64), "Unstage")
29624 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
29625 .tooltip({
29626 let focus_handle = editor.focus_handle(cx);
29627 move |_window, cx| {
29628 Tooltip::for_action_in(
29629 "Unstage Hunk",
29630 &::git::ToggleStaged,
29631 &focus_handle,
29632 cx,
29633 )
29634 }
29635 })
29636 .on_click({
29637 let editor = editor.clone();
29638 move |_event, _window, cx| {
29639 editor.update(cx, |editor, cx| {
29640 editor.stage_or_unstage_diff_hunks(
29641 false,
29642 vec![hunk_range.start..hunk_range.start],
29643 cx,
29644 );
29645 });
29646 }
29647 })
29648 })
29649 .child(
29650 Button::new(("restore", row as u64), "Restore")
29651 .tooltip({
29652 let focus_handle = editor.focus_handle(cx);
29653 move |_window, cx| {
29654 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
29655 }
29656 })
29657 .on_click({
29658 let editor = editor.clone();
29659 move |_event, window, cx| {
29660 editor.update(cx, |editor, cx| {
29661 let snapshot = editor.snapshot(window, cx);
29662 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
29663 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
29664 });
29665 }
29666 })
29667 .disabled(is_created_file),
29668 )
29669 .when(
29670 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
29671 |el| {
29672 el.child(
29673 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
29674 .shape(IconButtonShape::Square)
29675 .icon_size(IconSize::Small)
29676 // .disabled(!has_multiple_hunks)
29677 .tooltip({
29678 let focus_handle = editor.focus_handle(cx);
29679 move |_window, cx| {
29680 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
29681 }
29682 })
29683 .on_click({
29684 let editor = editor.clone();
29685 move |_event, window, cx| {
29686 editor.update(cx, |editor, cx| {
29687 let snapshot = editor.snapshot(window, cx);
29688 let position =
29689 hunk_range.end.to_point(&snapshot.buffer_snapshot());
29690 editor.go_to_hunk_before_or_after_position(
29691 &snapshot,
29692 position,
29693 Direction::Next,
29694 true,
29695 window,
29696 cx,
29697 );
29698 editor.expand_selected_diff_hunks(cx);
29699 });
29700 }
29701 }),
29702 )
29703 .child(
29704 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
29705 .shape(IconButtonShape::Square)
29706 .icon_size(IconSize::Small)
29707 // .disabled(!has_multiple_hunks)
29708 .tooltip({
29709 let focus_handle = editor.focus_handle(cx);
29710 move |_window, cx| {
29711 Tooltip::for_action_in(
29712 "Previous Hunk",
29713 &GoToPreviousHunk,
29714 &focus_handle,
29715 cx,
29716 )
29717 }
29718 })
29719 .on_click({
29720 let editor = editor.clone();
29721 move |_event, window, cx| {
29722 editor.update(cx, |editor, cx| {
29723 let snapshot = editor.snapshot(window, cx);
29724 let point =
29725 hunk_range.start.to_point(&snapshot.buffer_snapshot());
29726 editor.go_to_hunk_before_or_after_position(
29727 &snapshot,
29728 point,
29729 Direction::Prev,
29730 true,
29731 window,
29732 cx,
29733 );
29734 editor.expand_selected_diff_hunks(cx);
29735 });
29736 }
29737 }),
29738 )
29739 },
29740 )
29741 .into_any_element()
29742}
29743
29744pub fn multibuffer_context_lines(cx: &App) -> u32 {
29745 EditorSettings::try_get(cx)
29746 .map(|settings| settings.excerpt_context_lines)
29747 .unwrap_or(2)
29748 .min(32)
29749}