1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//!
11//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
12//!
13//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
14pub mod actions;
15pub mod blink_manager;
16mod bracket_colorization;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod document_colors;
21mod document_symbols;
22mod editor_settings;
23mod element;
24mod folding_ranges;
25mod git;
26mod highlight_matching_bracket;
27mod hover_links;
28pub mod hover_popover;
29mod indent_guides;
30mod inlays;
31pub mod items;
32mod jsx_tag_auto_close;
33mod linked_editing_ranges;
34mod lsp_ext;
35mod mouse_context_menu;
36pub mod movement;
37mod persistence;
38mod runnables;
39mod rust_analyzer_ext;
40pub mod scroll;
41mod selections_collection;
42pub mod semantic_tokens;
43mod split;
44pub mod split_editor_view;
45
46#[cfg(test)]
47mod code_completion_tests;
48#[cfg(test)]
49mod edit_prediction_tests;
50#[cfg(test)]
51mod editor_tests;
52mod signature_help;
53#[cfg(any(test, feature = "test-support"))]
54pub mod test;
55
56pub(crate) use actions::*;
57pub use display_map::{
58 ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder, HighlightKey,
59 SemanticTokenHighlight,
60};
61pub use edit_prediction_types::Direction;
62pub use editor_settings::{
63 CompletionDetailAlignment, CurrentLineHighlight, DiffViewStyle, DocumentColorsRenderMode,
64 EditorSettings, EditorSettingsScrollbarProxy, HideMouseMode, ScrollBeyondLastLine,
65 ScrollbarAxes, SearchSettings, ShowMinimap, ui_scrollbar_settings_from_raw,
66};
67pub use element::{
68 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
69 render_breadcrumb_text,
70};
71pub use git::blame::BlameRenderer;
72pub use hover_popover::hover_markdown_style;
73pub use inlays::Inlay;
74pub use items::MAX_TAB_TITLE_LEN;
75pub use linked_editing_ranges::LinkedEdits;
76pub use lsp::CompletionContext;
77pub use lsp_ext::lsp_tasks;
78pub use multi_buffer::{
79 Anchor, AnchorRangeExt, BufferOffset, ExcerptRange, MBTextSummary, MultiBuffer,
80 MultiBufferOffset, MultiBufferOffsetUtf16, MultiBufferSnapshot, PathKey, RowInfo, ToOffset,
81 ToPoint,
82};
83pub use split::{SplittableEditor, ToggleSplitDiff};
84pub use split_editor_view::SplitEditorView;
85pub use text::Bias;
86
87use ::git::{Restore, blame::BlameEntry, commit::ParsedCommitMessage, status::FileStatus};
88use aho_corasick::{AhoCorasick, AhoCorasickBuilder, BuildError};
89use anyhow::{Context as _, Result, anyhow, bail};
90use blink_manager::BlinkManager;
91use buffer_diff::DiffHunkStatus;
92use client::{Collaborator, ParticipantIndex, parse_zed_link};
93use clock::ReplicaId;
94use code_context_menus::{
95 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
96 CompletionsMenu, ContextMenuOrigin,
97};
98use collections::{BTreeMap, HashMap, HashSet, VecDeque};
99use convert_case::{Case, Casing};
100use dap::TelemetrySpawnLocation;
101use display_map::*;
102use document_colors::LspColorData;
103use edit_prediction_types::{
104 EditPredictionDelegate, EditPredictionDelegateHandle, EditPredictionDiscardReason,
105 EditPredictionGranularity, SuggestionDisplayType,
106};
107use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
108use element::{LineWithInvisibles, PositionMap, layout_line};
109use futures::{
110 FutureExt,
111 future::{self, Shared, join},
112};
113use fuzzy::{StringMatch, StringMatchCandidate};
114use git::blame::{GitBlame, GlobalBlameRenderer};
115use gpui::{
116 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
117 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
118 DispatchPhase, Edges, Entity, EntityId, EntityInputHandler, EventEmitter, FocusHandle,
119 FocusOutEvent, Focusable, FontId, FontStyle, FontWeight, Global, HighlightStyle, Hsla,
120 KeyContext, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, PaintQuad, ParentElement,
121 Pixels, PressureStage, Render, ScrollHandle, SharedString, SharedUri, Size, Stateful, Styled,
122 Subscription, Task, TextRun, TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle,
123 UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window, div, point, prelude::*,
124 pulsating_between, px, relative, size,
125};
126use hover_links::{HoverLink, HoveredLinkState, find_file};
127use hover_popover::{HoverState, hide_hover};
128use indent_guides::ActiveIndentGuidesState;
129use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
130use itertools::{Either, Itertools};
131use language::{
132 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
133 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
134 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
135 IndentSize, Language, LanguageAwareStyling, LanguageName, LanguageRegistry, LanguageScope,
136 LocalFile, OffsetRangeExt, OutlineItem, Point, Selection, SelectionGoal, TextObject,
137 TransactionId, TreeSitterOptions, WordsQuery,
138 language_settings::{
139 self, AllLanguageSettings, LanguageSettings, LspInsertMode, RewrapBehavior,
140 WordsCompletionMode, all_language_settings,
141 },
142 point_from_lsp, point_to_lsp, text_diff_with_options,
143};
144use linked_editing_ranges::refresh_linked_ranges;
145use lsp::{
146 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
147 LanguageServerId,
148};
149use markdown::Markdown;
150use mouse_context_menu::MouseContextMenu;
151use movement::TextLayoutDetails;
152use multi_buffer::{
153 ExcerptBoundaryInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint,
154 MultiBufferRow,
155};
156use parking_lot::Mutex;
157use persistence::EditorDb;
158use project::{
159 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
160 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
161 InvalidationStrategy, Location, LocationLink, LspAction, PrepareRenameResponse, Project,
162 ProjectItem, ProjectPath, ProjectTransaction,
163 debugger::{
164 breakpoint_store::{
165 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
166 BreakpointStore, BreakpointStoreEvent,
167 },
168 session::{Session, SessionEvent},
169 },
170 git_store::GitStoreEvent,
171 lsp_store::{
172 BufferSemanticTokens, CacheInlayHints, CompletionDocumentation, FormatTrigger,
173 LspFormatTarget, OpenLspBufferHandle, RefreshForServer,
174 },
175 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
176};
177use rand::seq::SliceRandom;
178use regex::Regex;
179use rpc::{ErrorCode, ErrorExt, proto::PeerId};
180use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, SharedScrollAnchor};
181use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
182use serde::{Deserialize, Serialize};
183use settings::{
184 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
185 update_settings_file,
186};
187use smallvec::{SmallVec, smallvec};
188use snippet::Snippet;
189use std::{
190 any::{Any, TypeId},
191 borrow::Cow,
192 cell::{OnceCell, RefCell},
193 cmp::{self, Ordering, Reverse},
194 collections::hash_map,
195 iter::{self, Peekable},
196 mem,
197 num::NonZeroU32,
198 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
199 path::{Path, PathBuf},
200 rc::Rc,
201 sync::Arc,
202 time::{Duration, Instant},
203};
204use task::TaskVariables;
205use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _, ToPoint as _};
206use theme::{
207 AccentColors, ActiveTheme, GlobalTheme, PlayerColor, StatusColors, SyntaxTheme, Theme,
208};
209use theme_settings::{ThemeSettings, observe_buffer_font_size_adjustment};
210use ui::{
211 Avatar, ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape,
212 IconName, IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
213 utils::WithRemSize,
214};
215use ui_input::ErasedEditor;
216use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
217use workspace::{
218 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, NavigationEntry, OpenInTerminal,
219 OpenTerminal, Pane, RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection,
220 TabBarSettings, Toast, ViewId, Workspace, WorkspaceId, WorkspaceSettings,
221 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
222 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
223 searchable::SearchEvent,
224};
225pub use zed_actions::editor::RevealInFileManager;
226use zed_actions::editor::{MoveDown, MoveUp};
227
228use crate::{
229 code_context_menus::CompletionsMenuSource,
230 editor_settings::MultiCursorModifier,
231 hover_links::{find_url, find_url_from_range},
232 inlays::{
233 InlineValueCache,
234 inlay_hints::{LspInlayHintData, inlay_hint_settings},
235 },
236 runnables::{ResolvedTasks, RunnableData, RunnableTasks},
237 scroll::{ScrollOffset, ScrollPixelOffset},
238 selections_collection::resolve_selections_wrapping_blocks,
239 semantic_tokens::SemanticTokenState,
240 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
241};
242
243pub const FILE_HEADER_HEIGHT: u32 = 2;
244pub const BUFFER_HEADER_PADDING: Rems = rems(0.25);
245pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
246const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
247const MAX_LINE_LEN: usize = 1024;
248const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
249const MAX_SELECTION_HISTORY_LEN: usize = 1024;
250pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
251#[doc(hidden)]
252pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
253pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
254
255pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
256pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
257pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
258pub const LSP_REQUEST_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(50);
259
260pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
261pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
262
263pub type RenderDiffHunkControlsFn = Arc<
264 dyn Fn(
265 u32,
266 &DiffHunkStatus,
267 Range<Anchor>,
268 bool,
269 Pixels,
270 &Entity<Editor>,
271 &mut Window,
272 &mut App,
273 ) -> AnyElement,
274>;
275
276enum ReportEditorEvent {
277 Saved { auto_saved: bool },
278 EditorOpened,
279 Closed,
280}
281
282impl ReportEditorEvent {
283 pub fn event_type(&self) -> &'static str {
284 match self {
285 Self::Saved { .. } => "Editor Saved",
286 Self::EditorOpened => "Editor Opened",
287 Self::Closed => "Editor Closed",
288 }
289 }
290}
291
292pub enum ActiveDebugLine {}
293pub enum DebugStackFrameLine {}
294
295pub enum ConflictsOuter {}
296pub enum ConflictsOurs {}
297pub enum ConflictsTheirs {}
298pub enum ConflictsOursMarker {}
299pub enum ConflictsTheirsMarker {}
300
301pub struct HunkAddedColor;
302pub struct HunkRemovedColor;
303
304#[derive(Debug, Copy, Clone, PartialEq, Eq)]
305pub enum Navigated {
306 Yes,
307 No,
308}
309
310impl Navigated {
311 pub fn from_bool(yes: bool) -> Navigated {
312 if yes { Navigated::Yes } else { Navigated::No }
313 }
314}
315
316#[derive(Debug, Clone, PartialEq, Eq)]
317enum DisplayDiffHunk {
318 Folded {
319 display_row: DisplayRow,
320 },
321 Unfolded {
322 is_created_file: bool,
323 diff_base_byte_range: Range<usize>,
324 display_row_range: Range<DisplayRow>,
325 multi_buffer_range: Range<Anchor>,
326 status: DiffHunkStatus,
327 word_diffs: Vec<Range<MultiBufferOffset>>,
328 },
329}
330
331pub enum HideMouseCursorOrigin {
332 TypingAction,
333 MovementAction,
334}
335
336pub fn init(cx: &mut App) {
337 cx.set_global(GlobalBlameRenderer(Arc::new(())));
338 cx.set_global(breadcrumbs::RenderBreadcrumbText(render_breadcrumb_text));
339
340 workspace::register_project_item::<Editor>(cx);
341 workspace::FollowableViewRegistry::register::<Editor>(cx);
342 workspace::register_serializable_item::<Editor>(cx);
343
344 cx.observe_new(
345 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
346 workspace.register_action(Editor::new_file);
347 workspace.register_action(Editor::new_file_split);
348 workspace.register_action(Editor::new_file_vertical);
349 workspace.register_action(Editor::new_file_horizontal);
350 workspace.register_action(Editor::cancel_language_server_work);
351 workspace.register_action(Editor::toggle_focus);
352 },
353 )
354 .detach();
355
356 cx.on_action(move |_: &workspace::NewFile, cx| {
357 let app_state = workspace::AppState::global(cx);
358 workspace::open_new(
359 Default::default(),
360 app_state,
361 cx,
362 |workspace, window, cx| Editor::new_file(workspace, &Default::default(), window, cx),
363 )
364 .detach_and_log_err(cx);
365 })
366 .on_action(move |_: &workspace::NewWindow, cx| {
367 let app_state = workspace::AppState::global(cx);
368 workspace::open_new(
369 Default::default(),
370 app_state,
371 cx,
372 |workspace, window, cx| {
373 cx.activate(true);
374 Editor::new_file(workspace, &Default::default(), window, cx)
375 },
376 )
377 .detach_and_log_err(cx);
378 });
379 _ = ui_input::ERASED_EDITOR_FACTORY.set(|window, cx| {
380 Arc::new(ErasedEditorImpl(
381 cx.new(|cx| Editor::single_line(window, cx)),
382 )) as Arc<dyn ErasedEditor>
383 });
384 _ = multi_buffer::EXCERPT_CONTEXT_LINES.set(multibuffer_context_lines);
385}
386
387pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
388 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
389}
390
391pub trait DiagnosticRenderer {
392 fn render_group(
393 &self,
394 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
395 buffer_id: BufferId,
396 snapshot: EditorSnapshot,
397 editor: WeakEntity<Editor>,
398 language_registry: Option<Arc<LanguageRegistry>>,
399 cx: &mut App,
400 ) -> Vec<BlockProperties<Anchor>>;
401
402 fn render_hover(
403 &self,
404 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
405 range: Range<Point>,
406 buffer_id: BufferId,
407 language_registry: Option<Arc<LanguageRegistry>>,
408 cx: &mut App,
409 ) -> Option<Entity<markdown::Markdown>>;
410
411 fn open_link(
412 &self,
413 editor: &mut Editor,
414 link: SharedString,
415 window: &mut Window,
416 cx: &mut Context<Editor>,
417 );
418}
419
420pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
421
422impl GlobalDiagnosticRenderer {
423 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
424 cx.try_global::<Self>().map(|g| g.0.clone())
425 }
426}
427
428impl gpui::Global for GlobalDiagnosticRenderer {}
429pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
430 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
431}
432
433pub struct SearchWithinRange;
434
435trait InvalidationRegion {
436 fn ranges(&self) -> &[Range<Anchor>];
437}
438
439#[derive(Clone, Debug, PartialEq)]
440pub enum SelectPhase {
441 Begin {
442 position: DisplayPoint,
443 add: bool,
444 click_count: usize,
445 },
446 BeginColumnar {
447 position: DisplayPoint,
448 reset: bool,
449 mode: ColumnarMode,
450 goal_column: u32,
451 },
452 Extend {
453 position: DisplayPoint,
454 click_count: usize,
455 },
456 Update {
457 position: DisplayPoint,
458 goal_column: u32,
459 scroll_delta: gpui::Point<f32>,
460 },
461 End,
462}
463
464#[derive(Clone, Debug, PartialEq)]
465pub enum ColumnarMode {
466 FromMouse,
467 FromSelection,
468}
469
470#[derive(Clone, Debug)]
471pub enum SelectMode {
472 Character,
473 Word(Range<Anchor>),
474 Line(Range<Anchor>),
475 All,
476}
477
478#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
479pub enum SizingBehavior {
480 /// The editor will layout itself using `size_full` and will include the vertical
481 /// scroll margin as requested by user settings.
482 #[default]
483 Default,
484 /// The editor will layout itself using `size_full`, but will not have any
485 /// vertical overscroll.
486 ExcludeOverscrollMargin,
487 /// The editor will request a vertical size according to its content and will be
488 /// layouted without a vertical scroll margin.
489 SizeByContent,
490}
491
492#[derive(Clone, PartialEq, Eq, Debug)]
493pub enum EditorMode {
494 SingleLine,
495 AutoHeight {
496 min_lines: usize,
497 max_lines: Option<usize>,
498 },
499 Full {
500 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
501 scale_ui_elements_with_buffer_font_size: bool,
502 /// When set to `true`, the editor will render a background for the active line.
503 show_active_line_background: bool,
504 /// Determines the sizing behavior for this editor
505 sizing_behavior: SizingBehavior,
506 },
507 Minimap {
508 parent: WeakEntity<Editor>,
509 },
510}
511
512impl EditorMode {
513 pub fn full() -> Self {
514 Self::Full {
515 scale_ui_elements_with_buffer_font_size: true,
516 show_active_line_background: true,
517 sizing_behavior: SizingBehavior::Default,
518 }
519 }
520
521 #[inline]
522 pub fn is_full(&self) -> bool {
523 matches!(self, Self::Full { .. })
524 }
525
526 #[inline]
527 pub fn is_single_line(&self) -> bool {
528 matches!(self, Self::SingleLine { .. })
529 }
530
531 #[inline]
532 fn is_minimap(&self) -> bool {
533 matches!(self, Self::Minimap { .. })
534 }
535}
536
537#[derive(Copy, Clone, Debug)]
538pub enum SoftWrap {
539 /// Prefer not to wrap at all.
540 ///
541 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
542 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
543 GitDiff,
544 /// Prefer a single line generally, unless an overly long line is encountered.
545 None,
546 /// Soft wrap lines that exceed the editor width.
547 EditorWidth,
548 /// Soft wrap lines at the preferred line length.
549 Column(u32),
550 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
551 Bounded(u32),
552}
553
554#[derive(Clone)]
555pub struct EditorStyle {
556 pub background: Hsla,
557 pub border: Hsla,
558 pub local_player: PlayerColor,
559 pub text: TextStyle,
560 pub scrollbar_width: Pixels,
561 pub syntax: Arc<SyntaxTheme>,
562 pub status: StatusColors,
563 pub inlay_hints_style: HighlightStyle,
564 pub edit_prediction_styles: EditPredictionStyles,
565 pub unnecessary_code_fade: f32,
566 pub show_underlines: bool,
567}
568
569impl Default for EditorStyle {
570 fn default() -> Self {
571 Self {
572 background: Hsla::default(),
573 border: Hsla::default(),
574 local_player: PlayerColor::default(),
575 text: TextStyle::default(),
576 scrollbar_width: Pixels::default(),
577 syntax: Default::default(),
578 // HACK: Status colors don't have a real default.
579 // We should look into removing the status colors from the editor
580 // style and retrieve them directly from the theme.
581 status: StatusColors::dark(),
582 inlay_hints_style: HighlightStyle::default(),
583 edit_prediction_styles: EditPredictionStyles {
584 insertion: HighlightStyle::default(),
585 whitespace: HighlightStyle::default(),
586 },
587 unnecessary_code_fade: Default::default(),
588 show_underlines: true,
589 }
590 }
591}
592
593pub fn make_inlay_hints_style(cx: &App) -> HighlightStyle {
594 let show_background = AllLanguageSettings::get_global(cx)
595 .defaults
596 .inlay_hints
597 .show_background;
598
599 let mut style = cx
600 .theme()
601 .syntax()
602 .style_for_name("hint")
603 .unwrap_or_default();
604
605 if style.color.is_none() {
606 style.color = Some(cx.theme().status().hint);
607 }
608
609 if !show_background {
610 style.background_color = None;
611 return style;
612 }
613
614 if style.background_color.is_none() {
615 style.background_color = Some(cx.theme().status().hint_background);
616 }
617
618 style
619}
620
621pub fn make_suggestion_styles(cx: &App) -> EditPredictionStyles {
622 EditPredictionStyles {
623 insertion: HighlightStyle {
624 color: Some(cx.theme().status().predictive),
625 ..HighlightStyle::default()
626 },
627 whitespace: HighlightStyle {
628 background_color: Some(cx.theme().status().created_background),
629 ..HighlightStyle::default()
630 },
631 }
632}
633
634type CompletionId = usize;
635
636pub(crate) enum EditDisplayMode {
637 TabAccept,
638 DiffPopover,
639 Inline,
640}
641
642enum EditPrediction {
643 Edit {
644 // TODO could be a language::Anchor?
645 edits: Vec<(Range<Anchor>, Arc<str>)>,
646 /// Predicted cursor position as (anchor, offset_from_anchor).
647 /// The anchor is in multibuffer coordinates; after applying edits,
648 /// resolve the anchor and add the offset to get the final cursor position.
649 cursor_position: Option<(Anchor, usize)>,
650 edit_preview: Option<EditPreview>,
651 display_mode: EditDisplayMode,
652 snapshot: BufferSnapshot,
653 },
654 /// Move to a specific location in the active editor
655 MoveWithin {
656 target: Anchor,
657 snapshot: BufferSnapshot,
658 },
659 /// Move to a specific location in a different editor (not the active one)
660 MoveOutside {
661 target: language::Anchor,
662 snapshot: BufferSnapshot,
663 },
664}
665
666struct EditPredictionState {
667 inlay_ids: Vec<InlayId>,
668 completion: EditPrediction,
669 completion_id: Option<SharedString>,
670 invalidation_range: Option<Range<Anchor>>,
671}
672
673enum EditPredictionSettings {
674 Disabled,
675 Enabled {
676 show_in_menu: bool,
677 preview_requires_modifier: bool,
678 },
679}
680
681#[derive(Debug, Clone)]
682struct InlineDiagnostic {
683 message: SharedString,
684 group_id: usize,
685 is_primary: bool,
686 start: Point,
687 severity: lsp::DiagnosticSeverity,
688}
689
690pub enum MenuEditPredictionsPolicy {
691 Never,
692 ByProvider,
693}
694
695pub enum EditPredictionPreview {
696 /// Modifier is not pressed
697 Inactive { released_too_fast: bool },
698 /// Modifier pressed
699 Active {
700 since: Instant,
701 previous_scroll_position: Option<SharedScrollAnchor>,
702 },
703}
704
705#[derive(Copy, Clone, Eq, PartialEq)]
706enum EditPredictionKeybindSurface {
707 Inline,
708 CursorPopoverCompact,
709 CursorPopoverExpanded,
710}
711
712#[derive(Copy, Clone, Eq, PartialEq, Debug)]
713enum EditPredictionKeybindAction {
714 Accept,
715 Preview,
716}
717
718struct EditPredictionKeybindDisplay {
719 #[cfg(test)]
720 accept_keystroke: Option<gpui::KeybindingKeystroke>,
721 #[cfg(test)]
722 preview_keystroke: Option<gpui::KeybindingKeystroke>,
723 displayed_keystroke: Option<gpui::KeybindingKeystroke>,
724 action: EditPredictionKeybindAction,
725 missing_accept_keystroke: bool,
726 show_hold_label: bool,
727}
728
729impl EditPredictionPreview {
730 pub fn released_too_fast(&self) -> bool {
731 match self {
732 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
733 EditPredictionPreview::Active { .. } => false,
734 }
735 }
736
737 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<SharedScrollAnchor>) {
738 if let EditPredictionPreview::Active {
739 previous_scroll_position,
740 ..
741 } = self
742 {
743 *previous_scroll_position = scroll_position;
744 }
745 }
746}
747
748pub struct ContextMenuOptions {
749 pub min_entries_visible: usize,
750 pub max_entries_visible: usize,
751 pub placement: Option<ContextMenuPlacement>,
752}
753
754#[derive(Debug, Clone, PartialEq, Eq)]
755pub enum ContextMenuPlacement {
756 Above,
757 Below,
758}
759
760#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
761struct EditorActionId(usize);
762
763impl EditorActionId {
764 pub fn post_inc(&mut self) -> Self {
765 let answer = self.0;
766
767 *self = Self(answer + 1);
768
769 Self(answer)
770 }
771}
772
773// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
774// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
775
776type BackgroundHighlight = (
777 Arc<dyn Fn(&usize, &Theme) -> Hsla + Send + Sync>,
778 Arc<[Range<Anchor>]>,
779);
780type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
781
782#[derive(Default)]
783struct ScrollbarMarkerState {
784 scrollbar_size: Size<Pixels>,
785 dirty: bool,
786 markers: Arc<[PaintQuad]>,
787 pending_refresh: Option<Task<Result<()>>>,
788}
789
790impl ScrollbarMarkerState {
791 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
792 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
793 }
794}
795
796#[derive(Clone, Copy, PartialEq, Eq)]
797pub enum MinimapVisibility {
798 Disabled,
799 Enabled {
800 /// The configuration currently present in the users settings.
801 setting_configuration: bool,
802 /// Whether to override the currently set visibility from the users setting.
803 toggle_override: bool,
804 },
805}
806
807impl MinimapVisibility {
808 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
809 if mode.is_full() {
810 Self::Enabled {
811 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
812 toggle_override: false,
813 }
814 } else {
815 Self::Disabled
816 }
817 }
818
819 fn hidden(&self) -> Self {
820 match *self {
821 Self::Enabled {
822 setting_configuration,
823 ..
824 } => Self::Enabled {
825 setting_configuration,
826 toggle_override: setting_configuration,
827 },
828 Self::Disabled => Self::Disabled,
829 }
830 }
831
832 fn disabled(&self) -> bool {
833 matches!(*self, Self::Disabled)
834 }
835
836 fn settings_visibility(&self) -> bool {
837 match *self {
838 Self::Enabled {
839 setting_configuration,
840 ..
841 } => setting_configuration,
842 _ => false,
843 }
844 }
845
846 fn visible(&self) -> bool {
847 match *self {
848 Self::Enabled {
849 setting_configuration,
850 toggle_override,
851 } => setting_configuration ^ toggle_override,
852 _ => false,
853 }
854 }
855
856 fn toggle_visibility(&self) -> Self {
857 match *self {
858 Self::Enabled {
859 toggle_override,
860 setting_configuration,
861 } => Self::Enabled {
862 setting_configuration,
863 toggle_override: !toggle_override,
864 },
865 Self::Disabled => Self::Disabled,
866 }
867 }
868}
869
870#[derive(Debug, Clone, Copy, PartialEq, Eq)]
871pub enum BufferSerialization {
872 All,
873 NonDirtyBuffers,
874}
875
876impl BufferSerialization {
877 fn new(restore_unsaved_buffers: bool) -> Self {
878 if restore_unsaved_buffers {
879 Self::All
880 } else {
881 Self::NonDirtyBuffers
882 }
883 }
884}
885
886/// Addons allow storing per-editor state in other crates (e.g. Vim)
887pub trait Addon: 'static {
888 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
889
890 fn render_buffer_header_controls(
891 &self,
892 _: &ExcerptBoundaryInfo,
893 _: &language::BufferSnapshot,
894 _: &Window,
895 _: &App,
896 ) -> Option<AnyElement> {
897 None
898 }
899
900 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
901 None
902 }
903
904 fn to_any(&self) -> &dyn std::any::Any;
905
906 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
907 None
908 }
909}
910
911struct ChangeLocation {
912 current: Option<Vec<Anchor>>,
913 original: Vec<Anchor>,
914}
915impl ChangeLocation {
916 fn locations(&self) -> &[Anchor] {
917 self.current.as_ref().unwrap_or(&self.original)
918 }
919}
920
921/// A set of caret positions, registered when the editor was edited.
922pub struct ChangeList {
923 changes: Vec<ChangeLocation>,
924 /// Currently "selected" change.
925 position: Option<usize>,
926}
927
928impl ChangeList {
929 pub fn new() -> Self {
930 Self {
931 changes: Vec::new(),
932 position: None,
933 }
934 }
935
936 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
937 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
938 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
939 if self.changes.is_empty() {
940 return None;
941 }
942
943 let prev = self.position.unwrap_or(self.changes.len());
944 let next = if direction == Direction::Prev {
945 prev.saturating_sub(count)
946 } else {
947 (prev + count).min(self.changes.len() - 1)
948 };
949 self.position = Some(next);
950 self.changes.get(next).map(|change| change.locations())
951 }
952
953 /// Adds a new change to the list, resetting the change list position.
954 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
955 self.position.take();
956 if let Some(last) = self.changes.last_mut()
957 && group
958 {
959 last.current = Some(new_positions)
960 } else {
961 self.changes.push(ChangeLocation {
962 original: new_positions,
963 current: None,
964 });
965 }
966 }
967
968 pub fn last(&self) -> Option<&[Anchor]> {
969 self.changes.last().map(|change| change.locations())
970 }
971
972 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
973 self.changes.last().map(|change| change.original.as_slice())
974 }
975
976 pub fn invert_last_group(&mut self) {
977 if let Some(last) = self.changes.last_mut()
978 && let Some(current) = last.current.as_mut()
979 {
980 mem::swap(&mut last.original, current);
981 }
982 }
983}
984
985#[derive(Clone)]
986struct InlineBlamePopoverState {
987 scroll_handle: ScrollHandle,
988 commit_message: Option<ParsedCommitMessage>,
989 markdown: Entity<Markdown>,
990}
991
992struct InlineBlamePopover {
993 position: gpui::Point<Pixels>,
994 hide_task: Option<Task<()>>,
995 popover_bounds: Option<Bounds<Pixels>>,
996 popover_state: InlineBlamePopoverState,
997 keyboard_grace: bool,
998}
999
1000enum SelectionDragState {
1001 /// State when no drag related activity is detected.
1002 None,
1003 /// State when the mouse is down on a selection that is about to be dragged.
1004 ReadyToDrag {
1005 selection: Selection<Anchor>,
1006 click_position: gpui::Point<Pixels>,
1007 mouse_down_time: Instant,
1008 },
1009 /// State when the mouse is dragging the selection in the editor.
1010 Dragging {
1011 selection: Selection<Anchor>,
1012 drop_cursor: Selection<Anchor>,
1013 hide_drop_cursor: bool,
1014 },
1015}
1016
1017enum ColumnarSelectionState {
1018 FromMouse {
1019 selection_tail: Anchor,
1020 display_point: Option<DisplayPoint>,
1021 },
1022 FromSelection {
1023 selection_tail: Anchor,
1024 },
1025}
1026
1027/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1028/// a breakpoint on them.
1029#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1030struct PhantomBreakpointIndicator {
1031 display_row: DisplayRow,
1032 /// There's a small debounce between hovering over the line and showing the indicator.
1033 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1034 is_active: bool,
1035 collides_with_existing_breakpoint: bool,
1036}
1037
1038/// Represents a diff review button indicator that shows up when hovering over lines in the gutter
1039/// in diff view mode.
1040#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1041pub(crate) struct PhantomDiffReviewIndicator {
1042 /// The starting anchor of the selection (or the only row if not dragging).
1043 pub start: Anchor,
1044 /// The ending anchor of the selection. Equal to start_anchor for single-line selection.
1045 pub end: Anchor,
1046 /// There's a small debounce between hovering over the line and showing the indicator.
1047 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1048 pub is_active: bool,
1049}
1050
1051#[derive(Clone, Debug)]
1052pub(crate) struct DiffReviewDragState {
1053 pub start_anchor: Anchor,
1054 pub current_anchor: Anchor,
1055}
1056
1057impl DiffReviewDragState {
1058 pub fn row_range(&self, snapshot: &DisplaySnapshot) -> std::ops::RangeInclusive<DisplayRow> {
1059 let start = self.start_anchor.to_display_point(snapshot).row();
1060 let current = self.current_anchor.to_display_point(snapshot).row();
1061
1062 (start..=current).sorted()
1063 }
1064}
1065
1066/// Identifies a specific hunk in the diff buffer.
1067/// Used as a key to group comments by their location.
1068#[derive(Clone, Debug)]
1069pub struct DiffHunkKey {
1070 /// The file path (relative to worktree) this hunk belongs to.
1071 pub file_path: Arc<util::rel_path::RelPath>,
1072 /// An anchor at the start of the hunk. This tracks position as the buffer changes.
1073 pub hunk_start_anchor: Anchor,
1074}
1075
1076/// A review comment stored locally before being sent to the Agent panel.
1077#[derive(Clone)]
1078pub struct StoredReviewComment {
1079 /// Unique identifier for this comment (for edit/delete operations).
1080 pub id: usize,
1081 /// The comment text entered by the user.
1082 pub comment: String,
1083 /// Anchors for the code range being reviewed.
1084 pub range: Range<Anchor>,
1085 /// Timestamp when the comment was created (for chronological ordering).
1086 pub created_at: Instant,
1087 /// Whether this comment is currently being edited inline.
1088 pub is_editing: bool,
1089}
1090
1091impl StoredReviewComment {
1092 pub fn new(id: usize, comment: String, anchor_range: Range<Anchor>) -> Self {
1093 Self {
1094 id,
1095 comment,
1096 range: anchor_range,
1097 created_at: Instant::now(),
1098 is_editing: false,
1099 }
1100 }
1101}
1102
1103/// Represents an active diff review overlay that appears when clicking the "Add Review" button.
1104pub(crate) struct DiffReviewOverlay {
1105 pub anchor_range: Range<Anchor>,
1106 /// The block ID for the overlay.
1107 pub block_id: CustomBlockId,
1108 /// The editor entity for the review input.
1109 pub prompt_editor: Entity<Editor>,
1110 /// The hunk key this overlay belongs to.
1111 pub hunk_key: DiffHunkKey,
1112 /// Whether the comments section is expanded.
1113 pub comments_expanded: bool,
1114 /// Editors for comments currently being edited inline.
1115 /// Key: comment ID, Value: Editor entity for inline editing.
1116 pub inline_edit_editors: HashMap<usize, Entity<Editor>>,
1117 /// Subscriptions for inline edit editors' action handlers.
1118 /// Key: comment ID, Value: Subscription keeping the Newline action handler alive.
1119 pub inline_edit_subscriptions: HashMap<usize, Subscription>,
1120 /// The current user's avatar URI for display in comment rows.
1121 pub user_avatar_uri: Option<SharedUri>,
1122 /// Subscription to keep the action handler alive.
1123 _subscription: Subscription,
1124}
1125
1126/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1127///
1128/// See the [module level documentation](self) for more information.
1129pub struct Editor {
1130 focus_handle: FocusHandle,
1131 last_focused_descendant: Option<WeakFocusHandle>,
1132 /// The text buffer being edited
1133 buffer: Entity<MultiBuffer>,
1134 /// Map of how text in the buffer should be displayed.
1135 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1136 pub display_map: Entity<DisplayMap>,
1137 placeholder_display_map: Option<Entity<DisplayMap>>,
1138 pub selections: SelectionsCollection,
1139 pub scroll_manager: ScrollManager,
1140 /// When inline assist editors are linked, they all render cursors because
1141 /// typing enters text into each of them, even the ones that aren't focused.
1142 pub(crate) show_cursor_when_unfocused: bool,
1143 columnar_selection_state: Option<ColumnarSelectionState>,
1144 add_selections_state: Option<AddSelectionsState>,
1145 select_next_state: Option<SelectNextState>,
1146 select_prev_state: Option<SelectNextState>,
1147 selection_history: SelectionHistory,
1148 defer_selection_effects: bool,
1149 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1150 autoclose_regions: Vec<AutocloseRegion>,
1151 snippet_stack: InvalidationStack<SnippetState>,
1152 select_syntax_node_history: SelectSyntaxNodeHistory,
1153 ime_transaction: Option<TransactionId>,
1154 pub diagnostics_max_severity: DiagnosticSeverity,
1155 active_diagnostics: ActiveDiagnostic,
1156 show_inline_diagnostics: bool,
1157 inline_diagnostics_update: Task<()>,
1158 inline_diagnostics_enabled: bool,
1159 diagnostics_enabled: bool,
1160 word_completions_enabled: bool,
1161 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1162 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1163 hard_wrap: Option<usize>,
1164 project: Option<Entity<Project>>,
1165 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1166 completion_provider: Option<Rc<dyn CompletionProvider>>,
1167 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1168 blink_manager: Entity<BlinkManager>,
1169 show_cursor_names: bool,
1170 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1171 pub show_local_selections: bool,
1172 mode: EditorMode,
1173 show_breadcrumbs: bool,
1174 show_gutter: bool,
1175 show_scrollbars: ScrollbarAxes,
1176 minimap_visibility: MinimapVisibility,
1177 offset_content: bool,
1178 disable_expand_excerpt_buttons: bool,
1179 delegate_expand_excerpts: bool,
1180 delegate_stage_and_restore: bool,
1181 delegate_open_excerpts: bool,
1182 enable_lsp_data: bool,
1183 enable_runnables: bool,
1184 show_line_numbers: Option<bool>,
1185 use_relative_line_numbers: Option<bool>,
1186 show_git_diff_gutter: Option<bool>,
1187 show_code_actions: Option<bool>,
1188 show_runnables: Option<bool>,
1189 show_breakpoints: Option<bool>,
1190 show_diff_review_button: bool,
1191 show_wrap_guides: Option<bool>,
1192 show_indent_guides: Option<bool>,
1193 buffers_with_disabled_indent_guides: HashSet<BufferId>,
1194 highlight_order: usize,
1195 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1196 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1197 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1198 scrollbar_marker_state: ScrollbarMarkerState,
1199 active_indent_guides_state: ActiveIndentGuidesState,
1200 nav_history: Option<ItemNavHistory>,
1201 context_menu: RefCell<Option<CodeContextMenu>>,
1202 context_menu_options: Option<ContextMenuOptions>,
1203 mouse_context_menu: Option<MouseContextMenu>,
1204 completion_tasks: Vec<(CompletionId, Task<()>)>,
1205 inline_blame_popover: Option<InlineBlamePopover>,
1206 inline_blame_popover_show_task: Option<Task<()>>,
1207 signature_help_state: SignatureHelpState,
1208 auto_signature_help: Option<bool>,
1209 find_all_references_task_sources: Vec<Anchor>,
1210 next_completion_id: CompletionId,
1211 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1212 code_actions_task: Option<Task<Result<()>>>,
1213 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1214 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1215 debounced_selection_highlight_complete: bool,
1216 document_highlights_task: Option<Task<()>>,
1217 linked_editing_range_task: Option<Task<Option<()>>>,
1218 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1219 pending_rename: Option<RenameState>,
1220 searchable: bool,
1221 cursor_shape: CursorShape,
1222 /// Whether the cursor is offset one character to the left when something is
1223 /// selected (needed for vim visual mode)
1224 cursor_offset_on_selection: bool,
1225 current_line_highlight: Option<CurrentLineHighlight>,
1226 /// Whether to collapse search match ranges to just their start position.
1227 /// When true, navigating to a match positions the cursor at the match
1228 /// without selecting the matched text.
1229 collapse_matches: bool,
1230 autoindent_mode: Option<AutoindentMode>,
1231 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1232 input_enabled: bool,
1233 expects_character_input: bool,
1234 use_modal_editing: bool,
1235 read_only: bool,
1236 leader_id: Option<CollaboratorId>,
1237 remote_id: Option<ViewId>,
1238 pub hover_state: HoverState,
1239 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1240 prev_pressure_stage: Option<PressureStage>,
1241 gutter_hovered: bool,
1242 hovered_link_state: Option<HoveredLinkState>,
1243 edit_prediction_provider: Option<RegisteredEditPredictionDelegate>,
1244 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1245 active_edit_prediction: Option<EditPredictionState>,
1246 /// Used to prevent flickering as the user types while the menu is open
1247 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1248 edit_prediction_settings: EditPredictionSettings,
1249 edit_predictions_hidden_for_vim_mode: bool,
1250 show_edit_predictions_override: Option<bool>,
1251 show_completions_on_input_override: Option<bool>,
1252 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1253 edit_prediction_preview: EditPredictionPreview,
1254 in_leading_whitespace: bool,
1255 next_inlay_id: usize,
1256 next_color_inlay_id: usize,
1257 _subscriptions: Vec<Subscription>,
1258 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1259 gutter_dimensions: GutterDimensions,
1260 style: Option<EditorStyle>,
1261 text_style_refinement: Option<TextStyleRefinement>,
1262 next_editor_action_id: EditorActionId,
1263 editor_actions: Rc<
1264 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1265 >,
1266 use_autoclose: bool,
1267 use_auto_surround: bool,
1268 use_selection_highlight: bool,
1269 auto_replace_emoji_shortcode: bool,
1270 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1271 show_git_blame_gutter: bool,
1272 show_git_blame_inline: bool,
1273 show_git_blame_inline_delay_task: Option<Task<()>>,
1274 git_blame_inline_enabled: bool,
1275 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1276 buffer_serialization: Option<BufferSerialization>,
1277 show_selection_menu: Option<bool>,
1278 blame: Option<Entity<GitBlame>>,
1279 blame_subscription: Option<Subscription>,
1280 custom_context_menu: Option<
1281 Box<
1282 dyn 'static
1283 + Fn(
1284 &mut Self,
1285 DisplayPoint,
1286 &mut Window,
1287 &mut Context<Self>,
1288 ) -> Option<Entity<ui::ContextMenu>>,
1289 >,
1290 >,
1291 last_bounds: Option<Bounds<Pixels>>,
1292 last_position_map: Option<Rc<PositionMap>>,
1293 expect_bounds_change: Option<Bounds<Pixels>>,
1294 runnables: RunnableData,
1295 breakpoint_store: Option<Entity<BreakpointStore>>,
1296 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1297 pub(crate) gutter_diff_review_indicator: (Option<PhantomDiffReviewIndicator>, Option<Task<()>>),
1298 pub(crate) diff_review_drag_state: Option<DiffReviewDragState>,
1299 /// Active diff review overlays. Multiple overlays can be open simultaneously
1300 /// when hunks have comments stored.
1301 pub(crate) diff_review_overlays: Vec<DiffReviewOverlay>,
1302 /// Stored review comments grouped by hunk.
1303 /// Uses a Vec instead of HashMap because DiffHunkKey contains an Anchor
1304 /// which doesn't implement Hash/Eq in a way suitable for HashMap keys.
1305 stored_review_comments: Vec<(DiffHunkKey, Vec<StoredReviewComment>)>,
1306 /// Counter for generating unique comment IDs.
1307 next_review_comment_id: usize,
1308 hovered_diff_hunk_row: Option<DisplayRow>,
1309 pull_diagnostics_task: Task<()>,
1310 in_project_search: bool,
1311 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1312 breadcrumb_header: Option<String>,
1313 focused_block: Option<FocusedBlock>,
1314 next_scroll_position: NextScrollCursorCenterTopBottom,
1315 addons: HashMap<TypeId, Box<dyn Addon>>,
1316 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1317 load_diff_task: Option<Shared<Task<()>>>,
1318 /// Whether we are temporarily displaying a diff other than git's
1319 temporary_diff_override: bool,
1320 selection_mark_mode: bool,
1321 toggle_fold_multiple_buffers: Task<()>,
1322 _scroll_cursor_center_top_bottom_task: Task<()>,
1323 serialize_selections: Task<()>,
1324 serialize_folds: Task<()>,
1325 mouse_cursor_hidden: bool,
1326 minimap: Option<Entity<Self>>,
1327 hide_mouse_mode: HideMouseMode,
1328 pub change_list: ChangeList,
1329 inline_value_cache: InlineValueCache,
1330 number_deleted_lines: bool,
1331
1332 selection_drag_state: SelectionDragState,
1333 colors: Option<LspColorData>,
1334 post_scroll_update: Task<()>,
1335 refresh_colors_task: Task<()>,
1336 use_document_folding_ranges: bool,
1337 refresh_folding_ranges_task: Task<()>,
1338 inlay_hints: Option<LspInlayHintData>,
1339 folding_newlines: Task<()>,
1340 select_next_is_case_sensitive: Option<bool>,
1341 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1342 on_local_selections_changed:
1343 Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
1344 suppress_selection_callback: bool,
1345 applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
1346 accent_data: Option<AccentData>,
1347 bracket_fetched_tree_sitter_chunks: HashMap<Range<text::Anchor>, HashSet<Range<BufferRow>>>,
1348 semantic_token_state: SemanticTokenState,
1349 pub(crate) refresh_matching_bracket_highlights_task: Task<()>,
1350 refresh_document_symbols_task: Shared<Task<()>>,
1351 lsp_document_symbols: HashMap<BufferId, Vec<OutlineItem<text::Anchor>>>,
1352 refresh_outline_symbols_at_cursor_at_cursor_task: Task<()>,
1353 outline_symbols_at_cursor: Option<(BufferId, Vec<OutlineItem<Anchor>>)>,
1354 sticky_headers_task: Task<()>,
1355 sticky_headers: Option<Vec<OutlineItem<Anchor>>>,
1356 pub(crate) colorize_brackets_task: Task<()>,
1357}
1358
1359#[derive(Debug, PartialEq)]
1360struct AccentData {
1361 colors: AccentColors,
1362 overrides: Vec<SharedString>,
1363}
1364
1365fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1366 if debounce_ms > 0 {
1367 Some(Duration::from_millis(debounce_ms))
1368 } else {
1369 None
1370 }
1371}
1372
1373#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1374enum NextScrollCursorCenterTopBottom {
1375 #[default]
1376 Center,
1377 Top,
1378 Bottom,
1379}
1380
1381impl NextScrollCursorCenterTopBottom {
1382 fn next(&self) -> Self {
1383 match self {
1384 Self::Center => Self::Top,
1385 Self::Top => Self::Bottom,
1386 Self::Bottom => Self::Center,
1387 }
1388 }
1389}
1390
1391#[derive(Clone)]
1392pub struct EditorSnapshot {
1393 pub mode: EditorMode,
1394 show_gutter: bool,
1395 offset_content: bool,
1396 show_line_numbers: Option<bool>,
1397 number_deleted_lines: bool,
1398 show_git_diff_gutter: Option<bool>,
1399 show_code_actions: Option<bool>,
1400 show_runnables: Option<bool>,
1401 show_breakpoints: Option<bool>,
1402 git_blame_gutter_max_author_length: Option<usize>,
1403 pub display_snapshot: DisplaySnapshot,
1404 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1405 is_focused: bool,
1406 scroll_anchor: SharedScrollAnchor,
1407 ongoing_scroll: OngoingScroll,
1408 current_line_highlight: CurrentLineHighlight,
1409 gutter_hovered: bool,
1410 semantic_tokens_enabled: bool,
1411}
1412
1413#[derive(Default, Debug, Clone, Copy)]
1414pub struct GutterDimensions {
1415 pub left_padding: Pixels,
1416 pub right_padding: Pixels,
1417 pub width: Pixels,
1418 pub margin: Pixels,
1419 pub git_blame_entries_width: Option<Pixels>,
1420}
1421
1422impl GutterDimensions {
1423 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1424 Self {
1425 margin: Self::default_gutter_margin(font_id, font_size, cx),
1426 ..Default::default()
1427 }
1428 }
1429
1430 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1431 -cx.text_system().descent(font_id, font_size)
1432 }
1433 /// The full width of the space taken up by the gutter.
1434 pub fn full_width(&self) -> Pixels {
1435 self.margin + self.width
1436 }
1437
1438 /// The width of the space reserved for the fold indicators,
1439 /// use alongside 'justify_end' and `gutter_width` to
1440 /// right align content with the line numbers
1441 pub fn fold_area_width(&self) -> Pixels {
1442 self.margin + self.right_padding
1443 }
1444}
1445
1446struct CharacterDimensions {
1447 em_width: Pixels,
1448 em_advance: Pixels,
1449 line_height: Pixels,
1450}
1451
1452#[derive(Debug)]
1453pub struct RemoteSelection {
1454 pub replica_id: ReplicaId,
1455 pub selection: Selection<Anchor>,
1456 pub cursor_shape: CursorShape,
1457 pub collaborator_id: CollaboratorId,
1458 pub line_mode: bool,
1459 pub user_name: Option<SharedString>,
1460 pub color: PlayerColor,
1461}
1462
1463#[derive(Clone, Debug)]
1464struct SelectionHistoryEntry {
1465 selections: Arc<[Selection<Anchor>]>,
1466 select_next_state: Option<SelectNextState>,
1467 select_prev_state: Option<SelectNextState>,
1468 add_selections_state: Option<AddSelectionsState>,
1469}
1470
1471#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1472enum SelectionHistoryMode {
1473 #[default]
1474 Normal,
1475 Undoing,
1476 Redoing,
1477 Skipping,
1478}
1479
1480#[derive(Clone, PartialEq, Eq, Hash)]
1481struct HoveredCursor {
1482 replica_id: ReplicaId,
1483 selection_id: usize,
1484}
1485
1486#[derive(Debug)]
1487/// SelectionEffects controls the side-effects of updating the selection.
1488///
1489/// The default behaviour does "what you mostly want":
1490/// - it pushes to the nav history if the cursor moved by >10 lines
1491/// - it re-triggers completion requests
1492/// - it scrolls to fit
1493///
1494/// You might want to modify these behaviours. For example when doing a "jump"
1495/// like go to definition, we always want to add to nav history; but when scrolling
1496/// in vim mode we never do.
1497///
1498/// Similarly, you might want to disable scrolling if you don't want the viewport to
1499/// move.
1500#[derive(Clone)]
1501pub struct SelectionEffects {
1502 nav_history: Option<bool>,
1503 completions: bool,
1504 scroll: Option<Autoscroll>,
1505}
1506
1507impl Default for SelectionEffects {
1508 fn default() -> Self {
1509 Self {
1510 nav_history: None,
1511 completions: true,
1512 scroll: Some(Autoscroll::fit()),
1513 }
1514 }
1515}
1516impl SelectionEffects {
1517 pub fn scroll(scroll: Autoscroll) -> Self {
1518 Self {
1519 scroll: Some(scroll),
1520 ..Default::default()
1521 }
1522 }
1523
1524 pub fn no_scroll() -> Self {
1525 Self {
1526 scroll: None,
1527 ..Default::default()
1528 }
1529 }
1530
1531 pub fn completions(self, completions: bool) -> Self {
1532 Self {
1533 completions,
1534 ..self
1535 }
1536 }
1537
1538 pub fn nav_history(self, nav_history: bool) -> Self {
1539 Self {
1540 nav_history: Some(nav_history),
1541 ..self
1542 }
1543 }
1544}
1545
1546struct DeferredSelectionEffectsState {
1547 changed: bool,
1548 effects: SelectionEffects,
1549 old_cursor_position: Anchor,
1550 history_entry: SelectionHistoryEntry,
1551}
1552
1553#[derive(Default)]
1554struct SelectionHistory {
1555 #[allow(clippy::type_complexity)]
1556 selections_by_transaction:
1557 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1558 mode: SelectionHistoryMode,
1559 undo_stack: VecDeque<SelectionHistoryEntry>,
1560 redo_stack: VecDeque<SelectionHistoryEntry>,
1561}
1562
1563impl SelectionHistory {
1564 #[track_caller]
1565 fn insert_transaction(
1566 &mut self,
1567 transaction_id: TransactionId,
1568 selections: Arc<[Selection<Anchor>]>,
1569 ) {
1570 if selections.is_empty() {
1571 log::error!(
1572 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1573 std::panic::Location::caller()
1574 );
1575 return;
1576 }
1577 self.selections_by_transaction
1578 .insert(transaction_id, (selections, None));
1579 }
1580
1581 #[allow(clippy::type_complexity)]
1582 fn transaction(
1583 &self,
1584 transaction_id: TransactionId,
1585 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1586 self.selections_by_transaction.get(&transaction_id)
1587 }
1588
1589 #[allow(clippy::type_complexity)]
1590 fn transaction_mut(
1591 &mut self,
1592 transaction_id: TransactionId,
1593 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1594 self.selections_by_transaction.get_mut(&transaction_id)
1595 }
1596
1597 fn push(&mut self, entry: SelectionHistoryEntry) {
1598 if !entry.selections.is_empty() {
1599 match self.mode {
1600 SelectionHistoryMode::Normal => {
1601 self.push_undo(entry);
1602 self.redo_stack.clear();
1603 }
1604 SelectionHistoryMode::Undoing => self.push_redo(entry),
1605 SelectionHistoryMode::Redoing => self.push_undo(entry),
1606 SelectionHistoryMode::Skipping => {}
1607 }
1608 }
1609 }
1610
1611 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1612 if self
1613 .undo_stack
1614 .back()
1615 .is_none_or(|e| e.selections != entry.selections)
1616 {
1617 self.undo_stack.push_back(entry);
1618 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1619 self.undo_stack.pop_front();
1620 }
1621 }
1622 }
1623
1624 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1625 if self
1626 .redo_stack
1627 .back()
1628 .is_none_or(|e| e.selections != entry.selections)
1629 {
1630 self.redo_stack.push_back(entry);
1631 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1632 self.redo_stack.pop_front();
1633 }
1634 }
1635 }
1636}
1637
1638#[derive(Clone, Copy)]
1639pub struct RowHighlightOptions {
1640 pub autoscroll: bool,
1641 pub include_gutter: bool,
1642}
1643
1644impl Default for RowHighlightOptions {
1645 fn default() -> Self {
1646 Self {
1647 autoscroll: Default::default(),
1648 include_gutter: true,
1649 }
1650 }
1651}
1652
1653struct RowHighlight {
1654 index: usize,
1655 range: Range<Anchor>,
1656 color: Hsla,
1657 options: RowHighlightOptions,
1658 type_id: TypeId,
1659}
1660
1661#[derive(Clone, Debug)]
1662struct AddSelectionsState {
1663 groups: Vec<AddSelectionsGroup>,
1664}
1665
1666#[derive(Clone, Debug)]
1667struct AddSelectionsGroup {
1668 above: bool,
1669 stack: Vec<usize>,
1670}
1671
1672#[derive(Clone)]
1673struct SelectNextState {
1674 query: AhoCorasick,
1675 wordwise: bool,
1676 done: bool,
1677}
1678
1679impl std::fmt::Debug for SelectNextState {
1680 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1681 f.debug_struct(std::any::type_name::<Self>())
1682 .field("wordwise", &self.wordwise)
1683 .field("done", &self.done)
1684 .finish()
1685 }
1686}
1687
1688#[derive(Debug)]
1689struct AutocloseRegion {
1690 selection_id: usize,
1691 range: Range<Anchor>,
1692 pair: BracketPair,
1693}
1694
1695#[derive(Debug)]
1696struct SnippetState {
1697 ranges: Vec<Vec<Range<Anchor>>>,
1698 active_index: usize,
1699 choices: Vec<Option<Vec<String>>>,
1700}
1701
1702#[doc(hidden)]
1703pub struct RenameState {
1704 pub range: Range<Anchor>,
1705 pub old_name: Arc<str>,
1706 pub editor: Entity<Editor>,
1707 block_id: CustomBlockId,
1708}
1709
1710struct InvalidationStack<T>(Vec<T>);
1711
1712struct RegisteredEditPredictionDelegate {
1713 provider: Arc<dyn EditPredictionDelegateHandle>,
1714 _subscription: Subscription,
1715}
1716
1717#[derive(Debug, PartialEq, Eq)]
1718pub struct ActiveDiagnosticGroup {
1719 pub active_range: Range<Anchor>,
1720 pub active_message: String,
1721 pub group_id: usize,
1722 pub blocks: HashSet<CustomBlockId>,
1723}
1724
1725#[derive(Debug, PartialEq, Eq)]
1726
1727pub(crate) enum ActiveDiagnostic {
1728 None,
1729 All,
1730 Group(ActiveDiagnosticGroup),
1731}
1732
1733#[derive(Serialize, Deserialize, Clone, Debug)]
1734pub struct ClipboardSelection {
1735 /// The number of bytes in this selection.
1736 pub len: usize,
1737 /// Whether this was a full-line selection.
1738 pub is_entire_line: bool,
1739 /// The indentation of the first line when this content was originally copied.
1740 pub first_line_indent: u32,
1741 #[serde(default)]
1742 pub file_path: Option<PathBuf>,
1743 #[serde(default)]
1744 pub line_range: Option<RangeInclusive<u32>>,
1745}
1746
1747impl ClipboardSelection {
1748 pub fn for_buffer(
1749 len: usize,
1750 is_entire_line: bool,
1751 range: Range<Point>,
1752 buffer: &MultiBufferSnapshot,
1753 project: Option<&Entity<Project>>,
1754 cx: &App,
1755 ) -> Self {
1756 let first_line_indent = buffer
1757 .indent_size_for_line(MultiBufferRow(range.start.row))
1758 .len;
1759
1760 let file_path = util::maybe!({
1761 let project = project?.read(cx);
1762 let file = buffer.file_at(range.start)?;
1763 let project_path = ProjectPath {
1764 worktree_id: file.worktree_id(cx),
1765 path: file.path().clone(),
1766 };
1767 project.absolute_path(&project_path, cx)
1768 });
1769
1770 let line_range = if file_path.is_some() {
1771 buffer
1772 .range_to_buffer_range(range)
1773 .map(|(_, buffer_range)| buffer_range.start.row..=buffer_range.end.row)
1774 } else {
1775 None
1776 };
1777
1778 Self {
1779 len,
1780 is_entire_line,
1781 first_line_indent,
1782 file_path,
1783 line_range,
1784 }
1785 }
1786}
1787
1788// selections, scroll behavior, was newest selection reversed
1789type SelectSyntaxNodeHistoryState = (
1790 Box<[Selection<Anchor>]>,
1791 SelectSyntaxNodeScrollBehavior,
1792 bool,
1793);
1794
1795#[derive(Default)]
1796struct SelectSyntaxNodeHistory {
1797 stack: Vec<SelectSyntaxNodeHistoryState>,
1798 // disable temporarily to allow changing selections without losing the stack
1799 pub disable_clearing: bool,
1800}
1801
1802impl SelectSyntaxNodeHistory {
1803 pub fn try_clear(&mut self) {
1804 if !self.disable_clearing {
1805 self.stack.clear();
1806 }
1807 }
1808
1809 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1810 self.stack.push(selection);
1811 }
1812
1813 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1814 self.stack.pop()
1815 }
1816}
1817
1818enum SelectSyntaxNodeScrollBehavior {
1819 CursorTop,
1820 FitSelection,
1821 CursorBottom,
1822}
1823
1824#[derive(Debug, Clone, Copy)]
1825pub(crate) struct NavigationData {
1826 cursor_anchor: Anchor,
1827 cursor_position: Point,
1828 scroll_anchor: ScrollAnchor,
1829 scroll_top_row: u32,
1830}
1831
1832#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1833pub enum GotoDefinitionKind {
1834 Symbol,
1835 Declaration,
1836 Type,
1837 Implementation,
1838}
1839
1840pub enum FormatTarget {
1841 Buffers(HashSet<Entity<Buffer>>),
1842 Ranges(Vec<Range<MultiBufferPoint>>),
1843}
1844
1845pub(crate) struct FocusedBlock {
1846 id: BlockId,
1847 focus_handle: WeakFocusHandle,
1848}
1849
1850#[derive(Clone, Debug)]
1851pub enum JumpData {
1852 MultiBufferRow {
1853 row: MultiBufferRow,
1854 line_offset_from_top: u32,
1855 },
1856 MultiBufferPoint {
1857 anchor: language::Anchor,
1858 position: Point,
1859 line_offset_from_top: u32,
1860 },
1861}
1862
1863pub enum MultibufferSelectionMode {
1864 First,
1865 All,
1866}
1867
1868#[derive(Clone, Copy, Debug, Default)]
1869pub struct RewrapOptions {
1870 pub override_language_settings: bool,
1871 pub preserve_existing_whitespace: bool,
1872 pub line_length: Option<usize>,
1873}
1874
1875impl Editor {
1876 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1877 let buffer = cx.new(|cx| Buffer::local("", cx));
1878 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1879 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1880 }
1881
1882 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1883 let buffer = cx.new(|cx| Buffer::local("", cx));
1884 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1885 Self::new(EditorMode::full(), buffer, None, window, cx)
1886 }
1887
1888 pub fn auto_height(
1889 min_lines: usize,
1890 max_lines: usize,
1891 window: &mut Window,
1892 cx: &mut Context<Self>,
1893 ) -> Self {
1894 let buffer = cx.new(|cx| Buffer::local("", cx));
1895 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1896 Self::new(
1897 EditorMode::AutoHeight {
1898 min_lines,
1899 max_lines: Some(max_lines),
1900 },
1901 buffer,
1902 None,
1903 window,
1904 cx,
1905 )
1906 }
1907
1908 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1909 /// The editor grows as tall as needed to fit its content.
1910 pub fn auto_height_unbounded(
1911 min_lines: usize,
1912 window: &mut Window,
1913 cx: &mut Context<Self>,
1914 ) -> Self {
1915 let buffer = cx.new(|cx| Buffer::local("", cx));
1916 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1917 Self::new(
1918 EditorMode::AutoHeight {
1919 min_lines,
1920 max_lines: None,
1921 },
1922 buffer,
1923 None,
1924 window,
1925 cx,
1926 )
1927 }
1928
1929 pub fn for_buffer(
1930 buffer: Entity<Buffer>,
1931 project: Option<Entity<Project>>,
1932 window: &mut Window,
1933 cx: &mut Context<Self>,
1934 ) -> Self {
1935 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1936 Self::new(EditorMode::full(), buffer, project, window, cx)
1937 }
1938
1939 pub fn for_multibuffer(
1940 buffer: Entity<MultiBuffer>,
1941 project: Option<Entity<Project>>,
1942 window: &mut Window,
1943 cx: &mut Context<Self>,
1944 ) -> Self {
1945 Self::new(EditorMode::full(), buffer, project, window, cx)
1946 }
1947
1948 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1949 let mut clone = Self::new(
1950 self.mode.clone(),
1951 self.buffer.clone(),
1952 self.project.clone(),
1953 window,
1954 cx,
1955 );
1956 let my_snapshot = self.display_map.update(cx, |display_map, cx| {
1957 let snapshot = display_map.snapshot(cx);
1958 clone.display_map.update(cx, |display_map, cx| {
1959 display_map.set_state(&snapshot, cx);
1960 });
1961 snapshot
1962 });
1963 let clone_snapshot = clone.display_map.update(cx, |map, cx| map.snapshot(cx));
1964 clone.folds_did_change(cx);
1965 clone.selections.clone_state(&self.selections);
1966 clone
1967 .scroll_manager
1968 .clone_state(&self.scroll_manager, &my_snapshot, &clone_snapshot, cx);
1969 clone.searchable = self.searchable;
1970 clone.read_only = self.read_only;
1971 clone.buffers_with_disabled_indent_guides =
1972 self.buffers_with_disabled_indent_guides.clone();
1973 clone
1974 }
1975
1976 pub fn new(
1977 mode: EditorMode,
1978 buffer: Entity<MultiBuffer>,
1979 project: Option<Entity<Project>>,
1980 window: &mut Window,
1981 cx: &mut Context<Self>,
1982 ) -> Self {
1983 Editor::new_internal(mode, buffer, project, None, window, cx)
1984 }
1985
1986 pub fn refresh_sticky_headers(
1987 &mut self,
1988 display_snapshot: &DisplaySnapshot,
1989 cx: &mut Context<Editor>,
1990 ) {
1991 if !self.mode.is_full() {
1992 return;
1993 }
1994 let multi_buffer = display_snapshot.buffer_snapshot().clone();
1995 let scroll_anchor = self
1996 .scroll_manager
1997 .native_anchor(display_snapshot, cx)
1998 .anchor;
1999 let Some(buffer_snapshot) = multi_buffer.as_singleton() else {
2000 return;
2001 };
2002
2003 let buffer = buffer_snapshot.clone();
2004 let Some((buffer_visible_start, _)) = multi_buffer.anchor_to_buffer_anchor(scroll_anchor)
2005 else {
2006 return;
2007 };
2008 let buffer_visible_start = buffer_visible_start.to_point(&buffer);
2009 let max_row = buffer.max_point().row;
2010 let start_row = buffer_visible_start.row.min(max_row);
2011 let end_row = (buffer_visible_start.row + 10).min(max_row);
2012
2013 let syntax = self.style(cx).syntax.clone();
2014 let background_task = cx.background_spawn(async move {
2015 buffer
2016 .outline_items_containing(
2017 Point::new(start_row, 0)..Point::new(end_row, 0),
2018 true,
2019 Some(syntax.as_ref()),
2020 )
2021 .into_iter()
2022 .filter_map(|outline_item| {
2023 Some(OutlineItem {
2024 depth: outline_item.depth,
2025 range: multi_buffer
2026 .buffer_anchor_range_to_anchor_range(outline_item.range)?,
2027 source_range_for_text: multi_buffer.buffer_anchor_range_to_anchor_range(
2028 outline_item.source_range_for_text,
2029 )?,
2030 text: outline_item.text,
2031 highlight_ranges: outline_item.highlight_ranges,
2032 name_ranges: outline_item.name_ranges,
2033 body_range: outline_item.body_range.and_then(|range| {
2034 multi_buffer.buffer_anchor_range_to_anchor_range(range)
2035 }),
2036 annotation_range: outline_item.annotation_range.and_then(|range| {
2037 multi_buffer.buffer_anchor_range_to_anchor_range(range)
2038 }),
2039 })
2040 })
2041 .collect()
2042 });
2043 self.sticky_headers_task = cx.spawn(async move |this, cx| {
2044 let sticky_headers = background_task.await;
2045 this.update(cx, |this, cx| {
2046 this.sticky_headers = Some(sticky_headers);
2047 cx.notify();
2048 })
2049 .ok();
2050 });
2051 }
2052
2053 fn new_internal(
2054 mode: EditorMode,
2055 multi_buffer: Entity<MultiBuffer>,
2056 project: Option<Entity<Project>>,
2057 display_map: Option<Entity<DisplayMap>>,
2058 window: &mut Window,
2059 cx: &mut Context<Self>,
2060 ) -> Self {
2061 debug_assert!(
2062 display_map.is_none() || mode.is_minimap(),
2063 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
2064 );
2065
2066 let full_mode = mode.is_full();
2067 let is_minimap = mode.is_minimap();
2068 let diagnostics_max_severity = if full_mode {
2069 EditorSettings::get_global(cx)
2070 .diagnostics_max_severity
2071 .unwrap_or(DiagnosticSeverity::Hint)
2072 } else {
2073 DiagnosticSeverity::Off
2074 };
2075 let style = window.text_style();
2076 let font_size = style.font_size.to_pixels(window.rem_size());
2077 let editor = cx.entity().downgrade();
2078 let fold_placeholder = FoldPlaceholder {
2079 constrain_width: false,
2080 render: Arc::new(move |fold_id, fold_range, cx| {
2081 let editor = editor.clone();
2082 FoldPlaceholder::fold_element(fold_id, cx)
2083 .cursor_pointer()
2084 .child("⋯")
2085 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
2086 .on_click(move |_, _window, cx| {
2087 editor
2088 .update(cx, |editor, cx| {
2089 editor.unfold_ranges(
2090 &[fold_range.start..fold_range.end],
2091 true,
2092 false,
2093 cx,
2094 );
2095 cx.stop_propagation();
2096 })
2097 .ok();
2098 })
2099 .into_any()
2100 }),
2101 merge_adjacent: true,
2102 ..FoldPlaceholder::default()
2103 };
2104 let display_map = display_map.unwrap_or_else(|| {
2105 cx.new(|cx| {
2106 DisplayMap::new(
2107 multi_buffer.clone(),
2108 style.font(),
2109 font_size,
2110 None,
2111 FILE_HEADER_HEIGHT,
2112 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2113 fold_placeholder,
2114 diagnostics_max_severity,
2115 cx,
2116 )
2117 })
2118 });
2119
2120 let selections = SelectionsCollection::new();
2121
2122 let blink_manager = cx.new(|cx| {
2123 let mut blink_manager = BlinkManager::new(
2124 CURSOR_BLINK_INTERVAL,
2125 |cx| EditorSettings::get_global(cx).cursor_blink,
2126 cx,
2127 );
2128 if is_minimap {
2129 blink_manager.disable(cx);
2130 }
2131 blink_manager
2132 });
2133
2134 let soft_wrap_mode_override =
2135 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
2136
2137 let mut project_subscriptions = Vec::new();
2138 if full_mode && let Some(project) = project.as_ref() {
2139 project_subscriptions.push(cx.subscribe_in(
2140 project,
2141 window,
2142 |editor, _, event, window, cx| match event {
2143 project::Event::RefreshCodeLens => {
2144 // we always query lens with actions, without storing them, always refreshing them
2145 }
2146 project::Event::RefreshInlayHints {
2147 server_id,
2148 request_id,
2149 } => {
2150 editor.refresh_inlay_hints(
2151 InlayHintRefreshReason::RefreshRequested {
2152 server_id: *server_id,
2153 request_id: *request_id,
2154 },
2155 cx,
2156 );
2157 }
2158 project::Event::RefreshSemanticTokens {
2159 server_id,
2160 request_id,
2161 } => {
2162 editor.refresh_semantic_tokens(
2163 None,
2164 Some(RefreshForServer {
2165 server_id: *server_id,
2166 request_id: *request_id,
2167 }),
2168 cx,
2169 );
2170 }
2171 project::Event::LanguageServerRemoved(_) => {
2172 editor.registered_buffers.clear();
2173 editor.register_visible_buffers(cx);
2174 editor.invalidate_semantic_tokens(None);
2175 editor.refresh_runnables(None, window, cx);
2176 editor.update_lsp_data(None, window, cx);
2177 editor.refresh_inlay_hints(InlayHintRefreshReason::ServerRemoved, cx);
2178 }
2179 project::Event::SnippetEdit(id, snippet_edits) => {
2180 // todo(lw): Non singletons
2181 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
2182 let snapshot = buffer.read(cx).snapshot();
2183 let focus_handle = editor.focus_handle(cx);
2184 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
2185 for (range, snippet) in snippet_edits {
2186 let buffer_range =
2187 language::range_from_lsp(*range).to_offset(&snapshot);
2188 editor
2189 .insert_snippet(
2190 &[MultiBufferOffset(buffer_range.start)
2191 ..MultiBufferOffset(buffer_range.end)],
2192 snippet.clone(),
2193 window,
2194 cx,
2195 )
2196 .ok();
2197 }
2198 }
2199 }
2200 }
2201 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
2202 let buffer_id = *buffer_id;
2203 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
2204 editor.register_buffer(buffer_id, cx);
2205 editor.refresh_runnables(Some(buffer_id), window, cx);
2206 editor.update_lsp_data(Some(buffer_id), window, cx);
2207 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
2208 refresh_linked_ranges(editor, window, cx);
2209 editor.refresh_code_actions(window, cx);
2210 editor.refresh_document_highlights(cx);
2211 }
2212 }
2213
2214 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
2215 let Some(workspace) = editor.workspace() else {
2216 return;
2217 };
2218 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2219 else {
2220 return;
2221 };
2222
2223 if active_editor.entity_id() == cx.entity_id() {
2224 let entity_id = cx.entity_id();
2225 workspace.update(cx, |this, cx| {
2226 this.panes_mut()
2227 .iter_mut()
2228 .filter(|pane| pane.entity_id() != entity_id)
2229 .for_each(|p| {
2230 p.update(cx, |pane, _| {
2231 pane.nav_history_mut().rename_item(
2232 entity_id,
2233 project_path.clone(),
2234 abs_path.clone().into(),
2235 );
2236 })
2237 });
2238 });
2239
2240 Self::open_transaction_for_hidden_buffers(
2241 workspace,
2242 transaction.clone(),
2243 "Rename".to_string(),
2244 window,
2245 cx,
2246 );
2247 }
2248 }
2249
2250 project::Event::WorkspaceEditApplied(transaction) => {
2251 let Some(workspace) = editor.workspace() else {
2252 return;
2253 };
2254 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2255 else {
2256 return;
2257 };
2258
2259 if active_editor.entity_id() == cx.entity_id() {
2260 Self::open_transaction_for_hidden_buffers(
2261 workspace,
2262 transaction.clone(),
2263 "LSP Edit".to_string(),
2264 window,
2265 cx,
2266 );
2267 }
2268 }
2269
2270 _ => {}
2271 },
2272 ));
2273 if let Some(task_inventory) = project
2274 .read(cx)
2275 .task_store()
2276 .read(cx)
2277 .task_inventory()
2278 .cloned()
2279 {
2280 project_subscriptions.push(cx.observe_in(
2281 &task_inventory,
2282 window,
2283 |editor, _, window, cx| {
2284 editor.refresh_runnables(None, window, cx);
2285 },
2286 ));
2287 };
2288
2289 project_subscriptions.push(cx.subscribe_in(
2290 &project.read(cx).breakpoint_store(),
2291 window,
2292 |editor, _, event, window, cx| match event {
2293 BreakpointStoreEvent::ClearDebugLines => {
2294 editor.clear_row_highlights::<ActiveDebugLine>();
2295 editor.refresh_inline_values(cx);
2296 }
2297 BreakpointStoreEvent::SetDebugLine => {
2298 if editor.go_to_active_debug_line(window, cx) {
2299 cx.stop_propagation();
2300 }
2301
2302 editor.refresh_inline_values(cx);
2303 }
2304 _ => {}
2305 },
2306 ));
2307 let git_store = project.read(cx).git_store().clone();
2308 let project = project.clone();
2309 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2310 if let GitStoreEvent::RepositoryAdded = event {
2311 this.load_diff_task = Some(
2312 update_uncommitted_diff_for_buffer(
2313 cx.entity(),
2314 &project,
2315 this.buffer.read(cx).all_buffers(),
2316 this.buffer.clone(),
2317 cx,
2318 )
2319 .shared(),
2320 );
2321 }
2322 }));
2323 }
2324
2325 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2326
2327 let inlay_hint_settings =
2328 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2329 let focus_handle = cx.focus_handle();
2330 if !is_minimap {
2331 cx.on_focus(&focus_handle, window, Self::handle_focus)
2332 .detach();
2333 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2334 .detach();
2335 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2336 .detach();
2337 cx.on_blur(&focus_handle, window, Self::handle_blur)
2338 .detach();
2339 cx.observe_pending_input(window, Self::observe_pending_input)
2340 .detach();
2341 }
2342
2343 let show_indent_guides =
2344 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2345 Some(false)
2346 } else {
2347 None
2348 };
2349
2350 let breakpoint_store = match (&mode, project.as_ref()) {
2351 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2352 _ => None,
2353 };
2354
2355 let mut code_action_providers = Vec::new();
2356 let mut load_uncommitted_diff = None;
2357 if let Some(project) = project.clone() {
2358 load_uncommitted_diff = Some(
2359 update_uncommitted_diff_for_buffer(
2360 cx.entity(),
2361 &project,
2362 multi_buffer.read(cx).all_buffers(),
2363 multi_buffer.clone(),
2364 cx,
2365 )
2366 .shared(),
2367 );
2368 code_action_providers.push(Rc::new(project) as Rc<_>);
2369 }
2370
2371 let mut editor = Self {
2372 focus_handle,
2373 show_cursor_when_unfocused: false,
2374 last_focused_descendant: None,
2375 buffer: multi_buffer.clone(),
2376 display_map: display_map.clone(),
2377 placeholder_display_map: None,
2378 selections,
2379 scroll_manager: ScrollManager::new(cx),
2380 columnar_selection_state: None,
2381 add_selections_state: None,
2382 select_next_state: None,
2383 select_prev_state: None,
2384 selection_history: SelectionHistory::default(),
2385 defer_selection_effects: false,
2386 deferred_selection_effects_state: None,
2387 autoclose_regions: Vec::new(),
2388 snippet_stack: InvalidationStack::default(),
2389 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2390 ime_transaction: None,
2391 active_diagnostics: ActiveDiagnostic::None,
2392 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2393 inline_diagnostics_update: Task::ready(()),
2394 inline_diagnostics: Vec::new(),
2395 soft_wrap_mode_override,
2396 diagnostics_max_severity,
2397 hard_wrap: None,
2398 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2399 semantics_provider: project
2400 .as_ref()
2401 .map(|project| Rc::new(project.downgrade()) as _),
2402 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2403 project,
2404 blink_manager: blink_manager.clone(),
2405 show_local_selections: true,
2406 show_scrollbars: ScrollbarAxes {
2407 horizontal: full_mode,
2408 vertical: full_mode,
2409 },
2410 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2411 offset_content: !matches!(mode, EditorMode::SingleLine),
2412 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2413 show_gutter: full_mode,
2414 show_line_numbers: (!full_mode).then_some(false),
2415 use_relative_line_numbers: None,
2416 disable_expand_excerpt_buttons: !full_mode,
2417 delegate_expand_excerpts: false,
2418 delegate_stage_and_restore: false,
2419 delegate_open_excerpts: false,
2420 enable_lsp_data: true,
2421 enable_runnables: true,
2422 show_git_diff_gutter: None,
2423 show_code_actions: None,
2424 show_runnables: None,
2425 show_breakpoints: None,
2426 show_diff_review_button: false,
2427 show_wrap_guides: None,
2428 show_indent_guides,
2429 buffers_with_disabled_indent_guides: HashSet::default(),
2430 highlight_order: 0,
2431 highlighted_rows: HashMap::default(),
2432 background_highlights: HashMap::default(),
2433 gutter_highlights: HashMap::default(),
2434 scrollbar_marker_state: ScrollbarMarkerState::default(),
2435 active_indent_guides_state: ActiveIndentGuidesState::default(),
2436 nav_history: None,
2437 context_menu: RefCell::new(None),
2438 context_menu_options: None,
2439 mouse_context_menu: None,
2440 completion_tasks: Vec::new(),
2441 inline_blame_popover: None,
2442 inline_blame_popover_show_task: None,
2443 signature_help_state: SignatureHelpState::default(),
2444 auto_signature_help: None,
2445 find_all_references_task_sources: Vec::new(),
2446 next_completion_id: 0,
2447 next_inlay_id: 0,
2448 code_action_providers,
2449 available_code_actions: None,
2450 code_actions_task: None,
2451 quick_selection_highlight_task: None,
2452 debounced_selection_highlight_task: None,
2453 debounced_selection_highlight_complete: false,
2454 document_highlights_task: None,
2455 linked_editing_range_task: None,
2456 pending_rename: None,
2457 searchable: !is_minimap,
2458 cursor_shape: EditorSettings::get_global(cx)
2459 .cursor_shape
2460 .unwrap_or_default(),
2461 cursor_offset_on_selection: false,
2462 current_line_highlight: None,
2463 autoindent_mode: Some(AutoindentMode::EachLine),
2464 collapse_matches: false,
2465 workspace: None,
2466 input_enabled: !is_minimap,
2467 expects_character_input: !is_minimap,
2468 use_modal_editing: full_mode,
2469 read_only: is_minimap,
2470 use_autoclose: true,
2471 use_auto_surround: true,
2472 use_selection_highlight: true,
2473 auto_replace_emoji_shortcode: false,
2474 jsx_tag_auto_close_enabled_in_any_buffer: false,
2475 leader_id: None,
2476 remote_id: None,
2477 hover_state: HoverState::default(),
2478 pending_mouse_down: None,
2479 prev_pressure_stage: None,
2480 hovered_link_state: None,
2481 edit_prediction_provider: None,
2482 active_edit_prediction: None,
2483 stale_edit_prediction_in_menu: None,
2484 edit_prediction_preview: EditPredictionPreview::Inactive {
2485 released_too_fast: false,
2486 },
2487 inline_diagnostics_enabled: full_mode,
2488 diagnostics_enabled: full_mode,
2489 word_completions_enabled: full_mode,
2490 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2491 gutter_hovered: false,
2492 pixel_position_of_newest_cursor: None,
2493 last_bounds: None,
2494 last_position_map: None,
2495 expect_bounds_change: None,
2496 gutter_dimensions: GutterDimensions::default(),
2497 style: None,
2498 show_cursor_names: false,
2499 hovered_cursors: HashMap::default(),
2500 next_editor_action_id: EditorActionId::default(),
2501 editor_actions: Rc::default(),
2502 edit_predictions_hidden_for_vim_mode: false,
2503 show_edit_predictions_override: None,
2504 show_completions_on_input_override: None,
2505 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2506 edit_prediction_settings: EditPredictionSettings::Disabled,
2507 in_leading_whitespace: false,
2508 custom_context_menu: None,
2509 show_git_blame_gutter: false,
2510 show_git_blame_inline: false,
2511 show_selection_menu: None,
2512 show_git_blame_inline_delay_task: None,
2513 git_blame_inline_enabled: full_mode
2514 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2515 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2516 buffer_serialization: is_minimap.not().then(|| {
2517 BufferSerialization::new(
2518 ProjectSettings::get_global(cx)
2519 .session
2520 .restore_unsaved_buffers,
2521 )
2522 }),
2523 blame: None,
2524 blame_subscription: None,
2525
2526 breakpoint_store,
2527 gutter_breakpoint_indicator: (None, None),
2528 gutter_diff_review_indicator: (None, None),
2529 diff_review_drag_state: None,
2530 diff_review_overlays: Vec::new(),
2531 stored_review_comments: Vec::new(),
2532 next_review_comment_id: 0,
2533 hovered_diff_hunk_row: None,
2534 _subscriptions: (!is_minimap)
2535 .then(|| {
2536 vec![
2537 cx.observe(&multi_buffer, Self::on_buffer_changed),
2538 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2539 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2540 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2541 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2542 cx.observe_global_in::<GlobalTheme>(window, Self::theme_changed),
2543 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2544 cx.observe_window_activation(window, |editor, window, cx| {
2545 let active = window.is_window_active();
2546 editor.blink_manager.update(cx, |blink_manager, cx| {
2547 if active {
2548 blink_manager.enable(cx);
2549 } else {
2550 blink_manager.disable(cx);
2551 }
2552 });
2553 if active {
2554 editor.show_mouse_cursor(cx);
2555 }
2556 }),
2557 ]
2558 })
2559 .unwrap_or_default(),
2560 runnables: RunnableData::new(),
2561 pull_diagnostics_task: Task::ready(()),
2562 colors: None,
2563 refresh_colors_task: Task::ready(()),
2564 use_document_folding_ranges: false,
2565 refresh_folding_ranges_task: Task::ready(()),
2566 inlay_hints: None,
2567 next_color_inlay_id: 0,
2568 post_scroll_update: Task::ready(()),
2569 linked_edit_ranges: Default::default(),
2570 in_project_search: false,
2571 previous_search_ranges: None,
2572 breadcrumb_header: None,
2573 focused_block: None,
2574 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2575 addons: HashMap::default(),
2576 registered_buffers: HashMap::default(),
2577 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2578 selection_mark_mode: false,
2579 toggle_fold_multiple_buffers: Task::ready(()),
2580 serialize_selections: Task::ready(()),
2581 serialize_folds: Task::ready(()),
2582 text_style_refinement: None,
2583 load_diff_task: load_uncommitted_diff,
2584 temporary_diff_override: false,
2585 mouse_cursor_hidden: false,
2586 minimap: None,
2587 hide_mouse_mode: EditorSettings::get_global(cx)
2588 .hide_mouse
2589 .unwrap_or_default(),
2590 change_list: ChangeList::new(),
2591 mode,
2592 selection_drag_state: SelectionDragState::None,
2593 folding_newlines: Task::ready(()),
2594 lookup_key: None,
2595 select_next_is_case_sensitive: None,
2596 on_local_selections_changed: None,
2597 suppress_selection_callback: false,
2598 applicable_language_settings: HashMap::default(),
2599 semantic_token_state: SemanticTokenState::new(cx, full_mode),
2600 accent_data: None,
2601 bracket_fetched_tree_sitter_chunks: HashMap::default(),
2602 number_deleted_lines: false,
2603 refresh_matching_bracket_highlights_task: Task::ready(()),
2604 refresh_document_symbols_task: Task::ready(()).shared(),
2605 lsp_document_symbols: HashMap::default(),
2606 refresh_outline_symbols_at_cursor_at_cursor_task: Task::ready(()),
2607 outline_symbols_at_cursor: None,
2608 sticky_headers_task: Task::ready(()),
2609 sticky_headers: None,
2610 colorize_brackets_task: Task::ready(()),
2611 };
2612
2613 if is_minimap {
2614 return editor;
2615 }
2616
2617 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2618 editor.accent_data = editor.fetch_accent_data(cx);
2619
2620 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2621 editor
2622 ._subscriptions
2623 .push(cx.observe(breakpoints, |_, _, cx| {
2624 cx.notify();
2625 }));
2626 }
2627 editor._subscriptions.extend(project_subscriptions);
2628
2629 editor._subscriptions.push(cx.subscribe_in(
2630 &cx.entity(),
2631 window,
2632 |editor, _, e: &EditorEvent, window, cx| match e {
2633 EditorEvent::ScrollPositionChanged { local, .. } => {
2634 if *local {
2635 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2636 editor.inline_blame_popover.take();
2637 let snapshot = editor.snapshot(window, cx);
2638 let new_anchor = editor
2639 .scroll_manager
2640 .native_anchor(&snapshot.display_snapshot, cx);
2641 editor.update_restoration_data(cx, move |data| {
2642 data.scroll_position = (
2643 new_anchor.top_row(snapshot.buffer_snapshot()),
2644 new_anchor.offset,
2645 );
2646 });
2647
2648 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2649 cx.background_executor()
2650 .timer(Duration::from_millis(50))
2651 .await;
2652 editor
2653 .update_in(cx, |editor, window, cx| {
2654 editor.update_data_on_scroll(window, cx)
2655 })
2656 .ok();
2657 });
2658 }
2659 editor.refresh_sticky_headers(&editor.snapshot(window, cx), cx);
2660 }
2661 EditorEvent::Edited { .. } => {
2662 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2663 .map(|vim_mode| vim_mode.0)
2664 .unwrap_or(false);
2665 if !vim_mode {
2666 let display_map = editor.display_snapshot(cx);
2667 let selections = editor.selections.all_adjusted_display(&display_map);
2668 let pop_state = editor
2669 .change_list
2670 .last()
2671 .map(|previous| {
2672 previous.len() == selections.len()
2673 && previous.iter().enumerate().all(|(ix, p)| {
2674 p.to_display_point(&display_map).row()
2675 == selections[ix].head().row()
2676 })
2677 })
2678 .unwrap_or(false);
2679 let new_positions = selections
2680 .into_iter()
2681 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2682 .collect();
2683 editor
2684 .change_list
2685 .push_to_change_list(pop_state, new_positions);
2686 }
2687 }
2688 _ => (),
2689 },
2690 ));
2691
2692 if let Some(dap_store) = editor
2693 .project
2694 .as_ref()
2695 .map(|project| project.read(cx).dap_store())
2696 {
2697 let weak_editor = cx.weak_entity();
2698
2699 editor
2700 ._subscriptions
2701 .push(
2702 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2703 let session_entity = cx.entity();
2704 weak_editor
2705 .update(cx, |editor, cx| {
2706 editor._subscriptions.push(
2707 cx.subscribe(&session_entity, Self::on_debug_session_event),
2708 );
2709 })
2710 .ok();
2711 }),
2712 );
2713
2714 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2715 editor
2716 ._subscriptions
2717 .push(cx.subscribe(&session, Self::on_debug_session_event));
2718 }
2719 }
2720
2721 // skip adding the initial selection to selection history
2722 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2723 editor.end_selection(window, cx);
2724 editor.selection_history.mode = SelectionHistoryMode::Normal;
2725
2726 editor.scroll_manager.show_scrollbars(window, cx);
2727 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2728
2729 if full_mode {
2730 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2731 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2732
2733 if editor.git_blame_inline_enabled {
2734 editor.start_git_blame_inline(false, window, cx);
2735 }
2736
2737 editor.go_to_active_debug_line(window, cx);
2738
2739 editor.minimap =
2740 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2741 editor.colors = Some(LspColorData::new(cx));
2742 editor.use_document_folding_ranges = true;
2743 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2744
2745 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2746 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2747 }
2748 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2749 }
2750
2751 editor
2752 }
2753
2754 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2755 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2756 }
2757
2758 pub fn deploy_mouse_context_menu(
2759 &mut self,
2760 position: gpui::Point<Pixels>,
2761 context_menu: Entity<ContextMenu>,
2762 window: &mut Window,
2763 cx: &mut Context<Self>,
2764 ) {
2765 self.mouse_context_menu = Some(MouseContextMenu::new(
2766 self,
2767 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2768 context_menu,
2769 window,
2770 cx,
2771 ));
2772 }
2773
2774 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2775 self.mouse_context_menu
2776 .as_ref()
2777 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2778 }
2779
2780 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2781 if self
2782 .selections
2783 .pending_anchor()
2784 .is_some_and(|pending_selection| {
2785 let snapshot = self.buffer().read(cx).snapshot(cx);
2786 pending_selection.range().includes(range, &snapshot)
2787 })
2788 {
2789 return true;
2790 }
2791
2792 self.selections
2793 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2794 .into_iter()
2795 .any(|selection| {
2796 // This is needed to cover a corner case, if we just check for an existing
2797 // selection in the fold range, having a cursor at the start of the fold
2798 // marks it as selected. Non-empty selections don't cause this.
2799 let length = selection.end - selection.start;
2800 length > 0
2801 })
2802 }
2803
2804 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2805 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2806 }
2807
2808 fn key_context_internal(
2809 &self,
2810 has_active_edit_prediction: bool,
2811 window: &mut Window,
2812 cx: &mut App,
2813 ) -> KeyContext {
2814 let mut key_context = KeyContext::new_with_defaults();
2815 key_context.add("Editor");
2816 let mode = match self.mode {
2817 EditorMode::SingleLine => "single_line",
2818 EditorMode::AutoHeight { .. } => "auto_height",
2819 EditorMode::Minimap { .. } => "minimap",
2820 EditorMode::Full { .. } => "full",
2821 };
2822
2823 if EditorSettings::jupyter_enabled(cx) {
2824 key_context.add("jupyter");
2825 }
2826
2827 key_context.set("mode", mode);
2828 if self.pending_rename.is_some() {
2829 key_context.add("renaming");
2830 }
2831
2832 if let Some(snippet_stack) = self.snippet_stack.last() {
2833 key_context.add("in_snippet");
2834
2835 if snippet_stack.active_index > 0 {
2836 key_context.add("has_previous_tabstop");
2837 }
2838
2839 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2840 key_context.add("has_next_tabstop");
2841 }
2842 }
2843
2844 match self.context_menu.borrow().as_ref() {
2845 Some(CodeContextMenu::Completions(menu)) => {
2846 if menu.visible() {
2847 key_context.add("menu");
2848 key_context.add("showing_completions");
2849 }
2850 }
2851 Some(CodeContextMenu::CodeActions(menu)) => {
2852 if menu.visible() {
2853 key_context.add("menu");
2854 key_context.add("showing_code_actions")
2855 }
2856 }
2857 None => {}
2858 }
2859
2860 if self.signature_help_state.has_multiple_signatures() {
2861 key_context.add("showing_signature_help");
2862 }
2863
2864 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2865 if !self.focus_handle(cx).contains_focused(window, cx)
2866 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2867 {
2868 for addon in self.addons.values() {
2869 addon.extend_key_context(&mut key_context, cx)
2870 }
2871 }
2872
2873 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2874 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2875 Some(
2876 file.full_path(cx)
2877 .extension()?
2878 .to_string_lossy()
2879 .to_lowercase(),
2880 )
2881 }) {
2882 key_context.set("extension", extension);
2883 }
2884 } else {
2885 key_context.add("multibuffer");
2886 }
2887
2888 if has_active_edit_prediction {
2889 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2890 key_context.add("copilot_suggestion");
2891 }
2892
2893 if self.in_leading_whitespace {
2894 key_context.add("in_leading_whitespace");
2895 }
2896 if self.edit_prediction_requires_modifier() {
2897 key_context.set("edit_prediction_mode", "subtle")
2898 } else {
2899 key_context.set("edit_prediction_mode", "eager");
2900 }
2901
2902 if self.selection_mark_mode {
2903 key_context.add("selection_mode");
2904 }
2905
2906 let disjoint = self.selections.disjoint_anchors();
2907 if matches!(
2908 &self.mode,
2909 EditorMode::SingleLine | EditorMode::AutoHeight { .. }
2910 ) && let [selection] = disjoint
2911 && selection.start == selection.end
2912 {
2913 let snapshot = self.snapshot(window, cx);
2914 let snapshot = snapshot.buffer_snapshot();
2915 let caret_offset = selection.end.to_offset(snapshot);
2916
2917 if caret_offset == MultiBufferOffset(0) {
2918 key_context.add("start_of_input");
2919 }
2920
2921 if caret_offset == snapshot.len() {
2922 key_context.add("end_of_input");
2923 }
2924 }
2925
2926 if self.has_any_expanded_diff_hunks(cx) {
2927 key_context.add("diffs_expanded");
2928 }
2929
2930 key_context
2931 }
2932
2933 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2934 self.last_bounds.as_ref()
2935 }
2936
2937 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2938 if self.mouse_cursor_hidden {
2939 self.mouse_cursor_hidden = false;
2940 cx.notify();
2941 }
2942 }
2943
2944 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2945 let hide_mouse_cursor = match origin {
2946 HideMouseCursorOrigin::TypingAction => {
2947 matches!(
2948 self.hide_mouse_mode,
2949 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2950 )
2951 }
2952 HideMouseCursorOrigin::MovementAction => {
2953 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2954 }
2955 };
2956 if self.mouse_cursor_hidden != hide_mouse_cursor {
2957 self.mouse_cursor_hidden = hide_mouse_cursor;
2958 cx.notify();
2959 }
2960 }
2961
2962 fn accept_edit_prediction_keystroke(
2963 &self,
2964 granularity: EditPredictionGranularity,
2965 window: &mut Window,
2966 cx: &mut App,
2967 ) -> Option<gpui::KeybindingKeystroke> {
2968 let key_context = self.key_context_internal(true, window, cx);
2969
2970 let bindings =
2971 match granularity {
2972 EditPredictionGranularity::Word => window
2973 .bindings_for_action_in_context(&AcceptNextWordEditPrediction, key_context),
2974 EditPredictionGranularity::Line => window
2975 .bindings_for_action_in_context(&AcceptNextLineEditPrediction, key_context),
2976 EditPredictionGranularity::Full => {
2977 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2978 }
2979 };
2980
2981 bindings
2982 .into_iter()
2983 .rev()
2984 .find_map(|binding| match binding.keystrokes() {
2985 [keystroke, ..] => Some(keystroke.clone()),
2986 _ => None,
2987 })
2988 }
2989
2990 fn preview_edit_prediction_keystroke(
2991 &self,
2992 window: &mut Window,
2993 cx: &mut App,
2994 ) -> Option<gpui::KeybindingKeystroke> {
2995 let key_context = self.key_context_internal(true, window, cx);
2996 let bindings = window.bindings_for_action_in_context(&AcceptEditPrediction, key_context);
2997 bindings
2998 .into_iter()
2999 .rev()
3000 .find_map(|binding| match binding.keystrokes() {
3001 [keystroke, ..] if keystroke.modifiers().modified() => Some(keystroke.clone()),
3002 _ => None,
3003 })
3004 }
3005
3006 fn edit_prediction_preview_modifiers_held(
3007 &self,
3008 modifiers: &Modifiers,
3009 window: &mut Window,
3010 cx: &mut App,
3011 ) -> bool {
3012 let key_context = self.key_context_internal(true, window, cx);
3013 let actions: [&dyn Action; 3] = [
3014 &AcceptEditPrediction,
3015 &AcceptNextWordEditPrediction,
3016 &AcceptNextLineEditPrediction,
3017 ];
3018
3019 actions.into_iter().any(|action| {
3020 window
3021 .bindings_for_action_in_context(action, key_context.clone())
3022 .into_iter()
3023 .rev()
3024 .any(|binding| {
3025 binding.keystrokes().first().is_some_and(|keystroke| {
3026 keystroke.modifiers().modified() && keystroke.modifiers() == modifiers
3027 })
3028 })
3029 })
3030 }
3031
3032 fn edit_prediction_cursor_popover_prefers_preview(
3033 &self,
3034 completion: &EditPredictionState,
3035 cx: &App,
3036 ) -> bool {
3037 let multibuffer_snapshot = self.buffer.read(cx).snapshot(cx);
3038
3039 match &completion.completion {
3040 EditPrediction::Edit {
3041 edits, snapshot, ..
3042 } => {
3043 let mut start_row: Option<u32> = None;
3044 let mut end_row: Option<u32> = None;
3045
3046 for (range, text) in edits {
3047 let Some((_, range)) =
3048 multibuffer_snapshot.anchor_range_to_buffer_anchor_range(range.clone())
3049 else {
3050 continue;
3051 };
3052 let edit_start_row = range.start.to_point(snapshot).row;
3053 let old_end_row = range.end.to_point(snapshot).row;
3054 let inserted_newline_count = text
3055 .as_ref()
3056 .chars()
3057 .filter(|character| *character == '\n')
3058 .count() as u32;
3059 let deleted_newline_count = old_end_row - edit_start_row;
3060 let preview_end_row = edit_start_row + inserted_newline_count;
3061
3062 start_row =
3063 Some(start_row.map_or(edit_start_row, |row| row.min(edit_start_row)));
3064 end_row = Some(end_row.map_or(preview_end_row, |row| row.max(preview_end_row)));
3065
3066 if deleted_newline_count > 1 {
3067 end_row = Some(end_row.map_or(old_end_row, |row| row.max(old_end_row)));
3068 }
3069 }
3070
3071 start_row
3072 .zip(end_row)
3073 .is_some_and(|(start_row, end_row)| end_row > start_row)
3074 }
3075 EditPrediction::MoveWithin { .. } | EditPrediction::MoveOutside { .. } => false,
3076 }
3077 }
3078
3079 fn edit_prediction_keybind_display(
3080 &self,
3081 surface: EditPredictionKeybindSurface,
3082 window: &mut Window,
3083 cx: &mut App,
3084 ) -> EditPredictionKeybindDisplay {
3085 let accept_keystroke =
3086 self.accept_edit_prediction_keystroke(EditPredictionGranularity::Full, window, cx);
3087 let preview_keystroke = self.preview_edit_prediction_keystroke(window, cx);
3088
3089 let action = match surface {
3090 EditPredictionKeybindSurface::Inline
3091 | EditPredictionKeybindSurface::CursorPopoverCompact => {
3092 if self.edit_prediction_requires_modifier() {
3093 EditPredictionKeybindAction::Preview
3094 } else {
3095 EditPredictionKeybindAction::Accept
3096 }
3097 }
3098 EditPredictionKeybindSurface::CursorPopoverExpanded => self
3099 .active_edit_prediction
3100 .as_ref()
3101 .filter(|completion| {
3102 self.edit_prediction_cursor_popover_prefers_preview(completion, cx)
3103 })
3104 .map_or(EditPredictionKeybindAction::Accept, |_| {
3105 EditPredictionKeybindAction::Preview
3106 }),
3107 };
3108 #[cfg(test)]
3109 let preview_copy = preview_keystroke.clone();
3110 #[cfg(test)]
3111 let accept_copy = accept_keystroke.clone();
3112
3113 let displayed_keystroke = match surface {
3114 EditPredictionKeybindSurface::Inline => match action {
3115 EditPredictionKeybindAction::Accept => accept_keystroke,
3116 EditPredictionKeybindAction::Preview => preview_keystroke,
3117 },
3118 EditPredictionKeybindSurface::CursorPopoverCompact
3119 | EditPredictionKeybindSurface::CursorPopoverExpanded => match action {
3120 EditPredictionKeybindAction::Accept => accept_keystroke,
3121 EditPredictionKeybindAction::Preview => {
3122 preview_keystroke.or_else(|| accept_keystroke.clone())
3123 }
3124 },
3125 };
3126
3127 let missing_accept_keystroke = displayed_keystroke.is_none();
3128
3129 EditPredictionKeybindDisplay {
3130 #[cfg(test)]
3131 accept_keystroke: accept_copy,
3132 #[cfg(test)]
3133 preview_keystroke: preview_copy,
3134 displayed_keystroke,
3135 action,
3136 missing_accept_keystroke,
3137 show_hold_label: matches!(surface, EditPredictionKeybindSurface::CursorPopoverCompact)
3138 && self.edit_prediction_preview.released_too_fast(),
3139 }
3140 }
3141
3142 pub fn new_file(
3143 workspace: &mut Workspace,
3144 _: &workspace::NewFile,
3145 window: &mut Window,
3146 cx: &mut Context<Workspace>,
3147 ) {
3148 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
3149 "Failed to create buffer",
3150 window,
3151 cx,
3152 |e, _, _| match e.error_code() {
3153 ErrorCode::RemoteUpgradeRequired => Some(format!(
3154 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
3155 e.error_tag("required").unwrap_or("the latest version")
3156 )),
3157 _ => None,
3158 },
3159 );
3160 }
3161
3162 pub fn new_in_workspace(
3163 workspace: &mut Workspace,
3164 window: &mut Window,
3165 cx: &mut Context<Workspace>,
3166 ) -> Task<Result<Entity<Editor>>> {
3167 let project = workspace.project().clone();
3168 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
3169
3170 cx.spawn_in(window, async move |workspace, cx| {
3171 let buffer = create.await?;
3172 workspace.update_in(cx, |workspace, window, cx| {
3173 let editor =
3174 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
3175 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
3176 editor
3177 })
3178 })
3179 }
3180
3181 fn new_file_vertical(
3182 workspace: &mut Workspace,
3183 _: &workspace::NewFileSplitVertical,
3184 window: &mut Window,
3185 cx: &mut Context<Workspace>,
3186 ) {
3187 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
3188 }
3189
3190 fn new_file_horizontal(
3191 workspace: &mut Workspace,
3192 _: &workspace::NewFileSplitHorizontal,
3193 window: &mut Window,
3194 cx: &mut Context<Workspace>,
3195 ) {
3196 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
3197 }
3198
3199 fn new_file_split(
3200 workspace: &mut Workspace,
3201 action: &workspace::NewFileSplit,
3202 window: &mut Window,
3203 cx: &mut Context<Workspace>,
3204 ) {
3205 Self::new_file_in_direction(workspace, action.0, window, cx)
3206 }
3207
3208 fn new_file_in_direction(
3209 workspace: &mut Workspace,
3210 direction: SplitDirection,
3211 window: &mut Window,
3212 cx: &mut Context<Workspace>,
3213 ) {
3214 let project = workspace.project().clone();
3215 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
3216
3217 cx.spawn_in(window, async move |workspace, cx| {
3218 let buffer = create.await?;
3219 workspace.update_in(cx, move |workspace, window, cx| {
3220 workspace.split_item(
3221 direction,
3222 Box::new(
3223 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
3224 ),
3225 window,
3226 cx,
3227 )
3228 })?;
3229 anyhow::Ok(())
3230 })
3231 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
3232 match e.error_code() {
3233 ErrorCode::RemoteUpgradeRequired => Some(format!(
3234 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
3235 e.error_tag("required").unwrap_or("the latest version")
3236 )),
3237 _ => None,
3238 }
3239 });
3240 }
3241
3242 pub fn leader_id(&self) -> Option<CollaboratorId> {
3243 self.leader_id
3244 }
3245
3246 pub fn buffer(&self) -> &Entity<MultiBuffer> {
3247 &self.buffer
3248 }
3249
3250 pub fn project(&self) -> Option<&Entity<Project>> {
3251 self.project.as_ref()
3252 }
3253
3254 pub fn workspace(&self) -> Option<Entity<Workspace>> {
3255 self.workspace.as_ref()?.0.upgrade()
3256 }
3257
3258 /// Detaches a task and shows an error notification in the workspace if available,
3259 /// otherwise just logs the error.
3260 pub fn detach_and_notify_err<R, E>(
3261 &self,
3262 task: Task<Result<R, E>>,
3263 window: &mut Window,
3264 cx: &mut App,
3265 ) where
3266 E: std::fmt::Debug + std::fmt::Display + 'static,
3267 R: 'static,
3268 {
3269 if let Some(workspace) = self.workspace() {
3270 task.detach_and_notify_err(workspace.downgrade(), window, cx);
3271 } else {
3272 task.detach_and_log_err(cx);
3273 }
3274 }
3275
3276 /// Returns the workspace serialization ID if this editor should be serialized.
3277 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
3278 self.workspace
3279 .as_ref()
3280 .filter(|_| self.should_serialize_buffer())
3281 .and_then(|workspace| workspace.1)
3282 }
3283
3284 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
3285 self.buffer().read(cx).title(cx)
3286 }
3287
3288 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
3289 let git_blame_gutter_max_author_length = self
3290 .render_git_blame_gutter(cx)
3291 .then(|| {
3292 if let Some(blame) = self.blame.as_ref() {
3293 let max_author_length =
3294 blame.update(cx, |blame, cx| blame.max_author_length(cx));
3295 Some(max_author_length)
3296 } else {
3297 None
3298 }
3299 })
3300 .flatten();
3301
3302 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3303
3304 EditorSnapshot {
3305 mode: self.mode.clone(),
3306 show_gutter: self.show_gutter,
3307 offset_content: self.offset_content,
3308 show_line_numbers: self.show_line_numbers,
3309 number_deleted_lines: self.number_deleted_lines,
3310 show_git_diff_gutter: self.show_git_diff_gutter,
3311 semantic_tokens_enabled: self.semantic_token_state.enabled(),
3312 show_code_actions: self.show_code_actions,
3313 show_runnables: self.show_runnables,
3314 show_breakpoints: self.show_breakpoints,
3315 git_blame_gutter_max_author_length,
3316 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
3317 display_snapshot,
3318 placeholder_display_snapshot: self
3319 .placeholder_display_map
3320 .as_ref()
3321 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
3322 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
3323 is_focused: self.focus_handle.is_focused(window),
3324 current_line_highlight: self
3325 .current_line_highlight
3326 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
3327 gutter_hovered: self.gutter_hovered,
3328 }
3329 }
3330
3331 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
3332 self.buffer.read(cx).language_at(point, cx)
3333 }
3334
3335 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
3336 self.buffer.read(cx).read(cx).file_at(point).cloned()
3337 }
3338
3339 pub fn active_buffer(&self, cx: &App) -> Option<Entity<Buffer>> {
3340 let multibuffer = self.buffer.read(cx);
3341 let snapshot = multibuffer.snapshot(cx);
3342 let (anchor, _) =
3343 snapshot.anchor_to_buffer_anchor(self.selections.newest_anchor().head())?;
3344 multibuffer.buffer(anchor.buffer_id)
3345 }
3346
3347 pub fn mode(&self) -> &EditorMode {
3348 &self.mode
3349 }
3350
3351 pub fn set_mode(&mut self, mode: EditorMode) {
3352 self.mode = mode;
3353 }
3354
3355 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
3356 self.collaboration_hub.as_deref()
3357 }
3358
3359 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
3360 self.collaboration_hub = Some(hub);
3361 }
3362
3363 pub fn set_in_project_search(&mut self, in_project_search: bool) {
3364 self.in_project_search = in_project_search;
3365 }
3366
3367 pub fn set_custom_context_menu(
3368 &mut self,
3369 f: impl 'static
3370 + Fn(
3371 &mut Self,
3372 DisplayPoint,
3373 &mut Window,
3374 &mut Context<Self>,
3375 ) -> Option<Entity<ui::ContextMenu>>,
3376 ) {
3377 self.custom_context_menu = Some(Box::new(f))
3378 }
3379
3380 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
3381 self.completion_provider = provider;
3382 }
3383
3384 #[cfg(any(test, feature = "test-support"))]
3385 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
3386 self.completion_provider.clone()
3387 }
3388
3389 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
3390 self.semantics_provider.clone()
3391 }
3392
3393 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
3394 self.semantics_provider = provider;
3395 }
3396
3397 pub fn set_edit_prediction_provider<T>(
3398 &mut self,
3399 provider: Option<Entity<T>>,
3400 window: &mut Window,
3401 cx: &mut Context<Self>,
3402 ) where
3403 T: EditPredictionDelegate,
3404 {
3405 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionDelegate {
3406 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
3407 if this.focus_handle.is_focused(window) {
3408 this.update_visible_edit_prediction(window, cx);
3409 }
3410 }),
3411 provider: Arc::new(provider),
3412 });
3413 self.update_edit_prediction_settings(cx);
3414 self.refresh_edit_prediction(false, false, window, cx);
3415 }
3416
3417 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
3418 self.placeholder_display_map
3419 .as_ref()
3420 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
3421 }
3422
3423 pub fn set_placeholder_text(
3424 &mut self,
3425 placeholder_text: &str,
3426 window: &mut Window,
3427 cx: &mut Context<Self>,
3428 ) {
3429 let multibuffer = cx
3430 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3431
3432 let style = window.text_style();
3433
3434 self.placeholder_display_map = Some(cx.new(|cx| {
3435 DisplayMap::new(
3436 multibuffer,
3437 style.font(),
3438 style.font_size.to_pixels(window.rem_size()),
3439 None,
3440 FILE_HEADER_HEIGHT,
3441 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3442 Default::default(),
3443 DiagnosticSeverity::Off,
3444 cx,
3445 )
3446 }));
3447 cx.notify();
3448 }
3449
3450 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3451 self.cursor_shape = cursor_shape;
3452
3453 // Disrupt blink for immediate user feedback that the cursor shape has changed
3454 self.blink_manager.update(cx, BlinkManager::show_cursor);
3455
3456 cx.notify();
3457 }
3458
3459 pub fn cursor_shape(&self) -> CursorShape {
3460 self.cursor_shape
3461 }
3462
3463 pub fn set_cursor_offset_on_selection(&mut self, set_cursor_offset_on_selection: bool) {
3464 self.cursor_offset_on_selection = set_cursor_offset_on_selection;
3465 }
3466
3467 pub fn set_current_line_highlight(
3468 &mut self,
3469 current_line_highlight: Option<CurrentLineHighlight>,
3470 ) {
3471 self.current_line_highlight = current_line_highlight;
3472 }
3473
3474 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3475 self.collapse_matches = collapse_matches;
3476 }
3477
3478 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3479 if self.collapse_matches {
3480 return range.start..range.start;
3481 }
3482 range.clone()
3483 }
3484
3485 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3486 self.display_map.read(cx).clip_at_line_ends
3487 }
3488
3489 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3490 if self.display_map.read(cx).clip_at_line_ends != clip {
3491 self.display_map
3492 .update(cx, |map, _| map.clip_at_line_ends = clip);
3493 }
3494 }
3495
3496 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3497 self.input_enabled = input_enabled;
3498 }
3499
3500 pub fn set_expects_character_input(&mut self, expects_character_input: bool) {
3501 self.expects_character_input = expects_character_input;
3502 }
3503
3504 pub fn set_edit_predictions_hidden_for_vim_mode(
3505 &mut self,
3506 hidden: bool,
3507 window: &mut Window,
3508 cx: &mut Context<Self>,
3509 ) {
3510 if hidden != self.edit_predictions_hidden_for_vim_mode {
3511 self.edit_predictions_hidden_for_vim_mode = hidden;
3512 if hidden {
3513 self.update_visible_edit_prediction(window, cx);
3514 } else {
3515 self.refresh_edit_prediction(true, false, window, cx);
3516 }
3517 }
3518 }
3519
3520 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3521 self.menu_edit_predictions_policy = value;
3522 }
3523
3524 pub fn set_autoindent(&mut self, autoindent: bool) {
3525 if autoindent {
3526 self.autoindent_mode = Some(AutoindentMode::EachLine);
3527 } else {
3528 self.autoindent_mode = None;
3529 }
3530 }
3531
3532 pub fn capability(&self, cx: &App) -> Capability {
3533 if self.read_only {
3534 Capability::ReadOnly
3535 } else {
3536 self.buffer.read(cx).capability()
3537 }
3538 }
3539
3540 pub fn read_only(&self, cx: &App) -> bool {
3541 self.read_only || self.buffer.read(cx).read_only()
3542 }
3543
3544 pub fn set_read_only(&mut self, read_only: bool) {
3545 self.read_only = read_only;
3546 }
3547
3548 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3549 self.use_autoclose = autoclose;
3550 }
3551
3552 pub fn set_use_selection_highlight(&mut self, highlight: bool) {
3553 self.use_selection_highlight = highlight;
3554 }
3555
3556 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3557 self.use_auto_surround = auto_surround;
3558 }
3559
3560 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3561 self.auto_replace_emoji_shortcode = auto_replace;
3562 }
3563
3564 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3565 self.buffer_serialization = should_serialize.then(|| {
3566 BufferSerialization::new(
3567 ProjectSettings::get_global(cx)
3568 .session
3569 .restore_unsaved_buffers,
3570 )
3571 })
3572 }
3573
3574 fn should_serialize_buffer(&self) -> bool {
3575 self.buffer_serialization.is_some()
3576 }
3577
3578 pub fn toggle_edit_predictions(
3579 &mut self,
3580 _: &ToggleEditPrediction,
3581 window: &mut Window,
3582 cx: &mut Context<Self>,
3583 ) {
3584 if self.show_edit_predictions_override.is_some() {
3585 self.set_show_edit_predictions(None, window, cx);
3586 } else {
3587 let show_edit_predictions = !self.edit_predictions_enabled();
3588 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3589 }
3590 }
3591
3592 pub fn set_show_completions_on_input(&mut self, show_completions_on_input: Option<bool>) {
3593 self.show_completions_on_input_override = show_completions_on_input;
3594 }
3595
3596 pub fn set_show_edit_predictions(
3597 &mut self,
3598 show_edit_predictions: Option<bool>,
3599 window: &mut Window,
3600 cx: &mut Context<Self>,
3601 ) {
3602 self.show_edit_predictions_override = show_edit_predictions;
3603 self.update_edit_prediction_settings(cx);
3604
3605 if let Some(false) = show_edit_predictions {
3606 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
3607 } else {
3608 self.refresh_edit_prediction(false, true, window, cx);
3609 }
3610 }
3611
3612 fn edit_predictions_disabled_in_scope(
3613 &self,
3614 buffer: &Entity<Buffer>,
3615 buffer_position: language::Anchor,
3616 cx: &App,
3617 ) -> bool {
3618 let snapshot = buffer.read(cx).snapshot();
3619 let settings = snapshot.settings_at(buffer_position, cx);
3620
3621 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3622 return false;
3623 };
3624
3625 scope.override_name().is_some_and(|scope_name| {
3626 settings
3627 .edit_predictions_disabled_in
3628 .iter()
3629 .any(|s| s == scope_name)
3630 })
3631 }
3632
3633 pub fn set_use_modal_editing(&mut self, to: bool) {
3634 self.use_modal_editing = to;
3635 }
3636
3637 pub fn use_modal_editing(&self) -> bool {
3638 self.use_modal_editing
3639 }
3640
3641 fn selections_did_change(
3642 &mut self,
3643 local: bool,
3644 old_cursor_position: &Anchor,
3645 effects: SelectionEffects,
3646 window: &mut Window,
3647 cx: &mut Context<Self>,
3648 ) {
3649 window.invalidate_character_coordinates();
3650
3651 // Copy selections to primary selection buffer
3652 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3653 if local {
3654 let selections = self
3655 .selections
3656 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3657 let buffer_handle = self.buffer.read(cx).read(cx);
3658
3659 let mut text = String::new();
3660 for (index, selection) in selections.iter().enumerate() {
3661 let text_for_selection = buffer_handle
3662 .text_for_range(selection.start..selection.end)
3663 .collect::<String>();
3664
3665 text.push_str(&text_for_selection);
3666 if index != selections.len() - 1 {
3667 text.push('\n');
3668 }
3669 }
3670
3671 if !text.is_empty() {
3672 cx.write_to_primary(ClipboardItem::new_string(text));
3673 }
3674 }
3675
3676 let selection_anchors = self.selections.disjoint_anchors_arc();
3677
3678 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3679 self.buffer.update(cx, |buffer, cx| {
3680 buffer.set_active_selections(
3681 &selection_anchors,
3682 self.selections.line_mode(),
3683 self.cursor_shape,
3684 cx,
3685 )
3686 });
3687 }
3688 let display_map = self
3689 .display_map
3690 .update(cx, |display_map, cx| display_map.snapshot(cx));
3691 let buffer = display_map.buffer_snapshot();
3692 if self.selections.count() == 1 {
3693 self.add_selections_state = None;
3694 }
3695 self.select_next_state = None;
3696 self.select_prev_state = None;
3697 self.select_syntax_node_history.try_clear();
3698 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3699 self.snippet_stack.invalidate(&selection_anchors, buffer);
3700 self.take_rename(false, window, cx);
3701
3702 let newest_selection = self.selections.newest_anchor();
3703 let new_cursor_position = newest_selection.head();
3704 let selection_start = newest_selection.start;
3705
3706 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3707 self.push_to_nav_history(
3708 *old_cursor_position,
3709 Some(new_cursor_position.to_point(buffer)),
3710 false,
3711 effects.nav_history == Some(true),
3712 cx,
3713 );
3714 }
3715
3716 if local {
3717 if let Some((anchor, _)) = buffer.anchor_to_buffer_anchor(new_cursor_position) {
3718 self.register_buffer(anchor.buffer_id, cx);
3719 }
3720
3721 let mut context_menu = self.context_menu.borrow_mut();
3722 let completion_menu = match context_menu.as_ref() {
3723 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3724 Some(CodeContextMenu::CodeActions(_)) => {
3725 *context_menu = None;
3726 None
3727 }
3728 None => None,
3729 };
3730 let completion_position = completion_menu.map(|menu| menu.initial_position);
3731 drop(context_menu);
3732
3733 if effects.completions
3734 && let Some(completion_position) = completion_position
3735 {
3736 let start_offset = selection_start.to_offset(buffer);
3737 let position_matches = start_offset == completion_position.to_offset(buffer);
3738 let continue_showing = if let Some((snap, ..)) =
3739 buffer.point_to_buffer_offset(completion_position)
3740 && !snap.capability.editable()
3741 {
3742 false
3743 } else if position_matches {
3744 if self.snippet_stack.is_empty() {
3745 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3746 == Some(CharKind::Word)
3747 } else {
3748 // Snippet choices can be shown even when the cursor is in whitespace.
3749 // Dismissing the menu with actions like backspace is handled by
3750 // invalidation regions.
3751 true
3752 }
3753 } else {
3754 false
3755 };
3756
3757 if continue_showing {
3758 self.open_or_update_completions_menu(None, None, false, window, cx);
3759 } else {
3760 self.hide_context_menu(window, cx);
3761 }
3762 }
3763
3764 hide_hover(self, cx);
3765
3766 if old_cursor_position.to_display_point(&display_map).row()
3767 != new_cursor_position.to_display_point(&display_map).row()
3768 {
3769 self.available_code_actions.take();
3770 }
3771 self.refresh_code_actions(window, cx);
3772 self.refresh_document_highlights(cx);
3773 refresh_linked_ranges(self, window, cx);
3774
3775 self.refresh_selected_text_highlights(&display_map, false, window, cx);
3776 self.refresh_matching_bracket_highlights(&display_map, cx);
3777 self.refresh_outline_symbols_at_cursor(cx);
3778 self.update_visible_edit_prediction(window, cx);
3779 self.inline_blame_popover.take();
3780 if self.git_blame_inline_enabled {
3781 self.start_inline_blame_timer(window, cx);
3782 }
3783 }
3784
3785 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3786
3787 if local && !self.suppress_selection_callback {
3788 if let Some(callback) = self.on_local_selections_changed.as_ref() {
3789 let cursor_position = self.selections.newest::<Point>(&display_map).head();
3790 callback(cursor_position, window, cx);
3791 }
3792 }
3793
3794 cx.emit(EditorEvent::SelectionsChanged { local });
3795
3796 let selections = &self.selections.disjoint_anchors_arc();
3797 if selections.len() == 1 {
3798 cx.emit(SearchEvent::ActiveMatchChanged)
3799 }
3800 if local && let Some(buffer_snapshot) = buffer.as_singleton() {
3801 let inmemory_selections = selections
3802 .iter()
3803 .map(|s| {
3804 let start = s.range().start.text_anchor_in(buffer_snapshot);
3805 let end = s.range().end.text_anchor_in(buffer_snapshot);
3806 (start..end).to_point(buffer_snapshot)
3807 })
3808 .collect();
3809 self.update_restoration_data(cx, |data| {
3810 data.selections = inmemory_selections;
3811 });
3812
3813 if WorkspaceSettings::get(None, cx).restore_on_startup
3814 != RestoreOnStartupBehavior::EmptyTab
3815 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3816 {
3817 let snapshot = self.buffer().read(cx).snapshot(cx);
3818 let selections = selections.clone();
3819 let background_executor = cx.background_executor().clone();
3820 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3821 let db = EditorDb::global(cx);
3822 self.serialize_selections = cx.background_spawn(async move {
3823 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3824 let db_selections = selections
3825 .iter()
3826 .map(|selection| {
3827 (
3828 selection.start.to_offset(&snapshot).0,
3829 selection.end.to_offset(&snapshot).0,
3830 )
3831 })
3832 .collect();
3833
3834 db.save_editor_selections(editor_id, workspace_id, db_selections)
3835 .await
3836 .with_context(|| {
3837 format!(
3838 "persisting editor selections for editor {editor_id}, \
3839 workspace {workspace_id:?}"
3840 )
3841 })
3842 .log_err();
3843 });
3844 }
3845 }
3846
3847 cx.notify();
3848 }
3849
3850 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3851 use text::ToOffset as _;
3852
3853 if self.mode.is_minimap()
3854 || WorkspaceSettings::get(None, cx).restore_on_startup
3855 == RestoreOnStartupBehavior::EmptyTab
3856 {
3857 return;
3858 }
3859
3860 let display_snapshot = self
3861 .display_map
3862 .update(cx, |display_map, cx| display_map.snapshot(cx));
3863 let Some(buffer_snapshot) = display_snapshot.buffer_snapshot().as_singleton() else {
3864 return;
3865 };
3866 let inmemory_folds = display_snapshot
3867 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3868 .map(|fold| {
3869 let start = fold.range.start.text_anchor_in(buffer_snapshot);
3870 let end = fold.range.end.text_anchor_in(buffer_snapshot);
3871 (start..end).to_point(buffer_snapshot)
3872 })
3873 .collect();
3874 self.update_restoration_data(cx, |data| {
3875 data.folds = inmemory_folds;
3876 });
3877
3878 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3879 return;
3880 };
3881
3882 // Get file path for path-based fold storage (survives tab close)
3883 let Some(file_path) = self.buffer().read(cx).as_singleton().and_then(|buffer| {
3884 project::File::from_dyn(buffer.read(cx).file())
3885 .map(|file| Arc::<Path>::from(file.abs_path(cx)))
3886 }) else {
3887 return;
3888 };
3889
3890 let background_executor = cx.background_executor().clone();
3891 const FINGERPRINT_LEN: usize = 32;
3892 let db_folds = display_snapshot
3893 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3894 .map(|fold| {
3895 let start = fold
3896 .range
3897 .start
3898 .text_anchor_in(buffer_snapshot)
3899 .to_offset(buffer_snapshot);
3900 let end = fold
3901 .range
3902 .end
3903 .text_anchor_in(buffer_snapshot)
3904 .to_offset(buffer_snapshot);
3905
3906 // Extract fingerprints - content at fold boundaries for validation on restore
3907 // Both fingerprints must be INSIDE the fold to avoid capturing surrounding
3908 // content that might change independently.
3909 // start_fp: first min(32, fold_len) bytes of fold content
3910 // end_fp: last min(32, fold_len) bytes of fold content
3911 // Clip to character boundaries to handle multibyte UTF-8 characters.
3912 let fold_len = end - start;
3913 let start_fp_end = buffer_snapshot
3914 .clip_offset(start + std::cmp::min(FINGERPRINT_LEN, fold_len), Bias::Left);
3915 let start_fp: String = buffer_snapshot
3916 .text_for_range(start..start_fp_end)
3917 .collect();
3918 let end_fp_start = buffer_snapshot
3919 .clip_offset(end.saturating_sub(FINGERPRINT_LEN).max(start), Bias::Right);
3920 let end_fp: String = buffer_snapshot.text_for_range(end_fp_start..end).collect();
3921
3922 (start, end, start_fp, end_fp)
3923 })
3924 .collect::<Vec<_>>();
3925 let db = EditorDb::global(cx);
3926 self.serialize_folds = cx.background_spawn(async move {
3927 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3928 if db_folds.is_empty() {
3929 // No folds - delete any persisted folds for this file
3930 db.delete_file_folds(workspace_id, file_path)
3931 .await
3932 .with_context(|| format!("deleting file folds for workspace {workspace_id:?}"))
3933 .log_err();
3934 } else {
3935 db.save_file_folds(workspace_id, file_path, db_folds)
3936 .await
3937 .with_context(|| {
3938 format!("persisting file folds for workspace {workspace_id:?}")
3939 })
3940 .log_err();
3941 }
3942 });
3943 }
3944
3945 pub fn sync_selections(
3946 &mut self,
3947 other: Entity<Editor>,
3948 cx: &mut Context<Self>,
3949 ) -> gpui::Subscription {
3950 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3951 if !other_selections.is_empty() {
3952 self.selections
3953 .change_with(&self.display_snapshot(cx), |selections| {
3954 selections.select_anchors(other_selections);
3955 });
3956 }
3957
3958 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3959 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3960 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3961 if other_selections.is_empty() {
3962 return;
3963 }
3964 let snapshot = this.display_snapshot(cx);
3965 this.selections.change_with(&snapshot, |selections| {
3966 selections.select_anchors(other_selections);
3967 });
3968 }
3969 });
3970
3971 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3972 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3973 let these_selections = this.selections.disjoint_anchors().to_vec();
3974 if these_selections.is_empty() {
3975 return;
3976 }
3977 other.update(cx, |other_editor, cx| {
3978 let snapshot = other_editor.display_snapshot(cx);
3979 other_editor
3980 .selections
3981 .change_with(&snapshot, |selections| {
3982 selections.select_anchors(these_selections);
3983 })
3984 });
3985 }
3986 });
3987
3988 Subscription::join(other_subscription, this_subscription)
3989 }
3990
3991 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3992 if self.buffer().read(cx).is_singleton() {
3993 return;
3994 }
3995 let snapshot = self.buffer.read(cx).snapshot(cx);
3996 let buffer_ids: HashSet<BufferId> = self
3997 .selections
3998 .disjoint_anchor_ranges()
3999 .flat_map(|range| snapshot.buffer_ids_for_range(range))
4000 .collect();
4001 for buffer_id in buffer_ids {
4002 self.unfold_buffer(buffer_id, cx);
4003 }
4004 }
4005
4006 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
4007 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
4008 /// effects of selection change occur at the end of the transaction.
4009 pub fn change_selections<R>(
4010 &mut self,
4011 effects: SelectionEffects,
4012 window: &mut Window,
4013 cx: &mut Context<Self>,
4014 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
4015 ) -> R {
4016 let snapshot = self.display_snapshot(cx);
4017 if let Some(state) = &mut self.deferred_selection_effects_state {
4018 state.effects.scroll = effects.scroll.or(state.effects.scroll);
4019 state.effects.completions = effects.completions;
4020 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
4021 let (changed, result) = self.selections.change_with(&snapshot, change);
4022 state.changed |= changed;
4023 return result;
4024 }
4025 let mut state = DeferredSelectionEffectsState {
4026 changed: false,
4027 effects,
4028 old_cursor_position: self.selections.newest_anchor().head(),
4029 history_entry: SelectionHistoryEntry {
4030 selections: self.selections.disjoint_anchors_arc(),
4031 select_next_state: self.select_next_state.clone(),
4032 select_prev_state: self.select_prev_state.clone(),
4033 add_selections_state: self.add_selections_state.clone(),
4034 },
4035 };
4036 let (changed, result) = self.selections.change_with(&snapshot, change);
4037 state.changed = state.changed || changed;
4038 if self.defer_selection_effects {
4039 self.deferred_selection_effects_state = Some(state);
4040 } else {
4041 self.apply_selection_effects(state, window, cx);
4042 }
4043 result
4044 }
4045
4046 /// Defers the effects of selection change, so that the effects of multiple calls to
4047 /// `change_selections` are applied at the end. This way these intermediate states aren't added
4048 /// to selection history and the state of popovers based on selection position aren't
4049 /// erroneously updated.
4050 pub fn with_selection_effects_deferred<R>(
4051 &mut self,
4052 window: &mut Window,
4053 cx: &mut Context<Self>,
4054 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
4055 ) -> R {
4056 let already_deferred = self.defer_selection_effects;
4057 self.defer_selection_effects = true;
4058 let result = update(self, window, cx);
4059 if !already_deferred {
4060 self.defer_selection_effects = false;
4061 if let Some(state) = self.deferred_selection_effects_state.take() {
4062 self.apply_selection_effects(state, window, cx);
4063 }
4064 }
4065 result
4066 }
4067
4068 fn apply_selection_effects(
4069 &mut self,
4070 state: DeferredSelectionEffectsState,
4071 window: &mut Window,
4072 cx: &mut Context<Self>,
4073 ) {
4074 if state.changed {
4075 self.selection_history.push(state.history_entry);
4076
4077 if let Some(autoscroll) = state.effects.scroll {
4078 self.request_autoscroll(autoscroll, cx);
4079 }
4080
4081 let old_cursor_position = &state.old_cursor_position;
4082
4083 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
4084
4085 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
4086 self.show_signature_help_auto(window, cx);
4087 }
4088 }
4089 }
4090
4091 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
4092 where
4093 I: IntoIterator<Item = (Range<S>, T)>,
4094 S: ToOffset,
4095 T: Into<Arc<str>>,
4096 {
4097 if self.read_only(cx) {
4098 return;
4099 }
4100
4101 self.buffer
4102 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
4103 }
4104
4105 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
4106 where
4107 I: IntoIterator<Item = (Range<S>, T)>,
4108 S: ToOffset,
4109 T: Into<Arc<str>>,
4110 {
4111 if self.read_only(cx) {
4112 return;
4113 }
4114
4115 self.buffer.update(cx, |buffer, cx| {
4116 buffer.edit(edits, self.autoindent_mode.clone(), cx)
4117 });
4118 }
4119
4120 pub fn edit_with_block_indent<I, S, T>(
4121 &mut self,
4122 edits: I,
4123 original_indent_columns: Vec<Option<u32>>,
4124 cx: &mut Context<Self>,
4125 ) where
4126 I: IntoIterator<Item = (Range<S>, T)>,
4127 S: ToOffset,
4128 T: Into<Arc<str>>,
4129 {
4130 if self.read_only(cx) {
4131 return;
4132 }
4133
4134 self.buffer.update(cx, |buffer, cx| {
4135 buffer.edit(
4136 edits,
4137 Some(AutoindentMode::Block {
4138 original_indent_columns,
4139 }),
4140 cx,
4141 )
4142 });
4143 }
4144
4145 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
4146 self.hide_context_menu(window, cx);
4147
4148 match phase {
4149 SelectPhase::Begin {
4150 position,
4151 add,
4152 click_count,
4153 } => self.begin_selection(position, add, click_count, window, cx),
4154 SelectPhase::BeginColumnar {
4155 position,
4156 goal_column,
4157 reset,
4158 mode,
4159 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
4160 SelectPhase::Extend {
4161 position,
4162 click_count,
4163 } => self.extend_selection(position, click_count, window, cx),
4164 SelectPhase::Update {
4165 position,
4166 goal_column,
4167 scroll_delta,
4168 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
4169 SelectPhase::End => self.end_selection(window, cx),
4170 }
4171 }
4172
4173 fn extend_selection(
4174 &mut self,
4175 position: DisplayPoint,
4176 click_count: usize,
4177 window: &mut Window,
4178 cx: &mut Context<Self>,
4179 ) {
4180 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4181 let tail = self
4182 .selections
4183 .newest::<MultiBufferOffset>(&display_map)
4184 .tail();
4185 let click_count = click_count.max(match self.selections.select_mode() {
4186 SelectMode::Character => 1,
4187 SelectMode::Word(_) => 2,
4188 SelectMode::Line(_) => 3,
4189 SelectMode::All => 4,
4190 });
4191 self.begin_selection(position, false, click_count, window, cx);
4192
4193 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
4194
4195 let current_selection = match self.selections.select_mode() {
4196 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
4197 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
4198 };
4199
4200 let mut pending_selection = self
4201 .selections
4202 .pending_anchor()
4203 .cloned()
4204 .expect("extend_selection not called with pending selection");
4205
4206 if pending_selection
4207 .start
4208 .cmp(¤t_selection.start, display_map.buffer_snapshot())
4209 == Ordering::Greater
4210 {
4211 pending_selection.start = current_selection.start;
4212 }
4213 if pending_selection
4214 .end
4215 .cmp(¤t_selection.end, display_map.buffer_snapshot())
4216 == Ordering::Less
4217 {
4218 pending_selection.end = current_selection.end;
4219 pending_selection.reversed = true;
4220 }
4221
4222 let mut pending_mode = self.selections.pending_mode().unwrap();
4223 match &mut pending_mode {
4224 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
4225 _ => {}
4226 }
4227
4228 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
4229 SelectionEffects::scroll(Autoscroll::fit())
4230 } else {
4231 SelectionEffects::no_scroll()
4232 };
4233
4234 self.change_selections(effects, window, cx, |s| {
4235 s.set_pending(pending_selection.clone(), pending_mode);
4236 s.set_is_extending(true);
4237 });
4238 }
4239
4240 fn begin_selection(
4241 &mut self,
4242 position: DisplayPoint,
4243 add: bool,
4244 click_count: usize,
4245 window: &mut Window,
4246 cx: &mut Context<Self>,
4247 ) {
4248 if !self.focus_handle.is_focused(window) {
4249 self.last_focused_descendant = None;
4250 window.focus(&self.focus_handle, cx);
4251 }
4252
4253 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4254 let buffer = display_map.buffer_snapshot();
4255 let position = display_map.clip_point(position, Bias::Left);
4256
4257 let start;
4258 let end;
4259 let mode;
4260 let mut auto_scroll;
4261 match click_count {
4262 1 => {
4263 start = buffer.anchor_before(position.to_point(&display_map));
4264 end = start;
4265 mode = SelectMode::Character;
4266 auto_scroll = true;
4267 }
4268 2 => {
4269 let position = display_map
4270 .clip_point(position, Bias::Left)
4271 .to_offset(&display_map, Bias::Left);
4272 let (range, _) = buffer.surrounding_word(position, None);
4273 start = buffer.anchor_before(range.start);
4274 end = buffer.anchor_before(range.end);
4275 mode = SelectMode::Word(start..end);
4276 auto_scroll = true;
4277 }
4278 3 => {
4279 let position = display_map
4280 .clip_point(position, Bias::Left)
4281 .to_point(&display_map);
4282 let line_start = display_map.prev_line_boundary(position).0;
4283 let next_line_start = buffer.clip_point(
4284 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4285 Bias::Left,
4286 );
4287 start = buffer.anchor_before(line_start);
4288 end = buffer.anchor_before(next_line_start);
4289 mode = SelectMode::Line(start..end);
4290 auto_scroll = true;
4291 }
4292 _ => {
4293 start = buffer.anchor_before(MultiBufferOffset(0));
4294 end = buffer.anchor_before(buffer.len());
4295 mode = SelectMode::All;
4296 auto_scroll = false;
4297 }
4298 }
4299 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
4300
4301 let point_to_delete: Option<usize> = {
4302 let selected_points: Vec<Selection<Point>> =
4303 self.selections.disjoint_in_range(start..end, &display_map);
4304
4305 if !add || click_count > 1 {
4306 None
4307 } else if !selected_points.is_empty() {
4308 Some(selected_points[0].id)
4309 } else {
4310 let clicked_point_already_selected =
4311 self.selections.disjoint_anchors().iter().find(|selection| {
4312 selection.start.to_point(buffer) == start.to_point(buffer)
4313 || selection.end.to_point(buffer) == end.to_point(buffer)
4314 });
4315
4316 clicked_point_already_selected.map(|selection| selection.id)
4317 }
4318 };
4319
4320 let selections_count = self.selections.count();
4321 let effects = if auto_scroll {
4322 SelectionEffects::default()
4323 } else {
4324 SelectionEffects::no_scroll()
4325 };
4326
4327 self.change_selections(effects, window, cx, |s| {
4328 if let Some(point_to_delete) = point_to_delete {
4329 s.delete(point_to_delete);
4330
4331 if selections_count == 1 {
4332 s.set_pending_anchor_range(start..end, mode);
4333 }
4334 } else {
4335 if !add {
4336 s.clear_disjoint();
4337 }
4338
4339 s.set_pending_anchor_range(start..end, mode);
4340 }
4341 });
4342 }
4343
4344 fn begin_columnar_selection(
4345 &mut self,
4346 position: DisplayPoint,
4347 goal_column: u32,
4348 reset: bool,
4349 mode: ColumnarMode,
4350 window: &mut Window,
4351 cx: &mut Context<Self>,
4352 ) {
4353 if !self.focus_handle.is_focused(window) {
4354 self.last_focused_descendant = None;
4355 window.focus(&self.focus_handle, cx);
4356 }
4357
4358 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4359
4360 if reset {
4361 let pointer_position = display_map
4362 .buffer_snapshot()
4363 .anchor_before(position.to_point(&display_map));
4364
4365 self.change_selections(
4366 SelectionEffects::scroll(Autoscroll::newest()),
4367 window,
4368 cx,
4369 |s| {
4370 s.clear_disjoint();
4371 s.set_pending_anchor_range(
4372 pointer_position..pointer_position,
4373 SelectMode::Character,
4374 );
4375 },
4376 );
4377 };
4378
4379 let tail = self.selections.newest::<Point>(&display_map).tail();
4380 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
4381 self.columnar_selection_state = match mode {
4382 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
4383 selection_tail: selection_anchor,
4384 display_point: if reset {
4385 if position.column() != goal_column {
4386 Some(DisplayPoint::new(position.row(), goal_column))
4387 } else {
4388 None
4389 }
4390 } else {
4391 None
4392 },
4393 }),
4394 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
4395 selection_tail: selection_anchor,
4396 }),
4397 };
4398
4399 if !reset {
4400 self.select_columns(position, goal_column, &display_map, window, cx);
4401 }
4402 }
4403
4404 fn update_selection(
4405 &mut self,
4406 position: DisplayPoint,
4407 goal_column: u32,
4408 scroll_delta: gpui::Point<f32>,
4409 window: &mut Window,
4410 cx: &mut Context<Self>,
4411 ) {
4412 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4413
4414 if self.columnar_selection_state.is_some() {
4415 self.select_columns(position, goal_column, &display_map, window, cx);
4416 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
4417 let buffer = display_map.buffer_snapshot();
4418 let head;
4419 let tail;
4420 let mode = self.selections.pending_mode().unwrap();
4421 match &mode {
4422 SelectMode::Character => {
4423 head = position.to_point(&display_map);
4424 tail = pending.tail().to_point(buffer);
4425 }
4426 SelectMode::Word(original_range) => {
4427 let offset = display_map
4428 .clip_point(position, Bias::Left)
4429 .to_offset(&display_map, Bias::Left);
4430 let original_range = original_range.to_offset(buffer);
4431
4432 let head_offset = if buffer.is_inside_word(offset, None)
4433 || original_range.contains(&offset)
4434 {
4435 let (word_range, _) = buffer.surrounding_word(offset, None);
4436 if word_range.start < original_range.start {
4437 word_range.start
4438 } else {
4439 word_range.end
4440 }
4441 } else {
4442 offset
4443 };
4444
4445 head = head_offset.to_point(buffer);
4446 if head_offset <= original_range.start {
4447 tail = original_range.end.to_point(buffer);
4448 } else {
4449 tail = original_range.start.to_point(buffer);
4450 }
4451 }
4452 SelectMode::Line(original_range) => {
4453 let original_range = original_range.to_point(display_map.buffer_snapshot());
4454
4455 let position = display_map
4456 .clip_point(position, Bias::Left)
4457 .to_point(&display_map);
4458 let line_start = display_map.prev_line_boundary(position).0;
4459 let next_line_start = buffer.clip_point(
4460 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4461 Bias::Left,
4462 );
4463
4464 if line_start < original_range.start {
4465 head = line_start
4466 } else {
4467 head = next_line_start
4468 }
4469
4470 if head <= original_range.start {
4471 tail = original_range.end;
4472 } else {
4473 tail = original_range.start;
4474 }
4475 }
4476 SelectMode::All => {
4477 return;
4478 }
4479 };
4480
4481 if head < tail {
4482 pending.start = buffer.anchor_before(head);
4483 pending.end = buffer.anchor_before(tail);
4484 pending.reversed = true;
4485 } else {
4486 pending.start = buffer.anchor_before(tail);
4487 pending.end = buffer.anchor_before(head);
4488 pending.reversed = false;
4489 }
4490
4491 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4492 s.set_pending(pending.clone(), mode);
4493 });
4494 } else {
4495 log::error!("update_selection dispatched with no pending selection");
4496 return;
4497 }
4498
4499 self.apply_scroll_delta(scroll_delta, window, cx);
4500 cx.notify();
4501 }
4502
4503 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4504 self.columnar_selection_state.take();
4505 if let Some(pending_mode) = self.selections.pending_mode() {
4506 let selections = self
4507 .selections
4508 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4509 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4510 s.select(selections);
4511 s.clear_pending();
4512 if s.is_extending() {
4513 s.set_is_extending(false);
4514 } else {
4515 s.set_select_mode(pending_mode);
4516 }
4517 });
4518 }
4519 }
4520
4521 fn select_columns(
4522 &mut self,
4523 head: DisplayPoint,
4524 goal_column: u32,
4525 display_map: &DisplaySnapshot,
4526 window: &mut Window,
4527 cx: &mut Context<Self>,
4528 ) {
4529 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4530 return;
4531 };
4532
4533 let tail = match columnar_state {
4534 ColumnarSelectionState::FromMouse {
4535 selection_tail,
4536 display_point,
4537 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4538 ColumnarSelectionState::FromSelection { selection_tail } => {
4539 selection_tail.to_display_point(display_map)
4540 }
4541 };
4542
4543 let start_row = cmp::min(tail.row(), head.row());
4544 let end_row = cmp::max(tail.row(), head.row());
4545 let start_column = cmp::min(tail.column(), goal_column);
4546 let end_column = cmp::max(tail.column(), goal_column);
4547 let reversed = start_column < tail.column();
4548
4549 let selection_ranges = (start_row.0..=end_row.0)
4550 .map(DisplayRow)
4551 .filter_map(|row| {
4552 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4553 || start_column <= display_map.line_len(row))
4554 && !display_map.is_block_line(row)
4555 {
4556 let start = display_map
4557 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4558 .to_point(display_map);
4559 let end = display_map
4560 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4561 .to_point(display_map);
4562 if reversed {
4563 Some(end..start)
4564 } else {
4565 Some(start..end)
4566 }
4567 } else {
4568 None
4569 }
4570 })
4571 .collect::<Vec<_>>();
4572 if selection_ranges.is_empty() {
4573 return;
4574 }
4575
4576 let ranges = match columnar_state {
4577 ColumnarSelectionState::FromMouse { .. } => {
4578 let mut non_empty_ranges = selection_ranges
4579 .iter()
4580 .filter(|selection_range| selection_range.start != selection_range.end)
4581 .peekable();
4582 if non_empty_ranges.peek().is_some() {
4583 non_empty_ranges.cloned().collect()
4584 } else {
4585 selection_ranges
4586 }
4587 }
4588 _ => selection_ranges,
4589 };
4590
4591 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4592 s.select_ranges(ranges);
4593 });
4594 cx.notify();
4595 }
4596
4597 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4598 self.selections
4599 .all_adjusted(snapshot)
4600 .iter()
4601 .any(|selection| !selection.is_empty())
4602 }
4603
4604 pub fn has_pending_nonempty_selection(&self) -> bool {
4605 let pending_nonempty_selection = match self.selections.pending_anchor() {
4606 Some(Selection { start, end, .. }) => start != end,
4607 None => false,
4608 };
4609
4610 pending_nonempty_selection
4611 || (self.columnar_selection_state.is_some()
4612 && self.selections.disjoint_anchors().len() > 1)
4613 }
4614
4615 pub fn has_pending_selection(&self) -> bool {
4616 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4617 }
4618
4619 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4620 self.selection_mark_mode = false;
4621 self.selection_drag_state = SelectionDragState::None;
4622
4623 if self.dismiss_menus_and_popups(true, window, cx) {
4624 cx.notify();
4625 return;
4626 }
4627 if self.clear_expanded_diff_hunks(cx) {
4628 cx.notify();
4629 return;
4630 }
4631 if self.show_git_blame_gutter {
4632 self.show_git_blame_gutter = false;
4633 cx.notify();
4634 return;
4635 }
4636
4637 if self.mode.is_full()
4638 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4639 {
4640 cx.notify();
4641 return;
4642 }
4643
4644 cx.propagate();
4645 }
4646
4647 pub fn dismiss_menus_and_popups(
4648 &mut self,
4649 is_user_requested: bool,
4650 window: &mut Window,
4651 cx: &mut Context<Self>,
4652 ) -> bool {
4653 let mut dismissed = false;
4654
4655 dismissed |= self.take_rename(false, window, cx).is_some();
4656 dismissed |= self.hide_blame_popover(true, cx);
4657 dismissed |= hide_hover(self, cx);
4658 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4659 dismissed |= self.hide_context_menu(window, cx).is_some();
4660 dismissed |= self.mouse_context_menu.take().is_some();
4661 dismissed |= is_user_requested
4662 && self.discard_edit_prediction(EditPredictionDiscardReason::Rejected, cx);
4663 dismissed |= self.snippet_stack.pop().is_some();
4664 if self.diff_review_drag_state.is_some() {
4665 self.cancel_diff_review_drag(cx);
4666 dismissed = true;
4667 }
4668 if !self.diff_review_overlays.is_empty() {
4669 self.dismiss_all_diff_review_overlays(cx);
4670 dismissed = true;
4671 }
4672
4673 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4674 self.dismiss_diagnostics(cx);
4675 dismissed = true;
4676 }
4677
4678 dismissed
4679 }
4680
4681 fn linked_editing_ranges_for(
4682 &self,
4683 query_range: Range<text::Anchor>,
4684 cx: &App,
4685 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4686 use text::ToOffset as TO;
4687
4688 if self.linked_edit_ranges.is_empty() {
4689 return None;
4690 }
4691 if query_range.start.buffer_id != query_range.end.buffer_id {
4692 return None;
4693 };
4694 let multibuffer_snapshot = self.buffer.read(cx).snapshot(cx);
4695 let buffer = self.buffer.read(cx).buffer(query_range.end.buffer_id)?;
4696 let buffer_snapshot = buffer.read(cx).snapshot();
4697 let (base_range, linked_ranges) = self.linked_edit_ranges.get(
4698 buffer_snapshot.remote_id(),
4699 query_range.clone(),
4700 &buffer_snapshot,
4701 )?;
4702 // find offset from the start of current range to current cursor position
4703 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4704
4705 let start_offset = TO::to_offset(&query_range.start, &buffer_snapshot);
4706 let start_difference = start_offset - start_byte_offset;
4707 let end_offset = TO::to_offset(&query_range.end, &buffer_snapshot);
4708 let end_difference = end_offset - start_byte_offset;
4709
4710 // Current range has associated linked ranges.
4711 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4712 for range in linked_ranges.iter() {
4713 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4714 let end_offset = start_offset + end_difference;
4715 let start_offset = start_offset + start_difference;
4716 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4717 continue;
4718 }
4719 if self.selections.disjoint_anchor_ranges().any(|s| {
4720 let Some((selection_start, _)) =
4721 multibuffer_snapshot.anchor_to_buffer_anchor(s.start)
4722 else {
4723 return false;
4724 };
4725 let Some((selection_end, _)) = multibuffer_snapshot.anchor_to_buffer_anchor(s.end)
4726 else {
4727 return false;
4728 };
4729 if selection_start.buffer_id != query_range.start.buffer_id
4730 || selection_end.buffer_id != query_range.end.buffer_id
4731 {
4732 return false;
4733 }
4734 TO::to_offset(&selection_start, &buffer_snapshot) <= end_offset
4735 && TO::to_offset(&selection_end, &buffer_snapshot) >= start_offset
4736 }) {
4737 continue;
4738 }
4739 let start = buffer_snapshot.anchor_after(start_offset);
4740 let end = buffer_snapshot.anchor_after(end_offset);
4741 linked_edits
4742 .entry(buffer.clone())
4743 .or_default()
4744 .push(start..end);
4745 }
4746 Some(linked_edits)
4747 }
4748
4749 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4750 let text: Arc<str> = text.into();
4751
4752 if self.read_only(cx) {
4753 return;
4754 }
4755
4756 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4757
4758 self.unfold_buffers_with_selections(cx);
4759
4760 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4761 let mut bracket_inserted = false;
4762 let mut edits = Vec::new();
4763 let mut linked_edits = LinkedEdits::new();
4764 let mut new_selections = Vec::with_capacity(selections.len());
4765 let mut new_autoclose_regions = Vec::new();
4766 let snapshot = self.buffer.read(cx).read(cx);
4767 let mut clear_linked_edit_ranges = false;
4768 let mut all_selections_read_only = true;
4769 let mut has_adjacent_edits = false;
4770 let mut in_adjacent_group = false;
4771
4772 let mut regions = self
4773 .selections_with_autoclose_regions(selections, &snapshot)
4774 .peekable();
4775
4776 while let Some((selection, autoclose_region)) = regions.next() {
4777 if snapshot
4778 .point_to_buffer_point(selection.head())
4779 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4780 {
4781 continue;
4782 }
4783 if snapshot
4784 .point_to_buffer_point(selection.tail())
4785 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4786 {
4787 // note, ideally we'd clip the tail to the closest writeable region towards the head
4788 continue;
4789 }
4790 all_selections_read_only = false;
4791
4792 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4793 // Determine if the inserted text matches the opening or closing
4794 // bracket of any of this language's bracket pairs.
4795 let mut bracket_pair = None;
4796 let mut is_bracket_pair_start = false;
4797 let mut is_bracket_pair_end = false;
4798 if !text.is_empty() {
4799 let mut bracket_pair_matching_end = None;
4800 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4801 // and they are removing the character that triggered IME popup.
4802 for (pair, enabled) in scope.brackets() {
4803 if !pair.close && !pair.surround {
4804 continue;
4805 }
4806
4807 if enabled && pair.start.ends_with(text.as_ref()) {
4808 let prefix_len = pair.start.len() - text.len();
4809 let preceding_text_matches_prefix = prefix_len == 0
4810 || (selection.start.column >= (prefix_len as u32)
4811 && snapshot.contains_str_at(
4812 Point::new(
4813 selection.start.row,
4814 selection.start.column - (prefix_len as u32),
4815 ),
4816 &pair.start[..prefix_len],
4817 ));
4818 if preceding_text_matches_prefix {
4819 bracket_pair = Some(pair.clone());
4820 is_bracket_pair_start = true;
4821 break;
4822 }
4823 }
4824 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4825 {
4826 // take first bracket pair matching end, but don't break in case a later bracket
4827 // pair matches start
4828 bracket_pair_matching_end = Some(pair.clone());
4829 }
4830 }
4831 if let Some(end) = bracket_pair_matching_end
4832 && bracket_pair.is_none()
4833 {
4834 bracket_pair = Some(end);
4835 is_bracket_pair_end = true;
4836 }
4837 }
4838
4839 if let Some(bracket_pair) = bracket_pair {
4840 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4841 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4842 let auto_surround =
4843 self.use_auto_surround && snapshot_settings.use_auto_surround;
4844 if selection.is_empty() {
4845 if is_bracket_pair_start {
4846 // If the inserted text is a suffix of an opening bracket and the
4847 // selection is preceded by the rest of the opening bracket, then
4848 // insert the closing bracket.
4849 let following_text_allows_autoclose = snapshot
4850 .chars_at(selection.start)
4851 .next()
4852 .is_none_or(|c| scope.should_autoclose_before(c));
4853
4854 let preceding_text_allows_autoclose = selection.start.column == 0
4855 || snapshot
4856 .reversed_chars_at(selection.start)
4857 .next()
4858 .is_none_or(|c| {
4859 bracket_pair.start != bracket_pair.end
4860 || !snapshot
4861 .char_classifier_at(selection.start)
4862 .is_word(c)
4863 });
4864
4865 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4866 && bracket_pair.start.len() == 1
4867 {
4868 let target = bracket_pair.start.chars().next().unwrap();
4869 let mut byte_offset = 0u32;
4870 let current_line_count = snapshot
4871 .reversed_chars_at(selection.start)
4872 .take_while(|&c| c != '\n')
4873 .filter(|c| {
4874 byte_offset += c.len_utf8() as u32;
4875 if *c != target {
4876 return false;
4877 }
4878
4879 let point = Point::new(
4880 selection.start.row,
4881 selection.start.column.saturating_sub(byte_offset),
4882 );
4883
4884 let is_enabled = snapshot
4885 .language_scope_at(point)
4886 .and_then(|scope| {
4887 scope
4888 .brackets()
4889 .find(|(pair, _)| {
4890 pair.start == bracket_pair.start
4891 })
4892 .map(|(_, enabled)| enabled)
4893 })
4894 .unwrap_or(true);
4895
4896 let is_delimiter = snapshot
4897 .language_scope_at(Point::new(
4898 point.row,
4899 point.column + 1,
4900 ))
4901 .and_then(|scope| {
4902 scope
4903 .brackets()
4904 .find(|(pair, _)| {
4905 pair.start == bracket_pair.start
4906 })
4907 .map(|(_, enabled)| !enabled)
4908 })
4909 .unwrap_or(false);
4910
4911 is_enabled && !is_delimiter
4912 })
4913 .count();
4914 current_line_count % 2 == 1
4915 } else {
4916 false
4917 };
4918
4919 if autoclose
4920 && bracket_pair.close
4921 && following_text_allows_autoclose
4922 && preceding_text_allows_autoclose
4923 && !is_closing_quote
4924 {
4925 let anchor = snapshot.anchor_before(selection.end);
4926 new_selections.push((selection.map(|_| anchor), text.len()));
4927 new_autoclose_regions.push((
4928 anchor,
4929 text.len(),
4930 selection.id,
4931 bracket_pair.clone(),
4932 ));
4933 edits.push((
4934 selection.range(),
4935 format!("{}{}", text, bracket_pair.end).into(),
4936 ));
4937 bracket_inserted = true;
4938 continue;
4939 }
4940 }
4941
4942 if let Some(region) = autoclose_region {
4943 // If the selection is followed by an auto-inserted closing bracket,
4944 // then don't insert that closing bracket again; just move the selection
4945 // past the closing bracket.
4946 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4947 && text.as_ref() == region.pair.end.as_str()
4948 && snapshot.contains_str_at(region.range.end, text.as_ref());
4949 if should_skip {
4950 let anchor = snapshot.anchor_after(selection.end);
4951 new_selections
4952 .push((selection.map(|_| anchor), region.pair.end.len()));
4953 continue;
4954 }
4955 }
4956
4957 let always_treat_brackets_as_autoclosed = snapshot
4958 .language_settings_at(selection.start, cx)
4959 .always_treat_brackets_as_autoclosed;
4960 if always_treat_brackets_as_autoclosed
4961 && is_bracket_pair_end
4962 && snapshot.contains_str_at(selection.end, text.as_ref())
4963 {
4964 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4965 // and the inserted text is a closing bracket and the selection is followed
4966 // by the closing bracket then move the selection past the closing bracket.
4967 let anchor = snapshot.anchor_after(selection.end);
4968 new_selections.push((selection.map(|_| anchor), text.len()));
4969 continue;
4970 }
4971 }
4972 // If an opening bracket is 1 character long and is typed while
4973 // text is selected, then surround that text with the bracket pair.
4974 else if auto_surround
4975 && bracket_pair.surround
4976 && is_bracket_pair_start
4977 && bracket_pair.start.chars().count() == 1
4978 {
4979 edits.push((selection.start..selection.start, text.clone()));
4980 edits.push((
4981 selection.end..selection.end,
4982 bracket_pair.end.as_str().into(),
4983 ));
4984 bracket_inserted = true;
4985 new_selections.push((
4986 Selection {
4987 id: selection.id,
4988 start: snapshot.anchor_after(selection.start),
4989 end: snapshot.anchor_before(selection.end),
4990 reversed: selection.reversed,
4991 goal: selection.goal,
4992 },
4993 0,
4994 ));
4995 continue;
4996 }
4997 }
4998 }
4999
5000 if self.auto_replace_emoji_shortcode
5001 && selection.is_empty()
5002 && text.as_ref().ends_with(':')
5003 && let Some(possible_emoji_short_code) =
5004 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
5005 && !possible_emoji_short_code.is_empty()
5006 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
5007 {
5008 let emoji_shortcode_start = Point::new(
5009 selection.start.row,
5010 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
5011 );
5012
5013 // Remove shortcode from buffer
5014 edits.push((
5015 emoji_shortcode_start..selection.start,
5016 "".to_string().into(),
5017 ));
5018 new_selections.push((
5019 Selection {
5020 id: selection.id,
5021 start: snapshot.anchor_after(emoji_shortcode_start),
5022 end: snapshot.anchor_before(selection.start),
5023 reversed: selection.reversed,
5024 goal: selection.goal,
5025 },
5026 0,
5027 ));
5028
5029 // Insert emoji
5030 let selection_start_anchor = snapshot.anchor_after(selection.start);
5031 new_selections.push((selection.map(|_| selection_start_anchor), 0));
5032 edits.push((selection.start..selection.end, emoji.to_string().into()));
5033
5034 continue;
5035 }
5036
5037 let next_is_adjacent = regions
5038 .peek()
5039 .is_some_and(|(next, _)| selection.end == next.start);
5040
5041 // If not handling any auto-close operation, then just replace the selected
5042 // text with the given input and move the selection to the end of the
5043 // newly inserted text.
5044 let anchor = if in_adjacent_group || next_is_adjacent {
5045 // After edits the right bias would shift those anchor to the next visible fragment
5046 // but we want to resolve to the previous one
5047 snapshot.anchor_before(selection.end)
5048 } else {
5049 snapshot.anchor_after(selection.end)
5050 };
5051
5052 if !self.linked_edit_ranges.is_empty() {
5053 let start_anchor = snapshot.anchor_before(selection.start);
5054 let classifier = snapshot
5055 .char_classifier_at(start_anchor)
5056 .scope_context(Some(CharScopeContext::LinkedEdit));
5057
5058 if let Some((_, anchor_range)) =
5059 snapshot.anchor_range_to_buffer_anchor_range(start_anchor..anchor)
5060 {
5061 let is_word_char = text
5062 .chars()
5063 .next()
5064 .is_none_or(|char| classifier.is_word(char));
5065
5066 let is_dot = text.as_ref() == ".";
5067 let should_apply_linked_edit = is_word_char || is_dot;
5068
5069 if should_apply_linked_edit {
5070 linked_edits.push(&self, anchor_range, text.clone(), cx);
5071 } else {
5072 clear_linked_edit_ranges = true;
5073 }
5074 }
5075 }
5076
5077 new_selections.push((selection.map(|_| anchor), 0));
5078 edits.push((selection.start..selection.end, text.clone()));
5079
5080 has_adjacent_edits |= next_is_adjacent;
5081 in_adjacent_group = next_is_adjacent;
5082 }
5083
5084 if all_selections_read_only {
5085 return;
5086 }
5087
5088 drop(regions);
5089 drop(snapshot);
5090
5091 self.transact(window, cx, |this, window, cx| {
5092 if clear_linked_edit_ranges {
5093 this.linked_edit_ranges.clear();
5094 }
5095 let initial_buffer_versions =
5096 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
5097
5098 this.buffer.update(cx, |buffer, cx| {
5099 if has_adjacent_edits {
5100 buffer.edit_non_coalesce(edits, this.autoindent_mode.clone(), cx);
5101 } else {
5102 buffer.edit(edits, this.autoindent_mode.clone(), cx);
5103 }
5104 });
5105 linked_edits.apply(cx);
5106 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
5107 let new_selection_deltas = new_selections.iter().map(|e| e.1);
5108 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
5109 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
5110 new_anchor_selections,
5111 &map,
5112 )
5113 .zip(new_selection_deltas)
5114 .map(|(selection, delta)| Selection {
5115 id: selection.id,
5116 start: selection.start + delta,
5117 end: selection.end + delta,
5118 reversed: selection.reversed,
5119 goal: SelectionGoal::None,
5120 })
5121 .collect::<Vec<_>>();
5122
5123 let mut i = 0;
5124 for (position, delta, selection_id, pair) in new_autoclose_regions {
5125 let position = position.to_offset(map.buffer_snapshot()) + delta;
5126 let start = map.buffer_snapshot().anchor_before(position);
5127 let end = map.buffer_snapshot().anchor_after(position);
5128 while let Some(existing_state) = this.autoclose_regions.get(i) {
5129 match existing_state
5130 .range
5131 .start
5132 .cmp(&start, map.buffer_snapshot())
5133 {
5134 Ordering::Less => i += 1,
5135 Ordering::Greater => break,
5136 Ordering::Equal => {
5137 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
5138 Ordering::Less => i += 1,
5139 Ordering::Equal => break,
5140 Ordering::Greater => break,
5141 }
5142 }
5143 }
5144 }
5145 this.autoclose_regions.insert(
5146 i,
5147 AutocloseRegion {
5148 selection_id,
5149 range: start..end,
5150 pair,
5151 },
5152 );
5153 }
5154
5155 let had_active_edit_prediction = this.has_active_edit_prediction();
5156 this.change_selections(
5157 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
5158 window,
5159 cx,
5160 |s| s.select(new_selections),
5161 );
5162
5163 if !bracket_inserted
5164 && let Some(on_type_format_task) =
5165 this.trigger_on_type_formatting(text.to_string(), window, cx)
5166 {
5167 on_type_format_task.detach_and_log_err(cx);
5168 }
5169
5170 let editor_settings = EditorSettings::get_global(cx);
5171 if bracket_inserted
5172 && (editor_settings.auto_signature_help
5173 || editor_settings.show_signature_help_after_edits)
5174 {
5175 this.show_signature_help(&ShowSignatureHelp, window, cx);
5176 }
5177
5178 let trigger_in_words =
5179 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
5180 if this.hard_wrap.is_some() {
5181 let latest: Range<Point> = this.selections.newest(&map).range();
5182 if latest.is_empty()
5183 && this
5184 .buffer()
5185 .read(cx)
5186 .snapshot(cx)
5187 .line_len(MultiBufferRow(latest.start.row))
5188 == latest.start.column
5189 {
5190 this.rewrap_impl(
5191 RewrapOptions {
5192 override_language_settings: true,
5193 preserve_existing_whitespace: true,
5194 line_length: None,
5195 },
5196 cx,
5197 )
5198 }
5199 }
5200 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
5201 refresh_linked_ranges(this, window, cx);
5202 this.refresh_edit_prediction(true, false, window, cx);
5203 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
5204 });
5205 }
5206
5207 fn find_possible_emoji_shortcode_at_position(
5208 snapshot: &MultiBufferSnapshot,
5209 position: Point,
5210 ) -> Option<String> {
5211 let mut chars = Vec::new();
5212 let mut found_colon = false;
5213 for char in snapshot.reversed_chars_at(position).take(100) {
5214 // Found a possible emoji shortcode in the middle of the buffer
5215 if found_colon {
5216 if char.is_whitespace() {
5217 chars.reverse();
5218 return Some(chars.iter().collect());
5219 }
5220 // If the previous character is not a whitespace, we are in the middle of a word
5221 // and we only want to complete the shortcode if the word is made up of other emojis
5222 let mut containing_word = String::new();
5223 for ch in snapshot
5224 .reversed_chars_at(position)
5225 .skip(chars.len() + 1)
5226 .take(100)
5227 {
5228 if ch.is_whitespace() {
5229 break;
5230 }
5231 containing_word.push(ch);
5232 }
5233 let containing_word = containing_word.chars().rev().collect::<String>();
5234 if util::word_consists_of_emojis(containing_word.as_str()) {
5235 chars.reverse();
5236 return Some(chars.iter().collect());
5237 }
5238 }
5239
5240 if char.is_whitespace() || !char.is_ascii() {
5241 return None;
5242 }
5243 if char == ':' {
5244 found_colon = true;
5245 } else {
5246 chars.push(char);
5247 }
5248 }
5249 // Found a possible emoji shortcode at the beginning of the buffer
5250 chars.reverse();
5251 Some(chars.iter().collect())
5252 }
5253
5254 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
5255 if self.read_only(cx) {
5256 return;
5257 }
5258
5259 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5260 self.transact(window, cx, |this, window, cx| {
5261 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
5262 let selections = this
5263 .selections
5264 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
5265 let multi_buffer = this.buffer.read(cx);
5266 let buffer = multi_buffer.snapshot(cx);
5267 selections
5268 .iter()
5269 .map(|selection| {
5270 let start_point = selection.start.to_point(&buffer);
5271 let mut existing_indent =
5272 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
5273 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
5274 let start = selection.start;
5275 let end = selection.end;
5276 let selection_is_empty = start == end;
5277 let language_scope = buffer.language_scope_at(start);
5278 let (delimiter, newline_config) = if let Some(language) = &language_scope {
5279 let needs_extra_newline = NewlineConfig::insert_extra_newline_brackets(
5280 &buffer,
5281 start..end,
5282 language,
5283 )
5284 || NewlineConfig::insert_extra_newline_tree_sitter(
5285 &buffer,
5286 start..end,
5287 );
5288
5289 let mut newline_config = NewlineConfig::Newline {
5290 additional_indent: IndentSize::spaces(0),
5291 extra_line_additional_indent: if needs_extra_newline {
5292 Some(IndentSize::spaces(0))
5293 } else {
5294 None
5295 },
5296 prevent_auto_indent: false,
5297 };
5298
5299 let comment_delimiter = maybe!({
5300 if !selection_is_empty {
5301 return None;
5302 }
5303
5304 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5305 return None;
5306 }
5307
5308 return comment_delimiter_for_newline(
5309 &start_point,
5310 &buffer,
5311 language,
5312 );
5313 });
5314
5315 let doc_delimiter = maybe!({
5316 if !selection_is_empty {
5317 return None;
5318 }
5319
5320 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5321 return None;
5322 }
5323
5324 return documentation_delimiter_for_newline(
5325 &start_point,
5326 &buffer,
5327 language,
5328 &mut newline_config,
5329 );
5330 });
5331
5332 let list_delimiter = maybe!({
5333 if !selection_is_empty {
5334 return None;
5335 }
5336
5337 if !multi_buffer.language_settings(cx).extend_list_on_newline {
5338 return None;
5339 }
5340
5341 return list_delimiter_for_newline(
5342 &start_point,
5343 &buffer,
5344 language,
5345 &mut newline_config,
5346 );
5347 });
5348
5349 (
5350 comment_delimiter.or(doc_delimiter).or(list_delimiter),
5351 newline_config,
5352 )
5353 } else {
5354 (
5355 None,
5356 NewlineConfig::Newline {
5357 additional_indent: IndentSize::spaces(0),
5358 extra_line_additional_indent: None,
5359 prevent_auto_indent: false,
5360 },
5361 )
5362 };
5363
5364 let (edit_start, new_text, prevent_auto_indent) = match &newline_config {
5365 NewlineConfig::ClearCurrentLine => {
5366 let row_start =
5367 buffer.point_to_offset(Point::new(start_point.row, 0));
5368 (row_start, String::new(), false)
5369 }
5370 NewlineConfig::UnindentCurrentLine { continuation } => {
5371 let row_start =
5372 buffer.point_to_offset(Point::new(start_point.row, 0));
5373 let tab_size = buffer.language_settings_at(start, cx).tab_size;
5374 let tab_size_indent = IndentSize::spaces(tab_size.get());
5375 let reduced_indent =
5376 existing_indent.with_delta(Ordering::Less, tab_size_indent);
5377 let mut new_text = String::new();
5378 new_text.extend(reduced_indent.chars());
5379 new_text.push_str(continuation);
5380 (row_start, new_text, true)
5381 }
5382 NewlineConfig::Newline {
5383 additional_indent,
5384 extra_line_additional_indent,
5385 prevent_auto_indent,
5386 } => {
5387 let auto_indent_mode =
5388 buffer.language_settings_at(start, cx).auto_indent;
5389 let preserve_indent =
5390 auto_indent_mode != language::AutoIndentMode::None;
5391 let apply_syntax_indent =
5392 auto_indent_mode == language::AutoIndentMode::SyntaxAware;
5393 let capacity_for_delimiter =
5394 delimiter.as_deref().map(str::len).unwrap_or_default();
5395 let existing_indent_len = if preserve_indent {
5396 existing_indent.len as usize
5397 } else {
5398 0
5399 };
5400 let extra_line_len = extra_line_additional_indent
5401 .map(|i| 1 + existing_indent_len + i.len as usize)
5402 .unwrap_or(0);
5403 let mut new_text = String::with_capacity(
5404 1 + capacity_for_delimiter
5405 + existing_indent_len
5406 + additional_indent.len as usize
5407 + extra_line_len,
5408 );
5409 new_text.push('\n');
5410 if preserve_indent {
5411 new_text.extend(existing_indent.chars());
5412 }
5413 new_text.extend(additional_indent.chars());
5414 if let Some(delimiter) = &delimiter {
5415 new_text.push_str(delimiter);
5416 }
5417 if let Some(extra_indent) = extra_line_additional_indent {
5418 new_text.push('\n');
5419 if preserve_indent {
5420 new_text.extend(existing_indent.chars());
5421 }
5422 new_text.extend(extra_indent.chars());
5423 }
5424 (
5425 start,
5426 new_text,
5427 *prevent_auto_indent || !apply_syntax_indent,
5428 )
5429 }
5430 };
5431
5432 let anchor = buffer.anchor_after(end);
5433 let new_selection = selection.map(|_| anchor);
5434 (
5435 ((edit_start..end, new_text), prevent_auto_indent),
5436 (newline_config.has_extra_line(), new_selection),
5437 )
5438 })
5439 .unzip()
5440 };
5441
5442 let mut auto_indent_edits = Vec::new();
5443 let mut edits = Vec::new();
5444 for (edit, prevent_auto_indent) in edits_with_flags {
5445 if prevent_auto_indent {
5446 edits.push(edit);
5447 } else {
5448 auto_indent_edits.push(edit);
5449 }
5450 }
5451 if !edits.is_empty() {
5452 this.edit(edits, cx);
5453 }
5454 if !auto_indent_edits.is_empty() {
5455 this.edit_with_autoindent(auto_indent_edits, cx);
5456 }
5457
5458 let buffer = this.buffer.read(cx).snapshot(cx);
5459 let new_selections = selection_info
5460 .into_iter()
5461 .map(|(extra_newline_inserted, new_selection)| {
5462 let mut cursor = new_selection.end.to_point(&buffer);
5463 if extra_newline_inserted {
5464 cursor.row -= 1;
5465 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
5466 }
5467 new_selection.map(|_| cursor)
5468 })
5469 .collect();
5470
5471 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
5472 this.refresh_edit_prediction(true, false, window, cx);
5473 if let Some(task) = this.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5474 task.detach_and_log_err(cx);
5475 }
5476 });
5477 }
5478
5479 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
5480 if self.read_only(cx) {
5481 return;
5482 }
5483
5484 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5485
5486 let buffer = self.buffer.read(cx);
5487 let snapshot = buffer.snapshot(cx);
5488
5489 let mut edits = Vec::new();
5490 let mut rows = Vec::new();
5491
5492 for (rows_inserted, selection) in self
5493 .selections
5494 .all_adjusted(&self.display_snapshot(cx))
5495 .into_iter()
5496 .enumerate()
5497 {
5498 let cursor = selection.head();
5499 let row = cursor.row;
5500
5501 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
5502
5503 let newline = "\n".to_string();
5504 edits.push((start_of_line..start_of_line, newline));
5505
5506 rows.push(row + rows_inserted as u32);
5507 }
5508
5509 self.transact(window, cx, |editor, window, cx| {
5510 editor.edit(edits, cx);
5511
5512 editor.change_selections(Default::default(), window, cx, |s| {
5513 let mut index = 0;
5514 s.move_cursors_with(&mut |map, _, _| {
5515 let row = rows[index];
5516 index += 1;
5517
5518 let point = Point::new(row, 0);
5519 let boundary = map.next_line_boundary(point).1;
5520 let clipped = map.clip_point(boundary, Bias::Left);
5521
5522 (clipped, SelectionGoal::None)
5523 });
5524 });
5525
5526 let mut indent_edits = Vec::new();
5527 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5528 for row in rows {
5529 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5530 for (row, indent) in indents {
5531 if indent.len == 0 {
5532 continue;
5533 }
5534
5535 let text = match indent.kind {
5536 IndentKind::Space => " ".repeat(indent.len as usize),
5537 IndentKind::Tab => "\t".repeat(indent.len as usize),
5538 };
5539 let point = Point::new(row.0, 0);
5540 indent_edits.push((point..point, text));
5541 }
5542 }
5543 editor.edit(indent_edits, cx);
5544 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5545 format.detach_and_log_err(cx);
5546 }
5547 });
5548 }
5549
5550 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5551 if self.read_only(cx) {
5552 return;
5553 }
5554
5555 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5556
5557 let mut buffer_edits: HashMap<EntityId, (Entity<Buffer>, Vec<Point>)> = HashMap::default();
5558 let mut rows = Vec::new();
5559 let mut rows_inserted = 0;
5560
5561 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5562 let cursor = selection.head();
5563 let row = cursor.row;
5564
5565 let point = Point::new(row, 0);
5566 let Some((buffer_handle, buffer_point)) =
5567 self.buffer.read(cx).point_to_buffer_point(point, cx)
5568 else {
5569 continue;
5570 };
5571
5572 buffer_edits
5573 .entry(buffer_handle.entity_id())
5574 .or_insert_with(|| (buffer_handle, Vec::new()))
5575 .1
5576 .push(buffer_point);
5577
5578 rows_inserted += 1;
5579 rows.push(row + rows_inserted);
5580 }
5581
5582 self.transact(window, cx, |editor, window, cx| {
5583 for (_, (buffer_handle, points)) in &buffer_edits {
5584 buffer_handle.update(cx, |buffer, cx| {
5585 let edits: Vec<_> = points
5586 .iter()
5587 .map(|point| {
5588 let target = Point::new(point.row + 1, 0);
5589 let start_of_line = buffer.point_to_offset(target).min(buffer.len());
5590 (start_of_line..start_of_line, "\n")
5591 })
5592 .collect();
5593 buffer.edit(edits, None, cx);
5594 });
5595 }
5596
5597 editor.change_selections(Default::default(), window, cx, |s| {
5598 let mut index = 0;
5599 s.move_cursors_with(&mut |map, _, _| {
5600 let row = rows[index];
5601 index += 1;
5602
5603 let point = Point::new(row, 0);
5604 let boundary = map.next_line_boundary(point).1;
5605 let clipped = map.clip_point(boundary, Bias::Left);
5606
5607 (clipped, SelectionGoal::None)
5608 });
5609 });
5610
5611 let mut indent_edits = Vec::new();
5612 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5613 for row in rows {
5614 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5615 for (row, indent) in indents {
5616 if indent.len == 0 {
5617 continue;
5618 }
5619
5620 let text = match indent.kind {
5621 IndentKind::Space => " ".repeat(indent.len as usize),
5622 IndentKind::Tab => "\t".repeat(indent.len as usize),
5623 };
5624 let point = Point::new(row.0, 0);
5625 indent_edits.push((point..point, text));
5626 }
5627 }
5628 editor.edit(indent_edits, cx);
5629 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5630 format.detach_and_log_err(cx);
5631 }
5632 });
5633 }
5634
5635 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5636 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5637 original_indent_columns: Vec::new(),
5638 });
5639 self.replace_selections(text, autoindent, window, cx, false);
5640 }
5641
5642 /// Replaces the editor's selections with the provided `text`, applying the
5643 /// given `autoindent_mode` (`None` will skip autoindentation).
5644 ///
5645 /// Early returns if the editor is in read-only mode, without applying any
5646 /// edits.
5647 fn replace_selections(
5648 &mut self,
5649 text: &str,
5650 autoindent_mode: Option<AutoindentMode>,
5651 window: &mut Window,
5652 cx: &mut Context<Self>,
5653 apply_linked_edits: bool,
5654 ) {
5655 if self.read_only(cx) {
5656 return;
5657 }
5658
5659 let text: Arc<str> = text.into();
5660 self.transact(window, cx, |this, window, cx| {
5661 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5662 let linked_edits = if apply_linked_edits {
5663 this.linked_edits_for_selections(text.clone(), cx)
5664 } else {
5665 LinkedEdits::new()
5666 };
5667
5668 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5669 let anchors = {
5670 let snapshot = buffer.read(cx);
5671 old_selections
5672 .iter()
5673 .map(|s| {
5674 let anchor = snapshot.anchor_after(s.head());
5675 s.map(|_| anchor)
5676 })
5677 .collect::<Vec<_>>()
5678 };
5679 buffer.edit(
5680 old_selections
5681 .iter()
5682 .map(|s| (s.start..s.end, text.clone())),
5683 autoindent_mode,
5684 cx,
5685 );
5686 anchors
5687 });
5688
5689 linked_edits.apply(cx);
5690
5691 this.change_selections(Default::default(), window, cx, |s| {
5692 s.select_anchors(selection_anchors);
5693 });
5694
5695 if apply_linked_edits {
5696 refresh_linked_ranges(this, window, cx);
5697 }
5698
5699 cx.notify();
5700 });
5701 }
5702
5703 /// Collects linked edits for the current selections, pairing each linked
5704 /// range with `text`.
5705 pub fn linked_edits_for_selections(&self, text: Arc<str>, cx: &App) -> LinkedEdits {
5706 let multibuffer_snapshot = self.buffer().read(cx).snapshot(cx);
5707 let mut linked_edits = LinkedEdits::new();
5708 if !self.linked_edit_ranges.is_empty() {
5709 for selection in self.selections.disjoint_anchors() {
5710 let Some((_, range)) =
5711 multibuffer_snapshot.anchor_range_to_buffer_anchor_range(selection.range())
5712 else {
5713 continue;
5714 };
5715 linked_edits.push(self, range, text.clone(), cx);
5716 }
5717 }
5718 linked_edits
5719 }
5720
5721 /// Deletes the content covered by the current selections and applies
5722 /// linked edits.
5723 pub fn delete_selections_with_linked_edits(
5724 &mut self,
5725 window: &mut Window,
5726 cx: &mut Context<Self>,
5727 ) {
5728 self.replace_selections("", None, window, cx, true);
5729 }
5730
5731 #[cfg(any(test, feature = "test-support"))]
5732 pub fn set_linked_edit_ranges_for_testing(
5733 &mut self,
5734 ranges: Vec<(Range<Point>, Vec<Range<Point>>)>,
5735 cx: &mut Context<Self>,
5736 ) -> Option<()> {
5737 let Some((buffer, _)) = self
5738 .buffer
5739 .read(cx)
5740 .text_anchor_for_position(self.selections.newest_anchor().start, cx)
5741 else {
5742 return None;
5743 };
5744 let buffer = buffer.read(cx);
5745 let buffer_id = buffer.remote_id();
5746 let mut linked_ranges = Vec::with_capacity(ranges.len());
5747 for (base_range, linked_ranges_points) in ranges {
5748 let base_anchor =
5749 buffer.anchor_before(base_range.start)..buffer.anchor_after(base_range.end);
5750 let linked_anchors = linked_ranges_points
5751 .into_iter()
5752 .map(|range| buffer.anchor_before(range.start)..buffer.anchor_after(range.end))
5753 .collect();
5754 linked_ranges.push((base_anchor, linked_anchors));
5755 }
5756 let mut map = HashMap::default();
5757 map.insert(buffer_id, linked_ranges);
5758 self.linked_edit_ranges = linked_editing_ranges::LinkedEditingRanges(map);
5759 Some(())
5760 }
5761
5762 fn trigger_completion_on_input(
5763 &mut self,
5764 text: &str,
5765 trigger_in_words: bool,
5766 window: &mut Window,
5767 cx: &mut Context<Self>,
5768 ) {
5769 let completions_source = self
5770 .context_menu
5771 .borrow()
5772 .as_ref()
5773 .and_then(|menu| match menu {
5774 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5775 CodeContextMenu::CodeActions(_) => None,
5776 });
5777
5778 match completions_source {
5779 Some(CompletionsMenuSource::Words { .. }) => {
5780 self.open_or_update_completions_menu(
5781 Some(CompletionsMenuSource::Words {
5782 ignore_threshold: false,
5783 }),
5784 None,
5785 trigger_in_words,
5786 window,
5787 cx,
5788 );
5789 }
5790 _ => self.open_or_update_completions_menu(
5791 None,
5792 Some(text.to_owned()).filter(|x| !x.is_empty()),
5793 true,
5794 window,
5795 cx,
5796 ),
5797 }
5798 }
5799
5800 /// If any empty selections is touching the start of its innermost containing autoclose
5801 /// region, expand it to select the brackets.
5802 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5803 let selections = self
5804 .selections
5805 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5806 let buffer = self.buffer.read(cx).read(cx);
5807 let new_selections = self
5808 .selections_with_autoclose_regions(selections, &buffer)
5809 .map(|(mut selection, region)| {
5810 if !selection.is_empty() {
5811 return selection;
5812 }
5813
5814 if let Some(region) = region {
5815 let mut range = region.range.to_offset(&buffer);
5816 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5817 range.start -= region.pair.start.len();
5818 if buffer.contains_str_at(range.start, ®ion.pair.start)
5819 && buffer.contains_str_at(range.end, ®ion.pair.end)
5820 {
5821 range.end += region.pair.end.len();
5822 selection.start = range.start;
5823 selection.end = range.end;
5824
5825 return selection;
5826 }
5827 }
5828 }
5829
5830 let always_treat_brackets_as_autoclosed = buffer
5831 .language_settings_at(selection.start, cx)
5832 .always_treat_brackets_as_autoclosed;
5833
5834 if !always_treat_brackets_as_autoclosed {
5835 return selection;
5836 }
5837
5838 if let Some(scope) = buffer.language_scope_at(selection.start) {
5839 for (pair, enabled) in scope.brackets() {
5840 if !enabled || !pair.close {
5841 continue;
5842 }
5843
5844 if buffer.contains_str_at(selection.start, &pair.end) {
5845 let pair_start_len = pair.start.len();
5846 if buffer.contains_str_at(
5847 selection.start.saturating_sub_usize(pair_start_len),
5848 &pair.start,
5849 ) {
5850 selection.start -= pair_start_len;
5851 selection.end += pair.end.len();
5852
5853 return selection;
5854 }
5855 }
5856 }
5857 }
5858
5859 selection
5860 })
5861 .collect();
5862
5863 drop(buffer);
5864 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5865 selections.select(new_selections)
5866 });
5867 }
5868
5869 /// Iterate the given selections, and for each one, find the smallest surrounding
5870 /// autoclose region. This uses the ordering of the selections and the autoclose
5871 /// regions to avoid repeated comparisons.
5872 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5873 &'a self,
5874 selections: impl IntoIterator<Item = Selection<D>>,
5875 buffer: &'a MultiBufferSnapshot,
5876 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5877 let mut i = 0;
5878 let mut regions = self.autoclose_regions.as_slice();
5879 selections.into_iter().map(move |selection| {
5880 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5881
5882 let mut enclosing = None;
5883 while let Some(pair_state) = regions.get(i) {
5884 if pair_state.range.end.to_offset(buffer) < range.start {
5885 regions = ®ions[i + 1..];
5886 i = 0;
5887 } else if pair_state.range.start.to_offset(buffer) > range.end {
5888 break;
5889 } else {
5890 if pair_state.selection_id == selection.id {
5891 enclosing = Some(pair_state);
5892 }
5893 i += 1;
5894 }
5895 }
5896
5897 (selection, enclosing)
5898 })
5899 }
5900
5901 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5902 fn invalidate_autoclose_regions(
5903 &mut self,
5904 mut selections: &[Selection<Anchor>],
5905 buffer: &MultiBufferSnapshot,
5906 ) {
5907 self.autoclose_regions.retain(|state| {
5908 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5909 return false;
5910 }
5911
5912 let mut i = 0;
5913 while let Some(selection) = selections.get(i) {
5914 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5915 selections = &selections[1..];
5916 continue;
5917 }
5918 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5919 break;
5920 }
5921 if selection.id == state.selection_id {
5922 return true;
5923 } else {
5924 i += 1;
5925 }
5926 }
5927 false
5928 });
5929 }
5930
5931 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5932 let offset = position.to_offset(buffer);
5933 let (word_range, kind) =
5934 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5935 if offset > word_range.start && kind == Some(CharKind::Word) {
5936 Some(
5937 buffer
5938 .text_for_range(word_range.start..offset)
5939 .collect::<String>(),
5940 )
5941 } else {
5942 None
5943 }
5944 }
5945
5946 pub fn is_lsp_relevant(&self, file: Option<&Arc<dyn language::File>>, cx: &App) -> bool {
5947 let Some(project) = self.project() else {
5948 return false;
5949 };
5950 let Some(buffer_file) = project::File::from_dyn(file) else {
5951 return false;
5952 };
5953 let Some(entry_id) = buffer_file.project_entry_id() else {
5954 return false;
5955 };
5956 let project = project.read(cx);
5957 let Some(buffer_worktree) = project.worktree_for_id(buffer_file.worktree_id(cx), cx) else {
5958 return false;
5959 };
5960 let Some(worktree_entry) = buffer_worktree.read(cx).entry_for_id(entry_id) else {
5961 return false;
5962 };
5963 !worktree_entry.is_ignored
5964 }
5965
5966 pub fn visible_buffers(&self, cx: &mut Context<Editor>) -> Vec<Entity<Buffer>> {
5967 let display_snapshot = self.display_snapshot(cx);
5968 let visible_range = self.multi_buffer_visible_range(&display_snapshot, cx);
5969 let multi_buffer = self.buffer().read(cx);
5970 display_snapshot
5971 .buffer_snapshot()
5972 .range_to_buffer_ranges(visible_range)
5973 .into_iter()
5974 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5975 .filter_map(|(buffer_snapshot, _, _)| multi_buffer.buffer(buffer_snapshot.remote_id()))
5976 .collect()
5977 }
5978
5979 pub fn visible_buffer_ranges(
5980 &self,
5981 cx: &mut Context<Editor>,
5982 ) -> Vec<(
5983 BufferSnapshot,
5984 Range<BufferOffset>,
5985 ExcerptRange<text::Anchor>,
5986 )> {
5987 let display_snapshot = self.display_snapshot(cx);
5988 let visible_range = self.multi_buffer_visible_range(&display_snapshot, cx);
5989 display_snapshot
5990 .buffer_snapshot()
5991 .range_to_buffer_ranges(visible_range)
5992 .into_iter()
5993 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5994 .collect()
5995 }
5996
5997 pub fn text_layout_details(&self, window: &mut Window, cx: &mut App) -> TextLayoutDetails {
5998 TextLayoutDetails {
5999 text_system: window.text_system().clone(),
6000 editor_style: self.style.clone().unwrap(),
6001 rem_size: window.rem_size(),
6002 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
6003 visible_rows: self.visible_line_count(),
6004 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
6005 }
6006 }
6007
6008 fn trigger_on_type_formatting(
6009 &self,
6010 input: String,
6011 window: &mut Window,
6012 cx: &mut Context<Self>,
6013 ) -> Option<Task<Result<()>>> {
6014 if input.chars().count() != 1 {
6015 return None;
6016 }
6017
6018 let project = self.project()?;
6019 let position = self.selections.newest_anchor().head();
6020 let (buffer, buffer_position) = self
6021 .buffer
6022 .read(cx)
6023 .text_anchor_for_position(position, cx)?;
6024
6025 let settings = LanguageSettings::for_buffer_at(&buffer.read(cx), buffer_position, cx);
6026 if !settings.use_on_type_format {
6027 return None;
6028 }
6029
6030 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
6031 // hence we do LSP request & edit on host side only — add formats to host's history.
6032 let push_to_lsp_host_history = true;
6033 // If this is not the host, append its history with new edits.
6034 let push_to_client_history = project.read(cx).is_via_collab();
6035
6036 let on_type_formatting = project.update(cx, |project, cx| {
6037 project.on_type_format(
6038 buffer.clone(),
6039 buffer_position,
6040 input,
6041 push_to_lsp_host_history,
6042 cx,
6043 )
6044 });
6045 Some(cx.spawn_in(window, async move |editor, cx| {
6046 if let Some(transaction) = on_type_formatting.await? {
6047 if push_to_client_history {
6048 buffer.update(cx, |buffer, _| {
6049 buffer.push_transaction(transaction, Instant::now());
6050 buffer.finalize_last_transaction();
6051 });
6052 }
6053 editor.update(cx, |editor, cx| {
6054 editor.refresh_document_highlights(cx);
6055 })?;
6056 }
6057 Ok(())
6058 }))
6059 }
6060
6061 pub fn show_word_completions(
6062 &mut self,
6063 _: &ShowWordCompletions,
6064 window: &mut Window,
6065 cx: &mut Context<Self>,
6066 ) {
6067 self.open_or_update_completions_menu(
6068 Some(CompletionsMenuSource::Words {
6069 ignore_threshold: true,
6070 }),
6071 None,
6072 false,
6073 window,
6074 cx,
6075 );
6076 }
6077
6078 pub fn show_completions(
6079 &mut self,
6080 _: &ShowCompletions,
6081 window: &mut Window,
6082 cx: &mut Context<Self>,
6083 ) {
6084 self.open_or_update_completions_menu(None, None, false, window, cx);
6085 }
6086
6087 fn open_or_update_completions_menu(
6088 &mut self,
6089 requested_source: Option<CompletionsMenuSource>,
6090 trigger: Option<String>,
6091 trigger_in_words: bool,
6092 window: &mut Window,
6093 cx: &mut Context<Self>,
6094 ) {
6095 if self.pending_rename.is_some() {
6096 return;
6097 }
6098
6099 let completions_source = self
6100 .context_menu
6101 .borrow()
6102 .as_ref()
6103 .and_then(|menu| match menu {
6104 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
6105 CodeContextMenu::CodeActions(_) => None,
6106 });
6107
6108 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
6109
6110 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
6111 // inserted and selected. To handle that case, the start of the selection is used so that
6112 // the menu starts with all choices.
6113 let position = self
6114 .selections
6115 .newest_anchor()
6116 .start
6117 .bias_right(&multibuffer_snapshot);
6118
6119 if position.diff_base_anchor().is_some() {
6120 return;
6121 }
6122 let multibuffer_position = multibuffer_snapshot.anchor_before(position);
6123 let Some((buffer_position, _)) =
6124 multibuffer_snapshot.anchor_to_buffer_anchor(multibuffer_position)
6125 else {
6126 return;
6127 };
6128 let Some(buffer) = self.buffer.read(cx).buffer(buffer_position.buffer_id) else {
6129 return;
6130 };
6131 let buffer_snapshot = buffer.read(cx).snapshot();
6132
6133 let menu_is_open = matches!(
6134 self.context_menu.borrow().as_ref(),
6135 Some(CodeContextMenu::Completions(_))
6136 );
6137
6138 let language = buffer_snapshot
6139 .language_at(buffer_position)
6140 .map(|language| language.name());
6141 let language_settings = multibuffer_snapshot.language_settings_at(multibuffer_position, cx);
6142 let completion_settings = language_settings.completions.clone();
6143
6144 let show_completions_on_input = self
6145 .show_completions_on_input_override
6146 .unwrap_or(language_settings.show_completions_on_input);
6147 if !menu_is_open && trigger.is_some() && !show_completions_on_input {
6148 return;
6149 }
6150
6151 let query: Option<Arc<String>> =
6152 Self::completion_query(&multibuffer_snapshot, multibuffer_position)
6153 .map(|query| query.into());
6154
6155 drop(multibuffer_snapshot);
6156
6157 // Hide the current completions menu when query is empty. Without this, cached
6158 // completions from before the trigger char may be reused (#32774).
6159 if query.is_none() && menu_is_open {
6160 self.hide_context_menu(window, cx);
6161 }
6162
6163 let mut ignore_word_threshold = false;
6164 let provider = match requested_source {
6165 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
6166 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
6167 ignore_word_threshold = ignore_threshold;
6168 None
6169 }
6170 Some(CompletionsMenuSource::SnippetChoices)
6171 | Some(CompletionsMenuSource::SnippetsOnly) => {
6172 log::error!("bug: SnippetChoices requested_source is not handled");
6173 None
6174 }
6175 };
6176
6177 let sort_completions = provider
6178 .as_ref()
6179 .is_some_and(|provider| provider.sort_completions());
6180
6181 let filter_completions = provider
6182 .as_ref()
6183 .is_none_or(|provider| provider.filter_completions());
6184
6185 let was_snippets_only = matches!(
6186 completions_source,
6187 Some(CompletionsMenuSource::SnippetsOnly)
6188 );
6189
6190 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
6191 if filter_completions {
6192 menu.filter(
6193 query.clone().unwrap_or_default(),
6194 buffer_position,
6195 &buffer,
6196 provider.clone(),
6197 window,
6198 cx,
6199 );
6200 }
6201 // When `is_incomplete` is false, no need to re-query completions when the current query
6202 // is a suffix of the initial query.
6203 let was_complete = !menu.is_incomplete;
6204 if was_complete && !was_snippets_only {
6205 // If the new query is a suffix of the old query (typing more characters) and
6206 // the previous result was complete, the existing completions can be filtered.
6207 //
6208 // Note that snippet completions are always complete.
6209 let query_matches = match (&menu.initial_query, &query) {
6210 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
6211 (None, _) => true,
6212 _ => false,
6213 };
6214 if query_matches {
6215 let position_matches = if menu.initial_position == position {
6216 true
6217 } else {
6218 let snapshot = self.buffer.read(cx).read(cx);
6219 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
6220 };
6221 if position_matches {
6222 return;
6223 }
6224 }
6225 }
6226 };
6227
6228 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
6229 buffer_snapshot.surrounding_word(buffer_position, None)
6230 {
6231 let word_to_exclude = buffer_snapshot
6232 .text_for_range(word_range.clone())
6233 .collect::<String>();
6234 (
6235 buffer_snapshot.anchor_before(word_range.start)
6236 ..buffer_snapshot.anchor_after(buffer_position),
6237 Some(word_to_exclude),
6238 )
6239 } else {
6240 (buffer_position..buffer_position, None)
6241 };
6242
6243 let show_completion_documentation = buffer_snapshot
6244 .settings_at(buffer_position, cx)
6245 .show_completion_documentation;
6246
6247 // The document can be large, so stay in reasonable bounds when searching for words,
6248 // otherwise completion pop-up might be slow to appear.
6249 const WORD_LOOKUP_ROWS: u32 = 5_000;
6250 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
6251 let min_word_search = buffer_snapshot.clip_point(
6252 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
6253 Bias::Left,
6254 );
6255 let max_word_search = buffer_snapshot.clip_point(
6256 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
6257 Bias::Right,
6258 );
6259 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
6260 ..buffer_snapshot.point_to_offset(max_word_search);
6261
6262 let skip_digits = query
6263 .as_ref()
6264 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
6265
6266 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
6267 trigger.as_ref().is_none_or(|trigger| {
6268 provider.is_completion_trigger(
6269 &buffer,
6270 buffer_position,
6271 trigger,
6272 trigger_in_words,
6273 cx,
6274 )
6275 })
6276 });
6277
6278 let provider_responses = if let Some(provider) = &provider
6279 && load_provider_completions
6280 {
6281 let trigger_character =
6282 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
6283 let completion_context = CompletionContext {
6284 trigger_kind: match &trigger_character {
6285 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
6286 None => CompletionTriggerKind::INVOKED,
6287 },
6288 trigger_character,
6289 };
6290
6291 provider.completions(&buffer, buffer_position, completion_context, window, cx)
6292 } else {
6293 Task::ready(Ok(Vec::new()))
6294 };
6295
6296 let load_word_completions = if !self.word_completions_enabled {
6297 false
6298 } else if requested_source
6299 == Some(CompletionsMenuSource::Words {
6300 ignore_threshold: true,
6301 })
6302 {
6303 true
6304 } else {
6305 load_provider_completions
6306 && completion_settings.words != WordsCompletionMode::Disabled
6307 && (ignore_word_threshold || {
6308 let words_min_length = completion_settings.words_min_length;
6309 // check whether word has at least `words_min_length` characters
6310 let query_chars = query.iter().flat_map(|q| q.chars());
6311 query_chars.take(words_min_length).count() == words_min_length
6312 })
6313 };
6314
6315 let mut words = if load_word_completions {
6316 cx.background_spawn({
6317 let buffer_snapshot = buffer_snapshot.clone();
6318 async move {
6319 buffer_snapshot.words_in_range(WordsQuery {
6320 fuzzy_contents: None,
6321 range: word_search_range,
6322 skip_digits,
6323 })
6324 }
6325 })
6326 } else {
6327 Task::ready(BTreeMap::default())
6328 };
6329
6330 let snippets = if let Some(provider) = &provider
6331 && provider.show_snippets()
6332 && let Some(project) = self.project()
6333 {
6334 let char_classifier = buffer_snapshot
6335 .char_classifier_at(buffer_position)
6336 .scope_context(Some(CharScopeContext::Completion));
6337 project.update(cx, |project, cx| {
6338 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
6339 })
6340 } else {
6341 Task::ready(Ok(CompletionResponse {
6342 completions: Vec::new(),
6343 display_options: Default::default(),
6344 is_incomplete: false,
6345 }))
6346 };
6347
6348 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
6349
6350 let id = post_inc(&mut self.next_completion_id);
6351 let task = cx.spawn_in(window, async move |editor, cx| {
6352 let Ok(()) = editor.update(cx, |this, _| {
6353 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
6354 }) else {
6355 return;
6356 };
6357
6358 // TODO: Ideally completions from different sources would be selectively re-queried, so
6359 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
6360 let mut completions = Vec::new();
6361 let mut is_incomplete = false;
6362 let mut display_options: Option<CompletionDisplayOptions> = None;
6363 if let Some(provider_responses) = provider_responses.await.log_err()
6364 && !provider_responses.is_empty()
6365 {
6366 for response in provider_responses {
6367 completions.extend(response.completions);
6368 is_incomplete = is_incomplete || response.is_incomplete;
6369 match display_options.as_mut() {
6370 None => {
6371 display_options = Some(response.display_options);
6372 }
6373 Some(options) => options.merge(&response.display_options),
6374 }
6375 }
6376 if completion_settings.words == WordsCompletionMode::Fallback {
6377 words = Task::ready(BTreeMap::default());
6378 }
6379 }
6380 let display_options = display_options.unwrap_or_default();
6381
6382 let mut words = words.await;
6383 if let Some(word_to_exclude) = &word_to_exclude {
6384 words.remove(word_to_exclude);
6385 }
6386 for lsp_completion in &completions {
6387 words.remove(&lsp_completion.new_text);
6388 }
6389 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
6390 replace_range: word_replace_range.clone(),
6391 new_text: word.clone(),
6392 label: CodeLabel::plain(word, None),
6393 match_start: None,
6394 snippet_deduplication_key: None,
6395 icon_path: None,
6396 documentation: None,
6397 source: CompletionSource::BufferWord {
6398 word_range,
6399 resolved: false,
6400 },
6401 insert_text_mode: Some(InsertTextMode::AS_IS),
6402 confirm: None,
6403 }));
6404
6405 completions.extend(
6406 snippets
6407 .await
6408 .into_iter()
6409 .flat_map(|response| response.completions),
6410 );
6411
6412 let menu = if completions.is_empty() {
6413 None
6414 } else {
6415 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
6416 let languages = editor
6417 .workspace
6418 .as_ref()
6419 .and_then(|(workspace, _)| workspace.upgrade())
6420 .map(|workspace| workspace.read(cx).app_state().languages.clone());
6421 let menu = CompletionsMenu::new(
6422 id,
6423 requested_source.unwrap_or(if load_provider_completions {
6424 CompletionsMenuSource::Normal
6425 } else {
6426 CompletionsMenuSource::SnippetsOnly
6427 }),
6428 sort_completions,
6429 show_completion_documentation,
6430 position,
6431 query.clone(),
6432 is_incomplete,
6433 buffer.clone(),
6434 completions.into(),
6435 editor
6436 .context_menu()
6437 .borrow_mut()
6438 .as_ref()
6439 .map(|menu| menu.primary_scroll_handle()),
6440 display_options,
6441 snippet_sort_order,
6442 languages,
6443 language,
6444 cx,
6445 );
6446
6447 let query = if filter_completions { query } else { None };
6448 let matches_task = menu.do_async_filtering(
6449 query.unwrap_or_default(),
6450 buffer_position,
6451 &buffer,
6452 cx,
6453 );
6454 (menu, matches_task)
6455 }) else {
6456 return;
6457 };
6458
6459 let matches = matches_task.await;
6460
6461 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
6462 // Newer menu already set, so exit.
6463 if let Some(CodeContextMenu::Completions(prev_menu)) =
6464 editor.context_menu.borrow().as_ref()
6465 && prev_menu.id > id
6466 {
6467 return;
6468 };
6469
6470 // Only valid to take prev_menu because either the new menu is immediately set
6471 // below, or the menu is hidden.
6472 if let Some(CodeContextMenu::Completions(prev_menu)) =
6473 editor.context_menu.borrow_mut().take()
6474 {
6475 let position_matches =
6476 if prev_menu.initial_position == menu.initial_position {
6477 true
6478 } else {
6479 let snapshot = editor.buffer.read(cx).read(cx);
6480 prev_menu.initial_position.to_offset(&snapshot)
6481 == menu.initial_position.to_offset(&snapshot)
6482 };
6483 if position_matches {
6484 // Preserve markdown cache before `set_filter_results` because it will
6485 // try to populate the documentation cache.
6486 menu.preserve_markdown_cache(prev_menu);
6487 }
6488 };
6489
6490 menu.set_filter_results(matches, provider, window, cx);
6491 }) else {
6492 return;
6493 };
6494
6495 menu.visible().then_some(menu)
6496 };
6497
6498 editor
6499 .update_in(cx, |editor, window, cx| {
6500 if editor.focus_handle.is_focused(window)
6501 && let Some(menu) = menu
6502 {
6503 *editor.context_menu.borrow_mut() =
6504 Some(CodeContextMenu::Completions(menu));
6505
6506 crate::hover_popover::hide_hover(editor, cx);
6507 if editor.show_edit_predictions_in_menu() {
6508 editor.update_visible_edit_prediction(window, cx);
6509 } else {
6510 editor
6511 .discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
6512 }
6513
6514 cx.notify();
6515 return;
6516 }
6517
6518 if editor.completion_tasks.len() <= 1 {
6519 // If there are no more completion tasks and the last menu was empty, we should hide it.
6520 let was_hidden = editor.hide_context_menu(window, cx).is_none();
6521 // If it was already hidden and we don't show edit predictions in the menu,
6522 // we should also show the edit prediction when available.
6523 if was_hidden && editor.show_edit_predictions_in_menu() {
6524 editor.update_visible_edit_prediction(window, cx);
6525 }
6526 }
6527 })
6528 .ok();
6529 });
6530
6531 self.completion_tasks.push((id, task));
6532 }
6533
6534 #[cfg(any(test, feature = "test-support"))]
6535 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
6536 let menu = self.context_menu.borrow();
6537 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
6538 let completions = menu.completions.borrow();
6539 Some(completions.to_vec())
6540 } else {
6541 None
6542 }
6543 }
6544
6545 pub fn with_completions_menu_matching_id<R>(
6546 &self,
6547 id: CompletionId,
6548 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
6549 ) -> R {
6550 let mut context_menu = self.context_menu.borrow_mut();
6551 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
6552 return f(None);
6553 };
6554 if completions_menu.id != id {
6555 return f(None);
6556 }
6557 f(Some(completions_menu))
6558 }
6559
6560 pub fn confirm_completion(
6561 &mut self,
6562 action: &ConfirmCompletion,
6563 window: &mut Window,
6564 cx: &mut Context<Self>,
6565 ) -> Option<Task<Result<()>>> {
6566 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6567 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
6568 }
6569
6570 pub fn confirm_completion_insert(
6571 &mut self,
6572 _: &ConfirmCompletionInsert,
6573 window: &mut Window,
6574 cx: &mut Context<Self>,
6575 ) -> Option<Task<Result<()>>> {
6576 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6577 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
6578 }
6579
6580 pub fn confirm_completion_replace(
6581 &mut self,
6582 _: &ConfirmCompletionReplace,
6583 window: &mut Window,
6584 cx: &mut Context<Self>,
6585 ) -> Option<Task<Result<()>>> {
6586 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6587 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
6588 }
6589
6590 pub fn compose_completion(
6591 &mut self,
6592 action: &ComposeCompletion,
6593 window: &mut Window,
6594 cx: &mut Context<Self>,
6595 ) -> Option<Task<Result<()>>> {
6596 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6597 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
6598 }
6599
6600 fn do_completion(
6601 &mut self,
6602 item_ix: Option<usize>,
6603 intent: CompletionIntent,
6604 window: &mut Window,
6605 cx: &mut Context<Editor>,
6606 ) -> Option<Task<Result<()>>> {
6607 use language::ToOffset as _;
6608
6609 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
6610 else {
6611 return None;
6612 };
6613
6614 let candidate_id = {
6615 let entries = completions_menu.entries.borrow();
6616 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6617 if self.show_edit_predictions_in_menu() {
6618 self.discard_edit_prediction(EditPredictionDiscardReason::Rejected, cx);
6619 }
6620 mat.candidate_id
6621 };
6622
6623 let completion = completions_menu
6624 .completions
6625 .borrow()
6626 .get(candidate_id)?
6627 .clone();
6628 cx.stop_propagation();
6629
6630 let buffer_handle = completions_menu.buffer.clone();
6631 let multibuffer_snapshot = self.buffer.read(cx).snapshot(cx);
6632 let (initial_position, _) =
6633 multibuffer_snapshot.anchor_to_buffer_anchor(completions_menu.initial_position)?;
6634
6635 let CompletionEdit {
6636 new_text,
6637 snippet,
6638 replace_range,
6639 } = process_completion_for_edit(&completion, intent, &buffer_handle, &initial_position, cx);
6640
6641 let buffer = buffer_handle.read(cx).snapshot();
6642 let newest_selection = self.selections.newest_anchor();
6643
6644 let Some(replace_range_multibuffer) =
6645 multibuffer_snapshot.buffer_anchor_range_to_anchor_range(replace_range.clone())
6646 else {
6647 return None;
6648 };
6649
6650 let Some((buffer_snapshot, newest_range_buffer)) =
6651 multibuffer_snapshot.anchor_range_to_buffer_anchor_range(newest_selection.range())
6652 else {
6653 return None;
6654 };
6655
6656 let old_text = buffer
6657 .text_for_range(replace_range.clone())
6658 .collect::<String>();
6659 let lookbehind = newest_range_buffer
6660 .start
6661 .to_offset(buffer_snapshot)
6662 .saturating_sub(replace_range.start.to_offset(&buffer_snapshot));
6663 let lookahead = replace_range
6664 .end
6665 .to_offset(&buffer_snapshot)
6666 .saturating_sub(newest_range_buffer.end.to_offset(&buffer));
6667 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6668 let suffix = &old_text[lookbehind.min(old_text.len())..];
6669
6670 let selections = self
6671 .selections
6672 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6673 let mut ranges = Vec::new();
6674 let mut all_commit_ranges = Vec::new();
6675 let mut linked_edits = LinkedEdits::new();
6676
6677 let text: Arc<str> = new_text.clone().into();
6678 for selection in &selections {
6679 let range = if selection.id == newest_selection.id {
6680 replace_range_multibuffer.clone()
6681 } else {
6682 let mut range = selection.range();
6683
6684 // if prefix is present, don't duplicate it
6685 if multibuffer_snapshot
6686 .contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix)
6687 {
6688 range.start = range.start.saturating_sub_usize(lookbehind);
6689
6690 // if suffix is also present, mimic the newest cursor and replace it
6691 if selection.id != newest_selection.id
6692 && multibuffer_snapshot.contains_str_at(range.end, suffix)
6693 {
6694 range.end += lookahead;
6695 }
6696 }
6697 range.to_anchors(&multibuffer_snapshot)
6698 };
6699
6700 ranges.push(range.clone());
6701
6702 let start_anchor = multibuffer_snapshot.anchor_before(range.start);
6703 let end_anchor = multibuffer_snapshot.anchor_after(range.end);
6704
6705 if let Some((buffer_snapshot_2, anchor_range)) =
6706 multibuffer_snapshot.anchor_range_to_buffer_anchor_range(start_anchor..end_anchor)
6707 && buffer_snapshot_2.remote_id() == buffer_snapshot.remote_id()
6708 {
6709 all_commit_ranges.push(anchor_range.clone());
6710 if !self.linked_edit_ranges.is_empty() {
6711 linked_edits.push(&self, anchor_range, text.clone(), cx);
6712 }
6713 }
6714 }
6715
6716 let common_prefix_len = old_text
6717 .chars()
6718 .zip(new_text.chars())
6719 .take_while(|(a, b)| a == b)
6720 .map(|(a, _)| a.len_utf8())
6721 .sum::<usize>();
6722
6723 cx.emit(EditorEvent::InputHandled {
6724 utf16_range_to_replace: None,
6725 text: new_text[common_prefix_len..].into(),
6726 });
6727
6728 let tx_id = self.transact(window, cx, |editor, window, cx| {
6729 if let Some(mut snippet) = snippet {
6730 snippet.text = new_text.to_string();
6731 let offset_ranges = ranges
6732 .iter()
6733 .map(|range| range.to_offset(&multibuffer_snapshot))
6734 .collect::<Vec<_>>();
6735 editor
6736 .insert_snippet(&offset_ranges, snippet, window, cx)
6737 .log_err();
6738 } else {
6739 editor.buffer.update(cx, |multi_buffer, cx| {
6740 let auto_indent = match completion.insert_text_mode {
6741 Some(InsertTextMode::AS_IS) => None,
6742 _ => editor.autoindent_mode.clone(),
6743 };
6744 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6745 multi_buffer.edit(edits, auto_indent, cx);
6746 });
6747 }
6748 linked_edits.apply(cx);
6749 editor.refresh_edit_prediction(true, false, window, cx);
6750 });
6751 self.invalidate_autoclose_regions(
6752 &self.selections.disjoint_anchors_arc(),
6753 &multibuffer_snapshot,
6754 );
6755
6756 let show_new_completions_on_confirm = completion
6757 .confirm
6758 .as_ref()
6759 .is_some_and(|confirm| confirm(intent, window, cx));
6760 if show_new_completions_on_confirm {
6761 self.open_or_update_completions_menu(None, None, false, window, cx);
6762 }
6763
6764 let provider = self.completion_provider.as_ref()?;
6765
6766 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6767 let command = lsp_store.as_ref().and_then(|lsp_store| {
6768 let CompletionSource::Lsp {
6769 lsp_completion,
6770 server_id,
6771 ..
6772 } = &completion.source
6773 else {
6774 return None;
6775 };
6776 let lsp_command = lsp_completion.command.as_ref()?;
6777 let available_commands = lsp_store
6778 .read(cx)
6779 .lsp_server_capabilities
6780 .get(server_id)
6781 .and_then(|server_capabilities| {
6782 server_capabilities
6783 .execute_command_provider
6784 .as_ref()
6785 .map(|options| options.commands.as_slice())
6786 })?;
6787 if available_commands.contains(&lsp_command.command) {
6788 Some(CodeAction {
6789 server_id: *server_id,
6790 range: language::Anchor::min_min_range_for_buffer(buffer.remote_id()),
6791 lsp_action: LspAction::Command(lsp_command.clone()),
6792 resolved: false,
6793 })
6794 } else {
6795 None
6796 }
6797 });
6798
6799 drop(completion);
6800 let apply_edits = provider.apply_additional_edits_for_completion(
6801 buffer_handle.clone(),
6802 completions_menu.completions.clone(),
6803 candidate_id,
6804 true,
6805 all_commit_ranges,
6806 cx,
6807 );
6808
6809 let editor_settings = EditorSettings::get_global(cx);
6810 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6811 // After the code completion is finished, users often want to know what signatures are needed.
6812 // so we should automatically call signature_help
6813 self.show_signature_help(&ShowSignatureHelp, window, cx);
6814 }
6815
6816 Some(cx.spawn_in(window, async move |editor, cx| {
6817 let additional_edits_tx = apply_edits.await?;
6818
6819 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6820 let title = command.lsp_action.title().to_owned();
6821 let project_transaction = lsp_store
6822 .update(cx, |lsp_store, cx| {
6823 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6824 })
6825 .await
6826 .context("applying post-completion command")?;
6827 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6828 Self::open_project_transaction(
6829 &editor,
6830 workspace.downgrade(),
6831 project_transaction,
6832 title,
6833 cx,
6834 )
6835 .await?;
6836 }
6837 }
6838
6839 if let Some(tx_id) = tx_id
6840 && let Some(additional_edits_tx) = additional_edits_tx
6841 {
6842 editor
6843 .update(cx, |editor, cx| {
6844 editor.buffer.update(cx, |buffer, cx| {
6845 buffer.merge_transactions(additional_edits_tx.id, tx_id, cx)
6846 });
6847 })
6848 .context("merge transactions")?;
6849 }
6850
6851 Ok(())
6852 }))
6853 }
6854
6855 pub fn toggle_code_actions(
6856 &mut self,
6857 action: &ToggleCodeActions,
6858 window: &mut Window,
6859 cx: &mut Context<Self>,
6860 ) {
6861 let quick_launch = action.quick_launch;
6862 let mut context_menu = self.context_menu.borrow_mut();
6863 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6864 if code_actions.deployed_from == action.deployed_from {
6865 // Toggle if we're selecting the same one
6866 *context_menu = None;
6867 cx.notify();
6868 return;
6869 } else {
6870 // Otherwise, clear it and start a new one
6871 *context_menu = None;
6872 cx.notify();
6873 }
6874 }
6875 drop(context_menu);
6876 let snapshot = self.snapshot(window, cx);
6877 let deployed_from = action.deployed_from.clone();
6878 let action = action.clone();
6879 self.completion_tasks.clear();
6880 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
6881
6882 let multibuffer_point = match &action.deployed_from {
6883 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6884 DisplayPoint::new(*row, 0).to_point(&snapshot)
6885 }
6886 _ => self
6887 .selections
6888 .newest::<Point>(&snapshot.display_snapshot)
6889 .head(),
6890 };
6891 let Some((buffer, buffer_row)) = snapshot
6892 .buffer_snapshot()
6893 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6894 .and_then(|(buffer_snapshot, range)| {
6895 self.buffer()
6896 .read(cx)
6897 .buffer(buffer_snapshot.remote_id())
6898 .map(|buffer| (buffer, range.start.row))
6899 })
6900 else {
6901 return;
6902 };
6903 let buffer_id = buffer.read(cx).remote_id();
6904 let tasks = self
6905 .runnables
6906 .runnables((buffer_id, buffer_row))
6907 .map(|t| Arc::new(t.to_owned()));
6908
6909 if !self.focus_handle.is_focused(window) {
6910 return;
6911 }
6912 let project = self.project.clone();
6913
6914 let code_actions_task = match deployed_from {
6915 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6916 _ => self.code_actions(buffer_row, window, cx),
6917 };
6918
6919 let runnable_task = match deployed_from {
6920 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6921 _ => {
6922 let mut task_context_task = Task::ready(None);
6923 if let Some(tasks) = &tasks
6924 && let Some(project) = project
6925 {
6926 task_context_task =
6927 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6928 }
6929
6930 cx.spawn_in(window, {
6931 let buffer = buffer.clone();
6932 async move |editor, cx| {
6933 let task_context = task_context_task.await;
6934
6935 let resolved_tasks =
6936 tasks
6937 .zip(task_context.clone())
6938 .map(|(tasks, task_context)| ResolvedTasks {
6939 templates: tasks.resolve(&task_context).collect(),
6940 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6941 multibuffer_point.row,
6942 tasks.column,
6943 )),
6944 });
6945 let debug_scenarios = editor
6946 .update(cx, |editor, cx| {
6947 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6948 })?
6949 .await;
6950 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6951 }
6952 })
6953 }
6954 };
6955
6956 cx.spawn_in(window, async move |editor, cx| {
6957 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6958 let code_actions = code_actions_task.await;
6959 let spawn_straight_away = quick_launch
6960 && resolved_tasks
6961 .as_ref()
6962 .is_some_and(|tasks| tasks.templates.len() == 1)
6963 && code_actions
6964 .as_ref()
6965 .is_none_or(|actions| actions.is_empty())
6966 && debug_scenarios.is_empty();
6967
6968 editor.update_in(cx, |editor, window, cx| {
6969 crate::hover_popover::hide_hover(editor, cx);
6970 let actions = CodeActionContents::new(
6971 resolved_tasks,
6972 code_actions,
6973 debug_scenarios,
6974 task_context.unwrap_or_default(),
6975 );
6976
6977 // Don't show the menu if there are no actions available
6978 if actions.is_empty() {
6979 cx.notify();
6980 return Task::ready(Ok(()));
6981 }
6982
6983 *editor.context_menu.borrow_mut() =
6984 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6985 buffer,
6986 actions,
6987 selected_item: Default::default(),
6988 scroll_handle: UniformListScrollHandle::default(),
6989 deployed_from,
6990 }));
6991 cx.notify();
6992 if spawn_straight_away
6993 && let Some(task) = editor.confirm_code_action(
6994 &ConfirmCodeAction { item_ix: Some(0) },
6995 window,
6996 cx,
6997 )
6998 {
6999 return task;
7000 }
7001
7002 Task::ready(Ok(()))
7003 })
7004 })
7005 .detach_and_log_err(cx);
7006 }
7007
7008 fn debug_scenarios(
7009 &mut self,
7010 resolved_tasks: &Option<ResolvedTasks>,
7011 buffer: &Entity<Buffer>,
7012 cx: &mut App,
7013 ) -> Task<Vec<task::DebugScenario>> {
7014 maybe!({
7015 let project = self.project()?;
7016 let dap_store = project.read(cx).dap_store();
7017 let mut scenarios = vec![];
7018 let resolved_tasks = resolved_tasks.as_ref()?;
7019 let buffer = buffer.read(cx);
7020 let language = buffer.language()?;
7021 let debug_adapter = LanguageSettings::for_buffer(&buffer, cx)
7022 .debuggers
7023 .first()
7024 .map(SharedString::from)
7025 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
7026
7027 dap_store.update(cx, |dap_store, cx| {
7028 for (_, task) in &resolved_tasks.templates {
7029 let maybe_scenario = dap_store.debug_scenario_for_build_task(
7030 task.original_task().clone(),
7031 debug_adapter.clone().into(),
7032 task.display_label().to_owned().into(),
7033 cx,
7034 );
7035 scenarios.push(maybe_scenario);
7036 }
7037 });
7038 Some(cx.background_spawn(async move {
7039 futures::future::join_all(scenarios)
7040 .await
7041 .into_iter()
7042 .flatten()
7043 .collect::<Vec<_>>()
7044 }))
7045 })
7046 .unwrap_or_else(|| Task::ready(vec![]))
7047 }
7048
7049 fn code_actions(
7050 &mut self,
7051 buffer_row: u32,
7052 window: &mut Window,
7053 cx: &mut Context<Self>,
7054 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
7055 let mut task = self.code_actions_task.take();
7056 cx.spawn_in(window, async move |editor, cx| {
7057 while let Some(prev_task) = task {
7058 prev_task.await.log_err();
7059 task = editor
7060 .update(cx, |this, _| this.code_actions_task.take())
7061 .ok()?;
7062 }
7063
7064 editor
7065 .update(cx, |editor, cx| {
7066 editor
7067 .available_code_actions
7068 .clone()
7069 .and_then(|(location, code_actions)| {
7070 let snapshot = location.buffer.read(cx).snapshot();
7071 let point_range = location.range.to_point(&snapshot);
7072 let point_range = point_range.start.row..=point_range.end.row;
7073 if point_range.contains(&buffer_row) {
7074 Some(code_actions)
7075 } else {
7076 None
7077 }
7078 })
7079 })
7080 .ok()
7081 .flatten()
7082 })
7083 }
7084
7085 pub fn confirm_code_action(
7086 &mut self,
7087 action: &ConfirmCodeAction,
7088 window: &mut Window,
7089 cx: &mut Context<Self>,
7090 ) -> Option<Task<Result<()>>> {
7091 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
7092
7093 let actions_menu =
7094 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
7095 menu
7096 } else {
7097 return None;
7098 };
7099
7100 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
7101 let action = actions_menu.actions.get(action_ix)?;
7102 let title = action.label();
7103 let buffer = actions_menu.buffer;
7104 let workspace = self.workspace()?;
7105
7106 match action {
7107 CodeActionsItem::Task(task_source_kind, resolved_task) => {
7108 workspace.update(cx, |workspace, cx| {
7109 workspace.schedule_resolved_task(
7110 task_source_kind,
7111 resolved_task,
7112 false,
7113 window,
7114 cx,
7115 );
7116
7117 Some(Task::ready(Ok(())))
7118 })
7119 }
7120 CodeActionsItem::CodeAction { action, provider } => {
7121 let apply_code_action =
7122 provider.apply_code_action(buffer, action, true, window, cx);
7123 let workspace = workspace.downgrade();
7124 Some(cx.spawn_in(window, async move |editor, cx| {
7125 let project_transaction = apply_code_action.await?;
7126 Self::open_project_transaction(
7127 &editor,
7128 workspace,
7129 project_transaction,
7130 title,
7131 cx,
7132 )
7133 .await
7134 }))
7135 }
7136 CodeActionsItem::DebugScenario(scenario) => {
7137 let context = actions_menu.actions.context.into();
7138
7139 workspace.update(cx, |workspace, cx| {
7140 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
7141 workspace.start_debug_session(
7142 scenario,
7143 context,
7144 Some(buffer),
7145 None,
7146 window,
7147 cx,
7148 );
7149 });
7150 Some(Task::ready(Ok(())))
7151 }
7152 }
7153 }
7154
7155 fn open_transaction_for_hidden_buffers(
7156 workspace: Entity<Workspace>,
7157 transaction: ProjectTransaction,
7158 title: String,
7159 window: &mut Window,
7160 cx: &mut Context<Self>,
7161 ) {
7162 if transaction.0.is_empty() {
7163 return;
7164 }
7165
7166 let edited_buffers_already_open = {
7167 let other_editors: Vec<Entity<Editor>> = workspace
7168 .read(cx)
7169 .panes()
7170 .iter()
7171 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
7172 .filter(|editor| editor.entity_id() != cx.entity_id())
7173 .collect();
7174
7175 transaction.0.keys().all(|buffer| {
7176 other_editors.iter().any(|editor| {
7177 let multi_buffer = editor.read(cx).buffer();
7178 multi_buffer.read(cx).is_singleton()
7179 && multi_buffer
7180 .read(cx)
7181 .as_singleton()
7182 .map_or(false, |singleton| {
7183 singleton.entity_id() == buffer.entity_id()
7184 })
7185 })
7186 })
7187 };
7188 if !edited_buffers_already_open {
7189 let workspace = workspace.downgrade();
7190 cx.defer_in(window, move |_, window, cx| {
7191 cx.spawn_in(window, async move |editor, cx| {
7192 Self::open_project_transaction(&editor, workspace, transaction, title, cx)
7193 .await
7194 .ok()
7195 })
7196 .detach();
7197 });
7198 }
7199 }
7200
7201 pub async fn open_project_transaction(
7202 editor: &WeakEntity<Editor>,
7203 workspace: WeakEntity<Workspace>,
7204 transaction: ProjectTransaction,
7205 title: String,
7206 cx: &mut AsyncWindowContext,
7207 ) -> Result<()> {
7208 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
7209 cx.update(|_, cx| {
7210 entries.sort_unstable_by_key(|(buffer, _)| {
7211 buffer.read(cx).file().map(|f| f.path().clone())
7212 });
7213 })?;
7214 if entries.is_empty() {
7215 return Ok(());
7216 }
7217
7218 // If the project transaction's edits are all contained within this editor, then
7219 // avoid opening a new editor to display them.
7220
7221 if let [(buffer, transaction)] = &*entries {
7222 let cursor_excerpt = editor.update(cx, |editor, cx| {
7223 let snapshot = editor.buffer().read(cx).snapshot(cx);
7224 let head = editor.selections.newest_anchor().head();
7225 let (buffer_snapshot, excerpt_range) = snapshot.excerpt_containing(head..head)?;
7226 if buffer_snapshot.remote_id() != buffer.read(cx).remote_id() {
7227 return None;
7228 }
7229 Some(excerpt_range)
7230 })?;
7231
7232 if let Some(excerpt_range) = cursor_excerpt {
7233 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
7234 let excerpt_range = excerpt_range.context.to_offset(buffer);
7235 buffer
7236 .edited_ranges_for_transaction::<usize>(transaction)
7237 .all(|range| {
7238 excerpt_range.start <= range.start && excerpt_range.end >= range.end
7239 })
7240 });
7241
7242 if all_edits_within_excerpt {
7243 return Ok(());
7244 }
7245 }
7246 }
7247
7248 let mut ranges_to_highlight = Vec::new();
7249 let excerpt_buffer = cx.new(|cx| {
7250 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
7251 for (buffer_handle, transaction) in &entries {
7252 let edited_ranges = buffer_handle
7253 .read(cx)
7254 .edited_ranges_for_transaction::<Point>(transaction)
7255 .collect::<Vec<_>>();
7256 multibuffer.set_excerpts_for_path(
7257 PathKey::for_buffer(buffer_handle, cx),
7258 buffer_handle.clone(),
7259 edited_ranges.clone(),
7260 multibuffer_context_lines(cx),
7261 cx,
7262 );
7263 let snapshot = multibuffer.snapshot(cx);
7264 let buffer_snapshot = buffer_handle.read(cx).snapshot();
7265 ranges_to_highlight.extend(edited_ranges.into_iter().filter_map(|range| {
7266 let text_range = buffer_snapshot.anchor_range_inside(range);
7267 let start = snapshot.anchor_in_buffer(text_range.start)?;
7268 let end = snapshot.anchor_in_buffer(text_range.end)?;
7269 Some(start..end)
7270 }));
7271 }
7272 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
7273 multibuffer
7274 });
7275
7276 workspace.update_in(cx, |workspace, window, cx| {
7277 let project = workspace.project().clone();
7278 let editor =
7279 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
7280 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
7281 editor.update(cx, |editor, cx| {
7282 editor.highlight_background(
7283 HighlightKey::Editor,
7284 &ranges_to_highlight,
7285 |_, theme| theme.colors().editor_highlighted_line_background,
7286 cx,
7287 );
7288 });
7289 })?;
7290
7291 Ok(())
7292 }
7293
7294 pub fn clear_code_action_providers(&mut self) {
7295 self.code_action_providers.clear();
7296 self.available_code_actions.take();
7297 }
7298
7299 pub fn add_code_action_provider(
7300 &mut self,
7301 provider: Rc<dyn CodeActionProvider>,
7302 window: &mut Window,
7303 cx: &mut Context<Self>,
7304 ) {
7305 if self
7306 .code_action_providers
7307 .iter()
7308 .any(|existing_provider| existing_provider.id() == provider.id())
7309 {
7310 return;
7311 }
7312
7313 self.code_action_providers.push(provider);
7314 self.refresh_code_actions(window, cx);
7315 }
7316
7317 pub fn remove_code_action_provider(
7318 &mut self,
7319 id: Arc<str>,
7320 window: &mut Window,
7321 cx: &mut Context<Self>,
7322 ) {
7323 self.code_action_providers
7324 .retain(|provider| provider.id() != id);
7325 self.refresh_code_actions(window, cx);
7326 }
7327
7328 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
7329 !self.code_action_providers.is_empty()
7330 && EditorSettings::get_global(cx).toolbar.code_actions
7331 }
7332
7333 pub fn has_available_code_actions(&self) -> bool {
7334 self.available_code_actions
7335 .as_ref()
7336 .is_some_and(|(_, actions)| !actions.is_empty())
7337 }
7338
7339 fn render_inline_code_actions(
7340 &self,
7341 icon_size: ui::IconSize,
7342 display_row: DisplayRow,
7343 is_active: bool,
7344 cx: &mut Context<Self>,
7345 ) -> AnyElement {
7346 let show_tooltip = !self.context_menu_visible();
7347 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
7348 .icon_size(icon_size)
7349 .shape(ui::IconButtonShape::Square)
7350 .icon_color(ui::Color::Hidden)
7351 .toggle_state(is_active)
7352 .when(show_tooltip, |this| {
7353 this.tooltip({
7354 let focus_handle = self.focus_handle.clone();
7355 move |_window, cx| {
7356 Tooltip::for_action_in(
7357 "Toggle Code Actions",
7358 &ToggleCodeActions {
7359 deployed_from: None,
7360 quick_launch: false,
7361 },
7362 &focus_handle,
7363 cx,
7364 )
7365 }
7366 })
7367 })
7368 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
7369 window.focus(&editor.focus_handle(cx), cx);
7370 editor.toggle_code_actions(
7371 &crate::actions::ToggleCodeActions {
7372 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
7373 display_row,
7374 )),
7375 quick_launch: false,
7376 },
7377 window,
7378 cx,
7379 );
7380 }))
7381 .into_any_element()
7382 }
7383
7384 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
7385 &self.context_menu
7386 }
7387
7388 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7389 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
7390 cx.background_executor()
7391 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
7392 .await;
7393
7394 let (start_buffer, start, _, end, _newest_selection) = this
7395 .update(cx, |this, cx| {
7396 let newest_selection = this.selections.newest_anchor().clone();
7397 if newest_selection.head().diff_base_anchor().is_some() {
7398 return None;
7399 }
7400 let display_snapshot = this.display_snapshot(cx);
7401 let newest_selection_adjusted =
7402 this.selections.newest_adjusted(&display_snapshot);
7403 let buffer = this.buffer.read(cx);
7404
7405 let (start_buffer, start) =
7406 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
7407 let (end_buffer, end) =
7408 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
7409
7410 Some((start_buffer, start, end_buffer, end, newest_selection))
7411 })?
7412 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
7413 .context(
7414 "Expected selection to lie in a single buffer when refreshing code actions",
7415 )?;
7416 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
7417 let providers = this.code_action_providers.clone();
7418 let tasks = this
7419 .code_action_providers
7420 .iter()
7421 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
7422 .collect::<Vec<_>>();
7423 (providers, tasks)
7424 })?;
7425
7426 let mut actions = Vec::new();
7427 for (provider, provider_actions) in
7428 providers.into_iter().zip(future::join_all(tasks).await)
7429 {
7430 if let Some(provider_actions) = provider_actions.log_err() {
7431 actions.extend(provider_actions.into_iter().map(|action| {
7432 AvailableCodeAction {
7433 action,
7434 provider: provider.clone(),
7435 }
7436 }));
7437 }
7438 }
7439
7440 this.update(cx, |this, cx| {
7441 this.available_code_actions = if actions.is_empty() {
7442 None
7443 } else {
7444 Some((
7445 Location {
7446 buffer: start_buffer,
7447 range: start..end,
7448 },
7449 actions.into(),
7450 ))
7451 };
7452 cx.notify();
7453 })
7454 }));
7455 }
7456
7457 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7458 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
7459 self.show_git_blame_inline = false;
7460
7461 self.show_git_blame_inline_delay_task =
7462 Some(cx.spawn_in(window, async move |this, cx| {
7463 cx.background_executor().timer(delay).await;
7464
7465 this.update(cx, |this, cx| {
7466 this.show_git_blame_inline = true;
7467 cx.notify();
7468 })
7469 .log_err();
7470 }));
7471 }
7472 }
7473
7474 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
7475 let snapshot = self.snapshot(window, cx);
7476 let cursor = self
7477 .selections
7478 .newest::<Point>(&snapshot.display_snapshot)
7479 .head();
7480 let Some((buffer, point)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor) else {
7481 return;
7482 };
7483
7484 if self.blame.is_none() {
7485 self.start_git_blame(true, window, cx);
7486 }
7487 let Some(blame) = self.blame.as_ref() else {
7488 return;
7489 };
7490
7491 let row_info = RowInfo {
7492 buffer_id: Some(buffer.remote_id()),
7493 buffer_row: Some(point.row),
7494 ..Default::default()
7495 };
7496 let Some((buffer, blame_entry)) = blame
7497 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
7498 .flatten()
7499 else {
7500 return;
7501 };
7502
7503 let anchor = self.selections.newest_anchor().head();
7504 let position = self.to_pixel_point(anchor, &snapshot, window, cx);
7505 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
7506 self.show_blame_popover(
7507 buffer,
7508 &blame_entry,
7509 position + last_bounds.origin,
7510 true,
7511 cx,
7512 );
7513 };
7514 }
7515
7516 fn show_blame_popover(
7517 &mut self,
7518 buffer: BufferId,
7519 blame_entry: &BlameEntry,
7520 position: gpui::Point<Pixels>,
7521 ignore_timeout: bool,
7522 cx: &mut Context<Self>,
7523 ) {
7524 if let Some(state) = &mut self.inline_blame_popover {
7525 state.hide_task.take();
7526 } else {
7527 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
7528 let blame_entry = blame_entry.clone();
7529 let show_task = cx.spawn(async move |editor, cx| {
7530 if !ignore_timeout {
7531 cx.background_executor()
7532 .timer(std::time::Duration::from_millis(blame_popover_delay))
7533 .await;
7534 }
7535 editor
7536 .update(cx, |editor, cx| {
7537 editor.inline_blame_popover_show_task.take();
7538 let Some(blame) = editor.blame.as_ref() else {
7539 return;
7540 };
7541 let blame = blame.read(cx);
7542 let details = blame.details_for_entry(buffer, &blame_entry);
7543 let markdown = cx.new(|cx| {
7544 Markdown::new(
7545 details
7546 .as_ref()
7547 .map(|message| message.message.clone())
7548 .unwrap_or_default(),
7549 None,
7550 None,
7551 cx,
7552 )
7553 });
7554 editor.inline_blame_popover = Some(InlineBlamePopover {
7555 position,
7556 hide_task: None,
7557 popover_bounds: None,
7558 popover_state: InlineBlamePopoverState {
7559 scroll_handle: ScrollHandle::new(),
7560 commit_message: details,
7561 markdown,
7562 },
7563 keyboard_grace: ignore_timeout,
7564 });
7565 cx.notify();
7566 })
7567 .ok();
7568 });
7569 self.inline_blame_popover_show_task = Some(show_task);
7570 }
7571 }
7572
7573 pub fn has_mouse_context_menu(&self) -> bool {
7574 self.mouse_context_menu.is_some()
7575 }
7576
7577 pub fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
7578 self.inline_blame_popover_show_task.take();
7579 if let Some(state) = &mut self.inline_blame_popover {
7580 let hide_task = cx.spawn(async move |editor, cx| {
7581 if !ignore_timeout {
7582 cx.background_executor()
7583 .timer(std::time::Duration::from_millis(100))
7584 .await;
7585 }
7586 editor
7587 .update(cx, |editor, cx| {
7588 editor.inline_blame_popover.take();
7589 cx.notify();
7590 })
7591 .ok();
7592 });
7593 state.hide_task = Some(hide_task);
7594 true
7595 } else {
7596 false
7597 }
7598 }
7599
7600 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
7601 if self.pending_rename.is_some() {
7602 return None;
7603 }
7604
7605 let provider = self.semantics_provider.clone()?;
7606 let buffer = self.buffer.read(cx);
7607 let newest_selection = self.selections.newest_anchor().clone();
7608 let cursor_position = newest_selection.head();
7609 let (cursor_buffer, cursor_buffer_position) =
7610 buffer.text_anchor_for_position(cursor_position, cx)?;
7611 let (tail_buffer, tail_buffer_position) =
7612 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
7613 if cursor_buffer != tail_buffer {
7614 return None;
7615 }
7616
7617 let snapshot = cursor_buffer.read(cx).snapshot();
7618 let word_ranges = cx.background_spawn(async move {
7619 // this might look odd to put on the background thread, but
7620 // `surrounding_word` can be quite expensive as it calls into
7621 // tree-sitter language scopes
7622 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
7623 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
7624 (start_word_range, end_word_range)
7625 });
7626
7627 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
7628 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
7629 let (start_word_range, end_word_range) = word_ranges.await;
7630 if start_word_range != end_word_range {
7631 this.update(cx, |this, cx| {
7632 this.document_highlights_task.take();
7633 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
7634 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
7635 })
7636 .ok();
7637 return;
7638 }
7639 cx.background_executor()
7640 .timer(Duration::from_millis(debounce))
7641 .await;
7642
7643 let highlights = if let Some(highlights) = cx.update(|cx| {
7644 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
7645 }) {
7646 highlights.await.log_err()
7647 } else {
7648 None
7649 };
7650
7651 if let Some(highlights) = highlights {
7652 this.update(cx, |this, cx| {
7653 if this.pending_rename.is_some() {
7654 return;
7655 }
7656
7657 let buffer = this.buffer.read(cx);
7658 if buffer
7659 .text_anchor_for_position(cursor_position, cx)
7660 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
7661 {
7662 return;
7663 }
7664
7665 let mut write_ranges = Vec::new();
7666 let mut read_ranges = Vec::new();
7667 let multibuffer_snapshot = buffer.snapshot(cx);
7668 for highlight in highlights {
7669 for range in
7670 multibuffer_snapshot.buffer_range_to_excerpt_ranges(highlight.range)
7671 {
7672 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7673 write_ranges.push(range);
7674 } else {
7675 read_ranges.push(range);
7676 }
7677 }
7678 }
7679
7680 this.highlight_background(
7681 HighlightKey::DocumentHighlightRead,
7682 &read_ranges,
7683 |_, theme| theme.colors().editor_document_highlight_read_background,
7684 cx,
7685 );
7686 this.highlight_background(
7687 HighlightKey::DocumentHighlightWrite,
7688 &write_ranges,
7689 |_, theme| theme.colors().editor_document_highlight_write_background,
7690 cx,
7691 );
7692 cx.notify();
7693 })
7694 .log_err();
7695 }
7696 }));
7697 None
7698 }
7699
7700 fn prepare_highlight_query_from_selection(
7701 &mut self,
7702 snapshot: &DisplaySnapshot,
7703 cx: &mut Context<Editor>,
7704 ) -> Option<(String, Range<Anchor>)> {
7705 if matches!(self.mode, EditorMode::SingleLine) {
7706 return None;
7707 }
7708 if !self.use_selection_highlight || !EditorSettings::get_global(cx).selection_highlight {
7709 return None;
7710 }
7711 if self.selections.count() != 1 || self.selections.line_mode() {
7712 return None;
7713 }
7714 let selection = self.selections.newest::<Point>(&snapshot);
7715 // If the selection spans multiple rows OR it is empty
7716 if selection.start.row != selection.end.row
7717 || selection.start.column == selection.end.column
7718 {
7719 return None;
7720 }
7721 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7722 let query = snapshot
7723 .buffer_snapshot()
7724 .text_for_range(selection_anchor_range.clone())
7725 .collect::<String>();
7726 if query.trim().is_empty() {
7727 return None;
7728 }
7729 Some((query, selection_anchor_range))
7730 }
7731
7732 #[ztracing::instrument(skip_all)]
7733 fn update_selection_occurrence_highlights(
7734 &mut self,
7735 multi_buffer_snapshot: MultiBufferSnapshot,
7736 query_text: String,
7737 query_range: Range<Anchor>,
7738 multi_buffer_range_to_query: Range<Point>,
7739 use_debounce: bool,
7740 window: &mut Window,
7741 cx: &mut Context<Editor>,
7742 ) -> Task<()> {
7743 cx.spawn_in(window, async move |editor, cx| {
7744 if use_debounce {
7745 cx.background_executor()
7746 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7747 .await;
7748 }
7749 let match_task = cx.background_spawn(async move {
7750 let buffer_ranges = multi_buffer_snapshot
7751 .range_to_buffer_ranges(
7752 multi_buffer_range_to_query.start..multi_buffer_range_to_query.end,
7753 )
7754 .into_iter()
7755 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7756 let mut match_ranges = Vec::new();
7757 let Ok(regex) = project::search::SearchQuery::text(
7758 query_text,
7759 false,
7760 false,
7761 false,
7762 Default::default(),
7763 Default::default(),
7764 false,
7765 None,
7766 ) else {
7767 return Vec::default();
7768 };
7769 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7770 for (buffer_snapshot, search_range, _) in buffer_ranges {
7771 match_ranges.extend(
7772 regex
7773 .search(
7774 &buffer_snapshot,
7775 Some(search_range.start.0..search_range.end.0),
7776 )
7777 .await
7778 .into_iter()
7779 .filter_map(|match_range| {
7780 let match_start = buffer_snapshot
7781 .anchor_after(search_range.start + match_range.start);
7782 let match_end = buffer_snapshot
7783 .anchor_before(search_range.start + match_range.end);
7784 {
7785 let range = multi_buffer_snapshot
7786 .anchor_in_buffer(match_start)?
7787 ..multi_buffer_snapshot.anchor_in_buffer(match_end)?;
7788 Some(range).filter(|match_anchor_range| {
7789 match_anchor_range != &query_range
7790 })
7791 }
7792 }),
7793 );
7794 }
7795 match_ranges
7796 });
7797 let match_ranges = match_task.await;
7798 editor
7799 .update_in(cx, |editor, _, cx| {
7800 if use_debounce {
7801 editor.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
7802 editor.debounced_selection_highlight_complete = true;
7803 } else if editor.debounced_selection_highlight_complete {
7804 return;
7805 }
7806 if !match_ranges.is_empty() {
7807 editor.highlight_background(
7808 HighlightKey::SelectedTextHighlight,
7809 &match_ranges,
7810 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7811 cx,
7812 )
7813 }
7814 })
7815 .log_err();
7816 })
7817 }
7818
7819 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7820 struct NewlineFold;
7821 let type_id = std::any::TypeId::of::<NewlineFold>();
7822 if !self.mode.is_single_line() {
7823 return;
7824 }
7825 let snapshot = self.snapshot(window, cx);
7826 if snapshot.buffer_snapshot().max_point().row == 0 {
7827 return;
7828 }
7829 let task = cx.background_spawn(async move {
7830 let new_newlines = snapshot
7831 .buffer_chars_at(MultiBufferOffset(0))
7832 .filter_map(|(c, i)| {
7833 if c == '\n' {
7834 Some(
7835 snapshot.buffer_snapshot().anchor_after(i)
7836 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7837 )
7838 } else {
7839 None
7840 }
7841 })
7842 .collect::<Vec<_>>();
7843 let existing_newlines = snapshot
7844 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7845 .filter_map(|fold| {
7846 if fold.placeholder.type_tag == Some(type_id) {
7847 Some(fold.range.start..fold.range.end)
7848 } else {
7849 None
7850 }
7851 })
7852 .collect::<Vec<_>>();
7853
7854 (new_newlines, existing_newlines)
7855 });
7856 self.folding_newlines = cx.spawn(async move |this, cx| {
7857 let (new_newlines, existing_newlines) = task.await;
7858 if new_newlines == existing_newlines {
7859 return;
7860 }
7861 let placeholder = FoldPlaceholder {
7862 render: Arc::new(move |_, _, cx| {
7863 div()
7864 .bg(cx.theme().status().hint_background)
7865 .border_b_1()
7866 .size_full()
7867 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7868 .border_color(cx.theme().status().hint)
7869 .child("\\n")
7870 .into_any()
7871 }),
7872 constrain_width: false,
7873 merge_adjacent: false,
7874 type_tag: Some(type_id),
7875 collapsed_text: None,
7876 };
7877 let creases = new_newlines
7878 .into_iter()
7879 .map(|range| Crease::simple(range, placeholder.clone()))
7880 .collect();
7881 this.update(cx, |this, cx| {
7882 this.display_map.update(cx, |display_map, cx| {
7883 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7884 display_map.fold(creases, cx);
7885 });
7886 })
7887 .ok();
7888 });
7889 }
7890
7891 #[ztracing::instrument(skip_all)]
7892 fn refresh_outline_symbols_at_cursor(&mut self, cx: &mut Context<Editor>) {
7893 if !self.lsp_data_enabled() {
7894 return;
7895 }
7896 let cursor = self.selections.newest_anchor().head();
7897 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7898
7899 if self.uses_lsp_document_symbols(cursor, &multi_buffer_snapshot, cx) {
7900 self.outline_symbols_at_cursor =
7901 self.lsp_symbols_at_cursor(cursor, &multi_buffer_snapshot, cx);
7902 cx.emit(EditorEvent::OutlineSymbolsChanged);
7903 cx.notify();
7904 } else {
7905 let syntax = cx.theme().syntax().clone();
7906 let background_task = cx.background_spawn(async move {
7907 multi_buffer_snapshot.symbols_containing(cursor, Some(&syntax))
7908 });
7909 self.refresh_outline_symbols_at_cursor_at_cursor_task =
7910 cx.spawn(async move |this, cx| {
7911 let symbols = background_task.await;
7912 this.update(cx, |this, cx| {
7913 this.outline_symbols_at_cursor = symbols;
7914 cx.emit(EditorEvent::OutlineSymbolsChanged);
7915 cx.notify();
7916 })
7917 .ok();
7918 });
7919 }
7920 }
7921
7922 #[ztracing::instrument(skip_all)]
7923 fn refresh_selected_text_highlights(
7924 &mut self,
7925 snapshot: &DisplaySnapshot,
7926 on_buffer_edit: bool,
7927 window: &mut Window,
7928 cx: &mut Context<Editor>,
7929 ) {
7930 let Some((query_text, query_range)) =
7931 self.prepare_highlight_query_from_selection(snapshot, cx)
7932 else {
7933 self.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
7934 self.quick_selection_highlight_task.take();
7935 self.debounced_selection_highlight_task.take();
7936 self.debounced_selection_highlight_complete = false;
7937 return;
7938 };
7939 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7940 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7941 let query_changed = self
7942 .quick_selection_highlight_task
7943 .as_ref()
7944 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range);
7945 if query_changed {
7946 self.debounced_selection_highlight_complete = false;
7947 }
7948 if on_buffer_edit || query_changed {
7949 self.quick_selection_highlight_task = Some((
7950 query_range.clone(),
7951 self.update_selection_occurrence_highlights(
7952 snapshot.buffer.clone(),
7953 query_text.clone(),
7954 query_range.clone(),
7955 self.multi_buffer_visible_range(&display_snapshot, cx),
7956 false,
7957 window,
7958 cx,
7959 ),
7960 ));
7961 }
7962 if on_buffer_edit
7963 || self
7964 .debounced_selection_highlight_task
7965 .as_ref()
7966 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7967 {
7968 let multi_buffer_start = multi_buffer_snapshot
7969 .anchor_before(MultiBufferOffset(0))
7970 .to_point(&multi_buffer_snapshot);
7971 let multi_buffer_end = multi_buffer_snapshot
7972 .anchor_after(multi_buffer_snapshot.len())
7973 .to_point(&multi_buffer_snapshot);
7974 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7975 self.debounced_selection_highlight_task = Some((
7976 query_range.clone(),
7977 self.update_selection_occurrence_highlights(
7978 snapshot.buffer.clone(),
7979 query_text,
7980 query_range,
7981 multi_buffer_full_range,
7982 true,
7983 window,
7984 cx,
7985 ),
7986 ));
7987 }
7988 }
7989
7990 pub fn multi_buffer_visible_range(
7991 &self,
7992 display_snapshot: &DisplaySnapshot,
7993 cx: &App,
7994 ) -> Range<Point> {
7995 let visible_start = self
7996 .scroll_manager
7997 .native_anchor(display_snapshot, cx)
7998 .anchor
7999 .to_point(display_snapshot.buffer_snapshot())
8000 .to_display_point(display_snapshot);
8001
8002 let mut target_end = visible_start;
8003 *target_end.row_mut() += self.visible_line_count().unwrap_or(0.).ceil() as u32;
8004
8005 visible_start.to_point(display_snapshot)
8006 ..display_snapshot
8007 .clip_point(target_end, Bias::Right)
8008 .to_point(display_snapshot)
8009 }
8010
8011 pub fn refresh_edit_prediction(
8012 &mut self,
8013 debounce: bool,
8014 user_requested: bool,
8015 window: &mut Window,
8016 cx: &mut Context<Self>,
8017 ) -> Option<()> {
8018 if self.leader_id.is_some() {
8019 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8020 return None;
8021 }
8022
8023 let cursor = self.selections.newest_anchor().head();
8024 let (buffer, cursor_buffer_position) =
8025 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8026
8027 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
8028 return None;
8029 }
8030
8031 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
8032 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8033 return None;
8034 }
8035
8036 self.update_visible_edit_prediction(window, cx);
8037
8038 if !user_requested
8039 && (!self.should_show_edit_predictions()
8040 || !self.is_focused(window)
8041 || buffer.read(cx).is_empty())
8042 {
8043 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8044 return None;
8045 }
8046
8047 self.edit_prediction_provider()?
8048 .refresh(buffer, cursor_buffer_position, debounce, cx);
8049 Some(())
8050 }
8051
8052 fn show_edit_predictions_in_menu(&self) -> bool {
8053 match self.edit_prediction_settings {
8054 EditPredictionSettings::Disabled => false,
8055 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
8056 }
8057 }
8058
8059 pub fn edit_predictions_enabled(&self) -> bool {
8060 match self.edit_prediction_settings {
8061 EditPredictionSettings::Disabled => false,
8062 EditPredictionSettings::Enabled { .. } => true,
8063 }
8064 }
8065
8066 fn edit_prediction_requires_modifier(&self) -> bool {
8067 match self.edit_prediction_settings {
8068 EditPredictionSettings::Disabled => false,
8069 EditPredictionSettings::Enabled {
8070 preview_requires_modifier,
8071 ..
8072 } => preview_requires_modifier,
8073 }
8074 }
8075
8076 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
8077 if self.edit_prediction_provider.is_none() {
8078 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8079 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8080 return;
8081 }
8082
8083 let selection = self.selections.newest_anchor();
8084 let cursor = selection.head();
8085
8086 if let Some((buffer, cursor_buffer_position)) =
8087 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
8088 {
8089 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
8090 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8091 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8092 return;
8093 }
8094 self.edit_prediction_settings =
8095 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8096 }
8097 }
8098
8099 fn edit_prediction_settings_at_position(
8100 &self,
8101 buffer: &Entity<Buffer>,
8102 buffer_position: language::Anchor,
8103 cx: &App,
8104 ) -> EditPredictionSettings {
8105 if !self.mode.is_full()
8106 || !self.show_edit_predictions_override.unwrap_or(true)
8107 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
8108 {
8109 return EditPredictionSettings::Disabled;
8110 }
8111
8112 if !LanguageSettings::for_buffer(&buffer.read(cx), cx).show_edit_predictions {
8113 return EditPredictionSettings::Disabled;
8114 };
8115
8116 let by_provider = matches!(
8117 self.menu_edit_predictions_policy,
8118 MenuEditPredictionsPolicy::ByProvider
8119 );
8120
8121 let show_in_menu = by_provider
8122 && self
8123 .edit_prediction_provider
8124 .as_ref()
8125 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
8126
8127 let file = buffer.read(cx).file();
8128 let preview_requires_modifier =
8129 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
8130
8131 EditPredictionSettings::Enabled {
8132 show_in_menu,
8133 preview_requires_modifier,
8134 }
8135 }
8136
8137 fn should_show_edit_predictions(&self) -> bool {
8138 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
8139 }
8140
8141 pub fn edit_prediction_preview_is_active(&self) -> bool {
8142 matches!(
8143 self.edit_prediction_preview,
8144 EditPredictionPreview::Active { .. }
8145 )
8146 }
8147
8148 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
8149 let cursor = self.selections.newest_anchor().head();
8150 if let Some((buffer, cursor_position)) =
8151 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
8152 {
8153 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
8154 } else {
8155 false
8156 }
8157 }
8158
8159 pub fn supports_minimap(&self, cx: &App) -> bool {
8160 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
8161 }
8162
8163 fn edit_predictions_enabled_in_buffer(
8164 &self,
8165 buffer: &Entity<Buffer>,
8166 buffer_position: language::Anchor,
8167 cx: &App,
8168 ) -> bool {
8169 maybe!({
8170 if self.read_only(cx) || self.leader_id.is_some() {
8171 return Some(false);
8172 }
8173 let provider = self.edit_prediction_provider()?;
8174 if !provider.is_enabled(buffer, buffer_position, cx) {
8175 return Some(false);
8176 }
8177 let buffer = buffer.read(cx);
8178 let Some(file) = buffer.file() else {
8179 return Some(true);
8180 };
8181 let settings = all_language_settings(Some(file), cx);
8182 Some(settings.edit_predictions_enabled_for_file(file, cx))
8183 })
8184 .unwrap_or(false)
8185 }
8186
8187 pub fn show_edit_prediction(
8188 &mut self,
8189 _: &ShowEditPrediction,
8190 window: &mut Window,
8191 cx: &mut Context<Self>,
8192 ) {
8193 if !self.has_active_edit_prediction() {
8194 self.refresh_edit_prediction(false, true, window, cx);
8195 return;
8196 }
8197
8198 self.update_visible_edit_prediction(window, cx);
8199 }
8200
8201 pub fn display_cursor_names(
8202 &mut self,
8203 _: &DisplayCursorNames,
8204 window: &mut Window,
8205 cx: &mut Context<Self>,
8206 ) {
8207 self.show_cursor_names(window, cx);
8208 }
8209
8210 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8211 self.show_cursor_names = true;
8212 cx.notify();
8213 cx.spawn_in(window, async move |this, cx| {
8214 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
8215 this.update(cx, |this, cx| {
8216 this.show_cursor_names = false;
8217 cx.notify()
8218 })
8219 .ok()
8220 })
8221 .detach();
8222 }
8223
8224 pub fn accept_partial_edit_prediction(
8225 &mut self,
8226 granularity: EditPredictionGranularity,
8227 window: &mut Window,
8228 cx: &mut Context<Self>,
8229 ) {
8230 if self.show_edit_predictions_in_menu() {
8231 self.hide_context_menu(window, cx);
8232 }
8233
8234 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
8235 return;
8236 };
8237
8238 if !matches!(granularity, EditPredictionGranularity::Full) && self.selections.count() != 1 {
8239 return;
8240 }
8241
8242 match &active_edit_prediction.completion {
8243 EditPrediction::MoveWithin { target, .. } => {
8244 let target = *target;
8245
8246 if matches!(granularity, EditPredictionGranularity::Full) {
8247 if let Some(position_map) = &self.last_position_map {
8248 let target_row = target.to_display_point(&position_map.snapshot).row();
8249 let is_visible = position_map.visible_row_range.contains(&target_row);
8250
8251 if is_visible || !self.edit_prediction_requires_modifier() {
8252 self.unfold_ranges(&[target..target], true, false, cx);
8253 self.change_selections(
8254 SelectionEffects::scroll(Autoscroll::newest()),
8255 window,
8256 cx,
8257 |selections| {
8258 selections.select_anchor_ranges([target..target]);
8259 },
8260 );
8261 self.clear_row_highlights::<EditPredictionPreview>();
8262 self.edit_prediction_preview
8263 .set_previous_scroll_position(None);
8264 } else {
8265 // Highlight and request scroll
8266 self.edit_prediction_preview
8267 .set_previous_scroll_position(Some(
8268 position_map.snapshot.scroll_anchor,
8269 ));
8270 self.highlight_rows::<EditPredictionPreview>(
8271 target..target,
8272 cx.theme().colors().editor_highlighted_line_background,
8273 RowHighlightOptions {
8274 autoscroll: true,
8275 ..Default::default()
8276 },
8277 cx,
8278 );
8279 self.request_autoscroll(Autoscroll::fit(), cx);
8280 }
8281 }
8282 } else {
8283 self.change_selections(
8284 SelectionEffects::scroll(Autoscroll::newest()),
8285 window,
8286 cx,
8287 |selections| {
8288 selections.select_anchor_ranges([target..target]);
8289 },
8290 );
8291 }
8292 }
8293 EditPrediction::MoveOutside { snapshot, target } => {
8294 if let Some(workspace) = self.workspace() {
8295 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
8296 .detach_and_log_err(cx);
8297 }
8298 }
8299 EditPrediction::Edit {
8300 edits,
8301 cursor_position,
8302 ..
8303 } => {
8304 self.report_edit_prediction_event(
8305 active_edit_prediction.completion_id.clone(),
8306 true,
8307 cx,
8308 );
8309
8310 match granularity {
8311 EditPredictionGranularity::Full => {
8312 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
8313
8314 // Compute fallback cursor position BEFORE applying the edit,
8315 // so the anchor tracks through the edit correctly
8316 let fallback_cursor_target = {
8317 let snapshot = self.buffer.read(cx).snapshot(cx);
8318 edits.last().unwrap().0.end.bias_right(&snapshot)
8319 };
8320
8321 self.buffer.update(cx, |buffer, cx| {
8322 buffer.edit(edits.iter().cloned(), None, cx)
8323 });
8324
8325 if let Some(provider) = self.edit_prediction_provider() {
8326 provider.accept(cx);
8327 }
8328
8329 // Resolve cursor position after the edit is applied
8330 let cursor_target = if let Some((anchor, offset)) = cursor_position {
8331 // The anchor tracks through the edit, then we add the offset
8332 let snapshot = self.buffer.read(cx).snapshot(cx);
8333 let base_offset = anchor.to_offset(&snapshot).0;
8334 let target_offset =
8335 MultiBufferOffset((base_offset + offset).min(snapshot.len().0));
8336 snapshot.anchor_after(target_offset)
8337 } else {
8338 fallback_cursor_target
8339 };
8340
8341 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
8342 s.select_anchor_ranges([cursor_target..cursor_target]);
8343 });
8344
8345 let selections = self.selections.disjoint_anchors_arc();
8346 if let Some(transaction_id_now) =
8347 self.buffer.read(cx).last_transaction_id(cx)
8348 {
8349 if transaction_id_prev != Some(transaction_id_now) {
8350 self.selection_history
8351 .insert_transaction(transaction_id_now, selections);
8352 }
8353 }
8354
8355 self.update_visible_edit_prediction(window, cx);
8356 if self.active_edit_prediction.is_none() {
8357 self.refresh_edit_prediction(true, true, window, cx);
8358 }
8359 cx.notify();
8360 }
8361 _ => {
8362 let snapshot = self.buffer.read(cx).snapshot(cx);
8363 let cursor_offset = self
8364 .selections
8365 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8366 .head();
8367
8368 let insertion = edits.iter().find_map(|(range, text)| {
8369 let range = range.to_offset(&snapshot);
8370 if range.is_empty() && range.start == cursor_offset {
8371 Some(text)
8372 } else {
8373 None
8374 }
8375 });
8376
8377 if let Some(text) = insertion {
8378 let text_to_insert = match granularity {
8379 EditPredictionGranularity::Word => {
8380 let mut partial = text
8381 .chars()
8382 .by_ref()
8383 .take_while(|c| c.is_alphabetic())
8384 .collect::<String>();
8385 if partial.is_empty() {
8386 partial = text
8387 .chars()
8388 .by_ref()
8389 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
8390 .collect::<String>();
8391 }
8392 partial
8393 }
8394 EditPredictionGranularity::Line => {
8395 if let Some(line) = text.split_inclusive('\n').next() {
8396 line.to_string()
8397 } else {
8398 text.to_string()
8399 }
8400 }
8401 EditPredictionGranularity::Full => unreachable!(),
8402 };
8403
8404 cx.emit(EditorEvent::InputHandled {
8405 utf16_range_to_replace: None,
8406 text: text_to_insert.clone().into(),
8407 });
8408
8409 self.replace_selections(&text_to_insert, None, window, cx, false);
8410 self.refresh_edit_prediction(true, true, window, cx);
8411 cx.notify();
8412 } else {
8413 self.accept_partial_edit_prediction(
8414 EditPredictionGranularity::Full,
8415 window,
8416 cx,
8417 );
8418 }
8419 }
8420 }
8421 }
8422 }
8423 }
8424
8425 pub fn accept_next_word_edit_prediction(
8426 &mut self,
8427 _: &AcceptNextWordEditPrediction,
8428 window: &mut Window,
8429 cx: &mut Context<Self>,
8430 ) {
8431 self.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
8432 }
8433
8434 pub fn accept_next_line_edit_prediction(
8435 &mut self,
8436 _: &AcceptNextLineEditPrediction,
8437 window: &mut Window,
8438 cx: &mut Context<Self>,
8439 ) {
8440 self.accept_partial_edit_prediction(EditPredictionGranularity::Line, window, cx);
8441 }
8442
8443 pub fn accept_edit_prediction(
8444 &mut self,
8445 _: &AcceptEditPrediction,
8446 window: &mut Window,
8447 cx: &mut Context<Self>,
8448 ) {
8449 self.accept_partial_edit_prediction(EditPredictionGranularity::Full, window, cx);
8450 }
8451
8452 fn discard_edit_prediction(
8453 &mut self,
8454 reason: EditPredictionDiscardReason,
8455 cx: &mut Context<Self>,
8456 ) -> bool {
8457 if reason == EditPredictionDiscardReason::Rejected {
8458 let completion_id = self
8459 .active_edit_prediction
8460 .as_ref()
8461 .and_then(|active_completion| active_completion.completion_id.clone());
8462
8463 self.report_edit_prediction_event(completion_id, false, cx);
8464 }
8465
8466 if let Some(provider) = self.edit_prediction_provider() {
8467 provider.discard(reason, cx);
8468 }
8469
8470 self.take_active_edit_prediction(reason == EditPredictionDiscardReason::Ignored, cx)
8471 }
8472
8473 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
8474 let Some(provider) = self.edit_prediction_provider() else {
8475 return;
8476 };
8477
8478 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
8479 let Some((position, _)) =
8480 buffer_snapshot.anchor_to_buffer_anchor(self.selections.newest_anchor().head())
8481 else {
8482 return;
8483 };
8484 let Some(buffer) = self.buffer.read(cx).buffer(position.buffer_id) else {
8485 return;
8486 };
8487
8488 let extension = buffer
8489 .read(cx)
8490 .file()
8491 .and_then(|file| Some(file.path().extension()?.to_string()));
8492
8493 let event_type = match accepted {
8494 true => "Edit Prediction Accepted",
8495 false => "Edit Prediction Discarded",
8496 };
8497 telemetry::event!(
8498 event_type,
8499 provider = provider.name(),
8500 prediction_id = id,
8501 suggestion_accepted = accepted,
8502 file_extension = extension,
8503 );
8504 }
8505
8506 fn open_editor_at_anchor(
8507 snapshot: &language::BufferSnapshot,
8508 target: language::Anchor,
8509 workspace: &Entity<Workspace>,
8510 window: &mut Window,
8511 cx: &mut App,
8512 ) -> Task<Result<()>> {
8513 workspace.update(cx, |workspace, cx| {
8514 let path = snapshot.file().map(|file| file.full_path(cx));
8515 let Some(path) =
8516 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
8517 else {
8518 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
8519 };
8520 let target = text::ToPoint::to_point(&target, snapshot);
8521 let item = workspace.open_path(path, None, true, window, cx);
8522 window.spawn(cx, async move |cx| {
8523 let Some(editor) = item.await?.downcast::<Editor>() else {
8524 return Ok(());
8525 };
8526 editor
8527 .update_in(cx, |editor, window, cx| {
8528 editor.go_to_singleton_buffer_point(target, window, cx);
8529 })
8530 .ok();
8531 anyhow::Ok(())
8532 })
8533 })
8534 }
8535
8536 pub fn has_active_edit_prediction(&self) -> bool {
8537 self.active_edit_prediction.is_some()
8538 }
8539
8540 fn take_active_edit_prediction(
8541 &mut self,
8542 preserve_stale_in_menu: bool,
8543 cx: &mut Context<Self>,
8544 ) -> bool {
8545 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
8546 if !preserve_stale_in_menu {
8547 self.stale_edit_prediction_in_menu = None;
8548 }
8549 return false;
8550 };
8551
8552 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
8553 self.clear_highlights(HighlightKey::EditPredictionHighlight, cx);
8554 self.stale_edit_prediction_in_menu =
8555 preserve_stale_in_menu.then_some(active_edit_prediction);
8556 true
8557 }
8558
8559 /// Returns true when we're displaying the edit prediction popover below the cursor
8560 /// like we are not previewing and the LSP autocomplete menu is visible
8561 /// or we are in `when_holding_modifier` mode.
8562 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
8563 if self.edit_prediction_preview_is_active()
8564 || !self.show_edit_predictions_in_menu()
8565 || !self.edit_predictions_enabled()
8566 {
8567 return false;
8568 }
8569
8570 if self.has_visible_completions_menu() {
8571 return true;
8572 }
8573
8574 has_completion && self.edit_prediction_requires_modifier()
8575 }
8576
8577 fn handle_modifiers_changed(
8578 &mut self,
8579 modifiers: Modifiers,
8580 position_map: &PositionMap,
8581 window: &mut Window,
8582 cx: &mut Context<Self>,
8583 ) {
8584 self.update_edit_prediction_settings(cx);
8585
8586 // Ensure that the edit prediction preview is updated, even when not
8587 // enabled, if there's an active edit prediction preview.
8588 if self.show_edit_predictions_in_menu()
8589 || self.edit_prediction_requires_modifier()
8590 || matches!(
8591 self.edit_prediction_preview,
8592 EditPredictionPreview::Active { .. }
8593 )
8594 {
8595 self.update_edit_prediction_preview(&modifiers, window, cx);
8596 }
8597
8598 self.update_selection_mode(&modifiers, position_map, window, cx);
8599
8600 let mouse_position = window.mouse_position();
8601 if !position_map.text_hitbox.is_hovered(window) {
8602 return;
8603 }
8604
8605 self.update_hovered_link(
8606 position_map.point_for_position(mouse_position),
8607 Some(mouse_position),
8608 &position_map.snapshot,
8609 modifiers,
8610 window,
8611 cx,
8612 )
8613 }
8614
8615 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8616 match EditorSettings::get_global(cx).multi_cursor_modifier {
8617 MultiCursorModifier::Alt => modifiers.secondary(),
8618 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
8619 }
8620 }
8621
8622 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8623 match EditorSettings::get_global(cx).multi_cursor_modifier {
8624 MultiCursorModifier::Alt => modifiers.alt,
8625 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
8626 }
8627 }
8628
8629 fn columnar_selection_mode(
8630 modifiers: &Modifiers,
8631 cx: &mut Context<Self>,
8632 ) -> Option<ColumnarMode> {
8633 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
8634 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
8635 Some(ColumnarMode::FromMouse)
8636 } else if Self::is_alt_pressed(modifiers, cx) {
8637 Some(ColumnarMode::FromSelection)
8638 } else {
8639 None
8640 }
8641 } else {
8642 None
8643 }
8644 }
8645
8646 fn update_selection_mode(
8647 &mut self,
8648 modifiers: &Modifiers,
8649 position_map: &PositionMap,
8650 window: &mut Window,
8651 cx: &mut Context<Self>,
8652 ) {
8653 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
8654 return;
8655 };
8656 if self.selections.pending_anchor().is_none() {
8657 return;
8658 }
8659
8660 let mouse_position = window.mouse_position();
8661 let point_for_position = position_map.point_for_position(mouse_position);
8662 let position = point_for_position.previous_valid;
8663
8664 self.select(
8665 SelectPhase::BeginColumnar {
8666 position,
8667 reset: false,
8668 mode,
8669 goal_column: point_for_position.exact_unclipped.column(),
8670 },
8671 window,
8672 cx,
8673 );
8674 }
8675
8676 fn update_edit_prediction_preview(
8677 &mut self,
8678 modifiers: &Modifiers,
8679 window: &mut Window,
8680 cx: &mut Context<Self>,
8681 ) {
8682 let modifiers_held = self.edit_prediction_preview_modifiers_held(modifiers, window, cx);
8683
8684 if modifiers_held {
8685 if matches!(
8686 self.edit_prediction_preview,
8687 EditPredictionPreview::Inactive { .. }
8688 ) {
8689 self.edit_prediction_preview = EditPredictionPreview::Active {
8690 previous_scroll_position: None,
8691 since: Instant::now(),
8692 };
8693
8694 self.update_visible_edit_prediction(window, cx);
8695 cx.notify();
8696 }
8697 } else if let EditPredictionPreview::Active {
8698 previous_scroll_position,
8699 since,
8700 } = self.edit_prediction_preview
8701 {
8702 if let (Some(previous_scroll_position), Some(position_map)) =
8703 (previous_scroll_position, self.last_position_map.as_ref())
8704 {
8705 self.set_scroll_position(
8706 previous_scroll_position
8707 .scroll_position(&position_map.snapshot.display_snapshot),
8708 window,
8709 cx,
8710 );
8711 }
8712
8713 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8714 released_too_fast: since.elapsed() < Duration::from_millis(200),
8715 };
8716 self.clear_row_highlights::<EditPredictionPreview>();
8717 self.update_visible_edit_prediction(window, cx);
8718 cx.notify();
8719 }
8720 }
8721
8722 fn update_visible_edit_prediction(
8723 &mut self,
8724 _window: &mut Window,
8725 cx: &mut Context<Self>,
8726 ) -> Option<()> {
8727 if self.ime_transaction.is_some() {
8728 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8729 return None;
8730 }
8731
8732 let selection = self.selections.newest_anchor();
8733 let multibuffer = self.buffer.read(cx).snapshot(cx);
8734 let cursor = selection.head();
8735 let (cursor_text_anchor, _) = multibuffer.anchor_to_buffer_anchor(cursor)?;
8736 let buffer = self.buffer.read(cx).buffer(cursor_text_anchor.buffer_id)?;
8737
8738 // Check project-level disable_ai setting for the current buffer
8739 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
8740 return None;
8741 }
8742 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8743
8744 let show_in_menu = self.show_edit_predictions_in_menu();
8745 let completions_menu_has_precedence = !show_in_menu
8746 && (self.context_menu.borrow().is_some()
8747 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8748
8749 if completions_menu_has_precedence
8750 || !offset_selection.is_empty()
8751 || self
8752 .active_edit_prediction
8753 .as_ref()
8754 .is_some_and(|completion| {
8755 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8756 return false;
8757 };
8758 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8759 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8760 !invalidation_range.contains(&offset_selection.head())
8761 })
8762 {
8763 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8764 return None;
8765 }
8766
8767 self.take_active_edit_prediction(true, cx);
8768 let Some(provider) = self.edit_prediction_provider() else {
8769 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8770 return None;
8771 };
8772
8773 self.edit_prediction_settings =
8774 self.edit_prediction_settings_at_position(&buffer, cursor_text_anchor, cx);
8775
8776 self.in_leading_whitespace = multibuffer.is_line_whitespace_upto(cursor);
8777
8778 if self.in_leading_whitespace {
8779 let cursor_point = cursor.to_point(&multibuffer);
8780 let mut suggested_indent = None;
8781 multibuffer.suggested_indents_callback(
8782 cursor_point.row..cursor_point.row + 1,
8783 &mut |_, indent| {
8784 suggested_indent = Some(indent);
8785 ControlFlow::Break(())
8786 },
8787 cx,
8788 );
8789
8790 if let Some(indent) = suggested_indent
8791 && indent.len == cursor_point.column
8792 {
8793 self.in_leading_whitespace = false;
8794 }
8795 }
8796
8797 let edit_prediction = provider.suggest(&buffer, cursor_text_anchor, cx)?;
8798
8799 let (completion_id, edits, predicted_cursor_position, edit_preview) = match edit_prediction
8800 {
8801 edit_prediction_types::EditPrediction::Local {
8802 id,
8803 edits,
8804 cursor_position,
8805 edit_preview,
8806 } => (id, edits, cursor_position, edit_preview),
8807 edit_prediction_types::EditPrediction::Jump {
8808 id,
8809 snapshot,
8810 target,
8811 } => {
8812 if let Some(provider) = &self.edit_prediction_provider {
8813 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8814 }
8815 self.stale_edit_prediction_in_menu = None;
8816 self.active_edit_prediction = Some(EditPredictionState {
8817 inlay_ids: vec![],
8818 completion: EditPrediction::MoveOutside { snapshot, target },
8819 completion_id: id,
8820 invalidation_range: None,
8821 });
8822 cx.notify();
8823 return Some(());
8824 }
8825 };
8826
8827 let edits = edits
8828 .into_iter()
8829 .flat_map(|(range, new_text)| {
8830 Some((
8831 multibuffer.buffer_anchor_range_to_anchor_range(range)?,
8832 new_text,
8833 ))
8834 })
8835 .collect::<Vec<_>>();
8836 if edits.is_empty() {
8837 return None;
8838 }
8839
8840 let cursor_position = predicted_cursor_position.and_then(|predicted| {
8841 let anchor = multibuffer.anchor_in_excerpt(predicted.anchor)?;
8842 Some((anchor, predicted.offset))
8843 });
8844
8845 let first_edit_start = edits.first().unwrap().0.start;
8846 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8847 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8848
8849 let last_edit_end = edits.last().unwrap().0.end;
8850 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8851 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8852
8853 let cursor_row = cursor.to_point(&multibuffer).row;
8854
8855 let snapshot = multibuffer
8856 .buffer_for_id(cursor_text_anchor.buffer_id)
8857 .cloned()?;
8858
8859 let mut inlay_ids = Vec::new();
8860 let invalidation_row_range;
8861 let move_invalidation_row_range = if cursor_row < edit_start_row {
8862 Some(cursor_row..edit_end_row)
8863 } else if cursor_row > edit_end_row {
8864 Some(edit_start_row..cursor_row)
8865 } else {
8866 None
8867 };
8868 let supports_jump = self
8869 .edit_prediction_provider
8870 .as_ref()
8871 .map(|provider| provider.provider.supports_jump_to_edit())
8872 .unwrap_or(true);
8873
8874 let is_move = supports_jump
8875 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8876 let completion = if is_move {
8877 if let Some(provider) = &self.edit_prediction_provider {
8878 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8879 }
8880 invalidation_row_range =
8881 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8882 let target = first_edit_start;
8883 EditPrediction::MoveWithin { target, snapshot }
8884 } else {
8885 let show_completions_in_menu = self.has_visible_completions_menu();
8886 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8887 && !self.edit_predictions_hidden_for_vim_mode;
8888
8889 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8890 if provider.show_tab_accept_marker() {
8891 EditDisplayMode::TabAccept
8892 } else {
8893 EditDisplayMode::Inline
8894 }
8895 } else {
8896 EditDisplayMode::DiffPopover
8897 };
8898
8899 let report_shown = match display_mode {
8900 EditDisplayMode::DiffPopover | EditDisplayMode::Inline => {
8901 show_completions_in_buffer || show_completions_in_menu
8902 }
8903 EditDisplayMode::TabAccept => {
8904 show_completions_in_menu || self.edit_prediction_preview_is_active()
8905 }
8906 };
8907
8908 if report_shown && let Some(provider) = &self.edit_prediction_provider {
8909 let suggestion_display_type = match display_mode {
8910 EditDisplayMode::DiffPopover => SuggestionDisplayType::DiffPopover,
8911 EditDisplayMode::Inline | EditDisplayMode::TabAccept => {
8912 SuggestionDisplayType::GhostText
8913 }
8914 };
8915 provider.provider.did_show(suggestion_display_type, cx);
8916 }
8917
8918 if show_completions_in_buffer {
8919 if edits
8920 .iter()
8921 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8922 {
8923 let mut inlays = Vec::new();
8924 for (range, new_text) in &edits {
8925 let inlay = Inlay::edit_prediction(
8926 post_inc(&mut self.next_inlay_id),
8927 range.start,
8928 new_text.as_ref(),
8929 );
8930 inlay_ids.push(inlay.id);
8931 inlays.push(inlay);
8932 }
8933
8934 self.splice_inlays(&[], inlays, cx);
8935 } else {
8936 let background_color = cx.theme().status().deleted_background;
8937 self.highlight_text(
8938 HighlightKey::EditPredictionHighlight,
8939 edits.iter().map(|(range, _)| range.clone()).collect(),
8940 HighlightStyle {
8941 background_color: Some(background_color),
8942 ..Default::default()
8943 },
8944 cx,
8945 );
8946 }
8947 }
8948
8949 invalidation_row_range = edit_start_row..edit_end_row;
8950
8951 EditPrediction::Edit {
8952 edits,
8953 cursor_position,
8954 edit_preview,
8955 display_mode,
8956 snapshot,
8957 }
8958 };
8959
8960 let invalidation_range = multibuffer
8961 .anchor_before(Point::new(invalidation_row_range.start, 0))
8962 ..multibuffer.anchor_after(Point::new(
8963 invalidation_row_range.end,
8964 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8965 ));
8966
8967 self.stale_edit_prediction_in_menu = None;
8968 self.active_edit_prediction = Some(EditPredictionState {
8969 inlay_ids,
8970 completion,
8971 completion_id,
8972 invalidation_range: Some(invalidation_range),
8973 });
8974
8975 cx.notify();
8976
8977 Some(())
8978 }
8979
8980 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8981 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8982 }
8983
8984 /// Get all display points of breakpoints that will be rendered within editor
8985 ///
8986 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8987 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8988 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8989 fn active_breakpoints(
8990 &self,
8991 range: Range<DisplayRow>,
8992 window: &mut Window,
8993 cx: &mut Context<Self>,
8994 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8995 let mut breakpoint_display_points = HashMap::default();
8996
8997 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8998 return breakpoint_display_points;
8999 };
9000
9001 let snapshot = self.snapshot(window, cx);
9002
9003 let multi_buffer_snapshot = snapshot.buffer_snapshot();
9004
9005 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
9006 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
9007
9008 for (buffer_snapshot, range, _) in
9009 multi_buffer_snapshot.range_to_buffer_ranges(range.start..range.end)
9010 {
9011 let Some(buffer) = self.buffer().read(cx).buffer(buffer_snapshot.remote_id()) else {
9012 continue;
9013 };
9014 let breakpoints = breakpoint_store.read(cx).breakpoints(
9015 &buffer,
9016 Some(
9017 buffer_snapshot.anchor_before(range.start)
9018 ..buffer_snapshot.anchor_after(range.end),
9019 ),
9020 &buffer_snapshot,
9021 cx,
9022 );
9023 for (breakpoint, state) in breakpoints {
9024 let Some(multi_buffer_anchor) =
9025 multi_buffer_snapshot.anchor_in_excerpt(breakpoint.position)
9026 else {
9027 continue;
9028 };
9029 let position = multi_buffer_anchor
9030 .to_point(&multi_buffer_snapshot)
9031 .to_display_point(&snapshot);
9032
9033 breakpoint_display_points.insert(
9034 position.row(),
9035 (multi_buffer_anchor, breakpoint.bp.clone(), state),
9036 );
9037 }
9038 }
9039
9040 breakpoint_display_points
9041 }
9042
9043 fn breakpoint_context_menu(
9044 &self,
9045 anchor: Anchor,
9046 window: &mut Window,
9047 cx: &mut Context<Self>,
9048 ) -> Entity<ui::ContextMenu> {
9049 let weak_editor = cx.weak_entity();
9050 let focus_handle = self.focus_handle(cx);
9051
9052 let row = self
9053 .buffer
9054 .read(cx)
9055 .snapshot(cx)
9056 .summary_for_anchor::<Point>(&anchor)
9057 .row;
9058
9059 let breakpoint = self
9060 .breakpoint_at_row(row, window, cx)
9061 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
9062
9063 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
9064 "Edit Log Breakpoint"
9065 } else {
9066 "Set Log Breakpoint"
9067 };
9068
9069 let condition_breakpoint_msg = if breakpoint
9070 .as_ref()
9071 .is_some_and(|bp| bp.1.condition.is_some())
9072 {
9073 "Edit Condition Breakpoint"
9074 } else {
9075 "Set Condition Breakpoint"
9076 };
9077
9078 let hit_condition_breakpoint_msg = if breakpoint
9079 .as_ref()
9080 .is_some_and(|bp| bp.1.hit_condition.is_some())
9081 {
9082 "Edit Hit Condition Breakpoint"
9083 } else {
9084 "Set Hit Condition Breakpoint"
9085 };
9086
9087 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
9088 "Unset Breakpoint"
9089 } else {
9090 "Set Breakpoint"
9091 };
9092
9093 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
9094
9095 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
9096 BreakpointState::Enabled => Some("Disable"),
9097 BreakpointState::Disabled => Some("Enable"),
9098 });
9099
9100 let (anchor, breakpoint) =
9101 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
9102
9103 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
9104 menu.on_blur_subscription(Subscription::new(|| {}))
9105 .context(focus_handle)
9106 .when(run_to_cursor, |this| {
9107 let weak_editor = weak_editor.clone();
9108 this.entry("Run to Cursor", None, move |window, cx| {
9109 weak_editor
9110 .update(cx, |editor, cx| {
9111 editor.change_selections(
9112 SelectionEffects::no_scroll(),
9113 window,
9114 cx,
9115 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
9116 );
9117 })
9118 .ok();
9119
9120 window.dispatch_action(Box::new(RunToCursor), cx);
9121 })
9122 .separator()
9123 })
9124 .when_some(toggle_state_msg, |this, msg| {
9125 this.entry(msg, None, {
9126 let weak_editor = weak_editor.clone();
9127 let breakpoint = breakpoint.clone();
9128 move |_window, cx| {
9129 weak_editor
9130 .update(cx, |this, cx| {
9131 this.edit_breakpoint_at_anchor(
9132 anchor,
9133 breakpoint.as_ref().clone(),
9134 BreakpointEditAction::InvertState,
9135 cx,
9136 );
9137 })
9138 .log_err();
9139 }
9140 })
9141 })
9142 .entry(set_breakpoint_msg, None, {
9143 let weak_editor = weak_editor.clone();
9144 let breakpoint = breakpoint.clone();
9145 move |_window, cx| {
9146 weak_editor
9147 .update(cx, |this, cx| {
9148 this.edit_breakpoint_at_anchor(
9149 anchor,
9150 breakpoint.as_ref().clone(),
9151 BreakpointEditAction::Toggle,
9152 cx,
9153 );
9154 })
9155 .log_err();
9156 }
9157 })
9158 .entry(log_breakpoint_msg, None, {
9159 let breakpoint = breakpoint.clone();
9160 let weak_editor = weak_editor.clone();
9161 move |window, cx| {
9162 weak_editor
9163 .update(cx, |this, cx| {
9164 this.add_edit_breakpoint_block(
9165 anchor,
9166 breakpoint.as_ref(),
9167 BreakpointPromptEditAction::Log,
9168 window,
9169 cx,
9170 );
9171 })
9172 .log_err();
9173 }
9174 })
9175 .entry(condition_breakpoint_msg, None, {
9176 let breakpoint = breakpoint.clone();
9177 let weak_editor = weak_editor.clone();
9178 move |window, cx| {
9179 weak_editor
9180 .update(cx, |this, cx| {
9181 this.add_edit_breakpoint_block(
9182 anchor,
9183 breakpoint.as_ref(),
9184 BreakpointPromptEditAction::Condition,
9185 window,
9186 cx,
9187 );
9188 })
9189 .log_err();
9190 }
9191 })
9192 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
9193 weak_editor
9194 .update(cx, |this, cx| {
9195 this.add_edit_breakpoint_block(
9196 anchor,
9197 breakpoint.as_ref(),
9198 BreakpointPromptEditAction::HitCondition,
9199 window,
9200 cx,
9201 );
9202 })
9203 .log_err();
9204 })
9205 })
9206 }
9207
9208 fn render_breakpoint(
9209 &self,
9210 position: Anchor,
9211 row: DisplayRow,
9212 breakpoint: &Breakpoint,
9213 state: Option<BreakpointSessionState>,
9214 cx: &mut Context<Self>,
9215 ) -> IconButton {
9216 let is_rejected = state.is_some_and(|s| !s.verified);
9217 // Is it a breakpoint that shows up when hovering over gutter?
9218 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
9219 (false, false),
9220 |PhantomBreakpointIndicator {
9221 is_active,
9222 display_row,
9223 collides_with_existing_breakpoint,
9224 }| {
9225 (
9226 is_active && display_row == row,
9227 collides_with_existing_breakpoint,
9228 )
9229 },
9230 );
9231
9232 let (color, icon) = {
9233 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
9234 (false, false) => ui::IconName::DebugBreakpoint,
9235 (true, false) => ui::IconName::DebugLogBreakpoint,
9236 (false, true) => ui::IconName::DebugDisabledBreakpoint,
9237 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
9238 };
9239
9240 let theme_colors = cx.theme().colors();
9241
9242 let color = if is_phantom {
9243 if collides_with_existing {
9244 Color::Custom(
9245 theme_colors
9246 .debugger_accent
9247 .blend(theme_colors.text.opacity(0.6)),
9248 )
9249 } else {
9250 Color::Hint
9251 }
9252 } else if is_rejected {
9253 Color::Disabled
9254 } else {
9255 Color::Debugger
9256 };
9257
9258 (color, icon)
9259 };
9260
9261 let breakpoint = Arc::from(breakpoint.clone());
9262
9263 let alt_as_text = gpui::Keystroke {
9264 modifiers: Modifiers::secondary_key(),
9265 ..Default::default()
9266 };
9267 let primary_action_text = if breakpoint.is_disabled() {
9268 "Enable breakpoint"
9269 } else if is_phantom && !collides_with_existing {
9270 "Set breakpoint"
9271 } else {
9272 "Unset breakpoint"
9273 };
9274 let focus_handle = self.focus_handle.clone();
9275
9276 let meta = if is_rejected {
9277 SharedString::from("No executable code is associated with this line.")
9278 } else if collides_with_existing && !breakpoint.is_disabled() {
9279 SharedString::from(format!(
9280 "{alt_as_text}-click to disable,\nright-click for more options."
9281 ))
9282 } else {
9283 SharedString::from("Right-click for more options.")
9284 };
9285 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
9286 .icon_size(IconSize::XSmall)
9287 .size(ui::ButtonSize::None)
9288 .when(is_rejected, |this| {
9289 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
9290 })
9291 .icon_color(color)
9292 .style(ButtonStyle::Transparent)
9293 .on_click(cx.listener({
9294 move |editor, event: &ClickEvent, window, cx| {
9295 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
9296 BreakpointEditAction::InvertState
9297 } else {
9298 BreakpointEditAction::Toggle
9299 };
9300
9301 window.focus(&editor.focus_handle(cx), cx);
9302 editor.update_breakpoint_collision_on_toggle(row, &edit_action);
9303 editor.edit_breakpoint_at_anchor(
9304 position,
9305 breakpoint.as_ref().clone(),
9306 edit_action,
9307 cx,
9308 );
9309 }
9310 }))
9311 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
9312 editor.set_breakpoint_context_menu(
9313 row,
9314 Some(position),
9315 event.position(),
9316 window,
9317 cx,
9318 );
9319 }))
9320 .tooltip(move |_window, cx| {
9321 Tooltip::with_meta_in(
9322 primary_action_text,
9323 Some(&ToggleBreakpoint),
9324 meta.clone(),
9325 &focus_handle,
9326 cx,
9327 )
9328 })
9329 }
9330
9331 fn build_tasks_context(
9332 project: &Entity<Project>,
9333 buffer: &Entity<Buffer>,
9334 buffer_row: u32,
9335 tasks: &Arc<RunnableTasks>,
9336 cx: &mut Context<Self>,
9337 ) -> Task<Option<task::TaskContext>> {
9338 let position = Point::new(buffer_row, tasks.column);
9339 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
9340 let location = Location {
9341 buffer: buffer.clone(),
9342 range: range_start..range_start,
9343 };
9344 // Fill in the environmental variables from the tree-sitter captures
9345 let mut captured_task_variables = TaskVariables::default();
9346 for (capture_name, value) in tasks.extra_variables.clone() {
9347 captured_task_variables.insert(
9348 task::VariableName::Custom(capture_name.into()),
9349 value.clone(),
9350 );
9351 }
9352 project.update(cx, |project, cx| {
9353 project.task_store().update(cx, |task_store, cx| {
9354 task_store.task_context_for_location(captured_task_variables, location, cx)
9355 })
9356 })
9357 }
9358
9359 pub fn context_menu_visible(&self) -> bool {
9360 !self.edit_prediction_preview_is_active()
9361 && self
9362 .context_menu
9363 .borrow()
9364 .as_ref()
9365 .is_some_and(|menu| menu.visible())
9366 }
9367
9368 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
9369 self.context_menu
9370 .borrow()
9371 .as_ref()
9372 .map(|menu| menu.origin())
9373 }
9374
9375 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
9376 self.context_menu_options = Some(options);
9377 }
9378
9379 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
9380 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
9381
9382 fn render_edit_prediction_popover(
9383 &mut self,
9384 text_bounds: &Bounds<Pixels>,
9385 content_origin: gpui::Point<Pixels>,
9386 right_margin: Pixels,
9387 editor_snapshot: &EditorSnapshot,
9388 visible_row_range: Range<DisplayRow>,
9389 scroll_top: ScrollOffset,
9390 scroll_bottom: ScrollOffset,
9391 line_layouts: &[LineWithInvisibles],
9392 line_height: Pixels,
9393 scroll_position: gpui::Point<ScrollOffset>,
9394 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9395 newest_selection_head: Option<DisplayPoint>,
9396 editor_width: Pixels,
9397 style: &EditorStyle,
9398 window: &mut Window,
9399 cx: &mut App,
9400 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9401 if self.mode().is_minimap() {
9402 return None;
9403 }
9404 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
9405
9406 if self.edit_prediction_visible_in_cursor_popover(true) {
9407 return None;
9408 }
9409
9410 match &active_edit_prediction.completion {
9411 EditPrediction::MoveWithin { target, .. } => {
9412 let target_display_point = target.to_display_point(editor_snapshot);
9413
9414 if self.edit_prediction_requires_modifier() {
9415 if !self.edit_prediction_preview_is_active() {
9416 return None;
9417 }
9418
9419 self.render_edit_prediction_modifier_jump_popover(
9420 text_bounds,
9421 content_origin,
9422 visible_row_range,
9423 line_layouts,
9424 line_height,
9425 scroll_pixel_position,
9426 newest_selection_head,
9427 target_display_point,
9428 window,
9429 cx,
9430 )
9431 } else {
9432 self.render_edit_prediction_eager_jump_popover(
9433 text_bounds,
9434 content_origin,
9435 editor_snapshot,
9436 visible_row_range,
9437 scroll_top,
9438 scroll_bottom,
9439 line_height,
9440 scroll_pixel_position,
9441 target_display_point,
9442 editor_width,
9443 window,
9444 cx,
9445 )
9446 }
9447 }
9448 EditPrediction::Edit {
9449 display_mode: EditDisplayMode::Inline,
9450 ..
9451 } => None,
9452 EditPrediction::Edit {
9453 display_mode: EditDisplayMode::TabAccept,
9454 edits,
9455 ..
9456 } => {
9457 let range = &edits.first()?.0;
9458 let target_display_point = range.end.to_display_point(editor_snapshot);
9459
9460 self.render_edit_prediction_end_of_line_popover(
9461 "Accept",
9462 editor_snapshot,
9463 visible_row_range,
9464 target_display_point,
9465 line_height,
9466 scroll_pixel_position,
9467 content_origin,
9468 editor_width,
9469 window,
9470 cx,
9471 )
9472 }
9473 EditPrediction::Edit {
9474 edits,
9475 edit_preview,
9476 display_mode: EditDisplayMode::DiffPopover,
9477 snapshot,
9478 ..
9479 } => self.render_edit_prediction_diff_popover(
9480 text_bounds,
9481 content_origin,
9482 right_margin,
9483 editor_snapshot,
9484 visible_row_range,
9485 line_layouts,
9486 line_height,
9487 scroll_position,
9488 scroll_pixel_position,
9489 newest_selection_head,
9490 editor_width,
9491 style,
9492 edits,
9493 edit_preview,
9494 snapshot,
9495 window,
9496 cx,
9497 ),
9498 EditPrediction::MoveOutside { snapshot, .. } => {
9499 let mut element = self
9500 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
9501 .into_any();
9502
9503 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9504 let origin_x = text_bounds.size.width - size.width - px(30.);
9505 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
9506 element.prepaint_at(origin, window, cx);
9507
9508 Some((element, origin))
9509 }
9510 }
9511 }
9512
9513 fn render_edit_prediction_modifier_jump_popover(
9514 &mut self,
9515 text_bounds: &Bounds<Pixels>,
9516 content_origin: gpui::Point<Pixels>,
9517 visible_row_range: Range<DisplayRow>,
9518 line_layouts: &[LineWithInvisibles],
9519 line_height: Pixels,
9520 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9521 newest_selection_head: Option<DisplayPoint>,
9522 target_display_point: DisplayPoint,
9523 window: &mut Window,
9524 cx: &mut App,
9525 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9526 let scrolled_content_origin =
9527 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
9528
9529 const SCROLL_PADDING_Y: Pixels = px(12.);
9530
9531 if target_display_point.row() < visible_row_range.start {
9532 return self.render_edit_prediction_scroll_popover(
9533 &|_| SCROLL_PADDING_Y,
9534 IconName::ArrowUp,
9535 visible_row_range,
9536 line_layouts,
9537 newest_selection_head,
9538 scrolled_content_origin,
9539 window,
9540 cx,
9541 );
9542 } else if target_display_point.row() >= visible_row_range.end {
9543 return self.render_edit_prediction_scroll_popover(
9544 &|size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
9545 IconName::ArrowDown,
9546 visible_row_range,
9547 line_layouts,
9548 newest_selection_head,
9549 scrolled_content_origin,
9550 window,
9551 cx,
9552 );
9553 }
9554
9555 const POLE_WIDTH: Pixels = px(2.);
9556
9557 let line_layout =
9558 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
9559 let target_column = target_display_point.column() as usize;
9560
9561 let target_x = line_layout.x_for_index(target_column);
9562 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
9563 - scroll_pixel_position.y;
9564
9565 let flag_on_right = target_x < text_bounds.size.width / 2.;
9566
9567 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
9568 border_color.l += 0.001;
9569
9570 let mut element = v_flex()
9571 .items_end()
9572 .when(flag_on_right, |el| el.items_start())
9573 .child(if flag_on_right {
9574 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9575 .rounded_bl(px(0.))
9576 .rounded_tl(px(0.))
9577 .border_l_2()
9578 .border_color(border_color)
9579 } else {
9580 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9581 .rounded_br(px(0.))
9582 .rounded_tr(px(0.))
9583 .border_r_2()
9584 .border_color(border_color)
9585 })
9586 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9587 .into_any();
9588
9589 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9590
9591 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9592 - point(
9593 if flag_on_right {
9594 POLE_WIDTH
9595 } else {
9596 size.width - POLE_WIDTH
9597 },
9598 size.height - line_height,
9599 );
9600
9601 origin.x = origin.x.max(content_origin.x);
9602
9603 element.prepaint_at(origin, window, cx);
9604
9605 Some((element, origin))
9606 }
9607
9608 fn render_edit_prediction_scroll_popover(
9609 &mut self,
9610 to_y: &dyn Fn(Size<Pixels>) -> Pixels,
9611 scroll_icon: IconName,
9612 visible_row_range: Range<DisplayRow>,
9613 line_layouts: &[LineWithInvisibles],
9614 newest_selection_head: Option<DisplayPoint>,
9615 scrolled_content_origin: gpui::Point<Pixels>,
9616 window: &mut Window,
9617 cx: &mut App,
9618 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9619 let mut element = self
9620 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9621 .into_any();
9622
9623 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9624
9625 let cursor = newest_selection_head?;
9626 let cursor_row_layout =
9627 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9628 let cursor_column = cursor.column() as usize;
9629
9630 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9631
9632 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9633
9634 element.prepaint_at(origin, window, cx);
9635 Some((element, origin))
9636 }
9637
9638 fn render_edit_prediction_eager_jump_popover(
9639 &mut self,
9640 text_bounds: &Bounds<Pixels>,
9641 content_origin: gpui::Point<Pixels>,
9642 editor_snapshot: &EditorSnapshot,
9643 visible_row_range: Range<DisplayRow>,
9644 scroll_top: ScrollOffset,
9645 scroll_bottom: ScrollOffset,
9646 line_height: Pixels,
9647 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9648 target_display_point: DisplayPoint,
9649 editor_width: Pixels,
9650 window: &mut Window,
9651 cx: &mut App,
9652 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9653 if target_display_point.row().as_f64() < scroll_top {
9654 let mut element = self
9655 .render_edit_prediction_line_popover(
9656 "Jump to Edit",
9657 Some(IconName::ArrowUp),
9658 window,
9659 cx,
9660 )
9661 .into_any();
9662
9663 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9664 let offset = point(
9665 (text_bounds.size.width - size.width) / 2.,
9666 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9667 );
9668
9669 let origin = text_bounds.origin + offset;
9670 element.prepaint_at(origin, window, cx);
9671 Some((element, origin))
9672 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9673 let mut element = self
9674 .render_edit_prediction_line_popover(
9675 "Jump to Edit",
9676 Some(IconName::ArrowDown),
9677 window,
9678 cx,
9679 )
9680 .into_any();
9681
9682 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9683 let offset = point(
9684 (text_bounds.size.width - size.width) / 2.,
9685 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9686 );
9687
9688 let origin = text_bounds.origin + offset;
9689 element.prepaint_at(origin, window, cx);
9690 Some((element, origin))
9691 } else {
9692 self.render_edit_prediction_end_of_line_popover(
9693 "Jump to Edit",
9694 editor_snapshot,
9695 visible_row_range,
9696 target_display_point,
9697 line_height,
9698 scroll_pixel_position,
9699 content_origin,
9700 editor_width,
9701 window,
9702 cx,
9703 )
9704 }
9705 }
9706
9707 fn render_edit_prediction_end_of_line_popover(
9708 self: &mut Editor,
9709 label: &'static str,
9710 editor_snapshot: &EditorSnapshot,
9711 visible_row_range: Range<DisplayRow>,
9712 target_display_point: DisplayPoint,
9713 line_height: Pixels,
9714 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9715 content_origin: gpui::Point<Pixels>,
9716 editor_width: Pixels,
9717 window: &mut Window,
9718 cx: &mut App,
9719 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9720 let target_line_end = DisplayPoint::new(
9721 target_display_point.row(),
9722 editor_snapshot.line_len(target_display_point.row()),
9723 );
9724
9725 let mut element = self
9726 .render_edit_prediction_line_popover(label, None, window, cx)
9727 .into_any();
9728
9729 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9730
9731 let line_origin =
9732 self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
9733
9734 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9735 let mut origin = start_point
9736 + line_origin
9737 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9738 origin.x = origin.x.max(content_origin.x);
9739
9740 let max_x = content_origin.x + editor_width - size.width;
9741
9742 if origin.x > max_x {
9743 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9744
9745 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9746 origin.y += offset;
9747 IconName::ArrowUp
9748 } else {
9749 origin.y -= offset;
9750 IconName::ArrowDown
9751 };
9752
9753 element = self
9754 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9755 .into_any();
9756
9757 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9758
9759 origin.x = content_origin.x + editor_width - size.width - px(2.);
9760 }
9761
9762 element.prepaint_at(origin, window, cx);
9763 Some((element, origin))
9764 }
9765
9766 fn render_edit_prediction_diff_popover(
9767 self: &Editor,
9768 text_bounds: &Bounds<Pixels>,
9769 content_origin: gpui::Point<Pixels>,
9770 right_margin: Pixels,
9771 editor_snapshot: &EditorSnapshot,
9772 visible_row_range: Range<DisplayRow>,
9773 line_layouts: &[LineWithInvisibles],
9774 line_height: Pixels,
9775 scroll_position: gpui::Point<ScrollOffset>,
9776 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9777 newest_selection_head: Option<DisplayPoint>,
9778 editor_width: Pixels,
9779 style: &EditorStyle,
9780 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9781 edit_preview: &Option<language::EditPreview>,
9782 snapshot: &language::BufferSnapshot,
9783 window: &mut Window,
9784 cx: &mut App,
9785 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9786 let edit_start = edits
9787 .first()
9788 .unwrap()
9789 .0
9790 .start
9791 .to_display_point(editor_snapshot);
9792 let edit_end = edits
9793 .last()
9794 .unwrap()
9795 .0
9796 .end
9797 .to_display_point(editor_snapshot);
9798
9799 let is_visible = visible_row_range.contains(&edit_start.row())
9800 || visible_row_range.contains(&edit_end.row());
9801 if !is_visible {
9802 return None;
9803 }
9804
9805 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9806 crate::edit_prediction_edit_text(
9807 snapshot,
9808 edits,
9809 edit_preview,
9810 false,
9811 editor_snapshot.buffer_snapshot(),
9812 cx,
9813 )
9814 } else {
9815 // Fallback for providers without edit_preview
9816 crate::edit_prediction_fallback_text(edits, cx)
9817 };
9818
9819 let styled_text = highlighted_edits.to_styled_text(&style.text);
9820 let line_count = highlighted_edits.text.lines().count();
9821
9822 const BORDER_WIDTH: Pixels = px(1.);
9823
9824 let keybind = self.render_edit_prediction_keybind(window, cx);
9825 let has_keybind = keybind.is_some();
9826
9827 let mut element = h_flex()
9828 .items_start()
9829 .child(
9830 h_flex()
9831 .bg(cx.theme().colors().editor_background)
9832 .border(BORDER_WIDTH)
9833 .shadow_xs()
9834 .border_color(cx.theme().colors().border)
9835 .rounded_l_lg()
9836 .when(line_count > 1, |el| el.rounded_br_lg())
9837 .pr_1()
9838 .child(styled_text),
9839 )
9840 .child(
9841 h_flex()
9842 .h(line_height + BORDER_WIDTH * 2.)
9843 .px_1p5()
9844 .gap_1()
9845 // Workaround: For some reason, there's a gap if we don't do this
9846 .ml(-BORDER_WIDTH)
9847 .shadow(vec![gpui::BoxShadow {
9848 color: gpui::black().opacity(0.05),
9849 offset: point(px(1.), px(1.)),
9850 blur_radius: px(2.),
9851 spread_radius: px(0.),
9852 }])
9853 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9854 .border(BORDER_WIDTH)
9855 .border_color(cx.theme().colors().border)
9856 .rounded_r_lg()
9857 .id("edit_prediction_diff_popover_keybind")
9858 .when(!has_keybind, |el| {
9859 let status_colors = cx.theme().status();
9860
9861 el.bg(status_colors.error_background)
9862 .border_color(status_colors.error.opacity(0.6))
9863 .child(Icon::new(IconName::Info).color(Color::Error))
9864 .cursor_default()
9865 .hoverable_tooltip(move |_window, cx| {
9866 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9867 })
9868 })
9869 .children(keybind),
9870 )
9871 .into_any();
9872
9873 let longest_row =
9874 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9875 let longest_line_width = if visible_row_range.contains(&longest_row) {
9876 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9877 } else {
9878 layout_line(
9879 longest_row,
9880 editor_snapshot,
9881 style,
9882 editor_width,
9883 |_| false,
9884 window,
9885 cx,
9886 )
9887 .width
9888 };
9889
9890 let viewport_bounds =
9891 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9892 right: -right_margin,
9893 ..Default::default()
9894 });
9895
9896 let x_after_longest = Pixels::from(
9897 ScrollPixelOffset::from(
9898 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9899 ) - scroll_pixel_position.x,
9900 );
9901
9902 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9903
9904 // Fully visible if it can be displayed within the window (allow overlapping other
9905 // panes). However, this is only allowed if the popover starts within text_bounds.
9906 let can_position_to_the_right = x_after_longest < text_bounds.right()
9907 && x_after_longest + element_bounds.width < viewport_bounds.right();
9908
9909 let mut origin = if can_position_to_the_right {
9910 point(
9911 x_after_longest,
9912 text_bounds.origin.y
9913 + Pixels::from(
9914 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9915 - scroll_pixel_position.y,
9916 ),
9917 )
9918 } else {
9919 let cursor_row = newest_selection_head.map(|head| head.row());
9920 let above_edit = edit_start
9921 .row()
9922 .0
9923 .checked_sub(line_count as u32)
9924 .map(DisplayRow);
9925 let below_edit = Some(edit_end.row() + 1);
9926 let above_cursor =
9927 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9928 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9929
9930 // Place the edit popover adjacent to the edit if there is a location
9931 // available that is onscreen and does not obscure the cursor. Otherwise,
9932 // place it adjacent to the cursor.
9933 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9934 .into_iter()
9935 .flatten()
9936 .find(|&start_row| {
9937 let end_row = start_row + line_count as u32;
9938 visible_row_range.contains(&start_row)
9939 && visible_row_range.contains(&end_row)
9940 && cursor_row
9941 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9942 })?;
9943
9944 content_origin
9945 + point(
9946 Pixels::from(-scroll_pixel_position.x),
9947 Pixels::from(
9948 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9949 ),
9950 )
9951 };
9952
9953 origin.x -= BORDER_WIDTH;
9954
9955 window.with_content_mask(
9956 Some(gpui::ContentMask {
9957 bounds: *text_bounds,
9958 }),
9959 |window| {
9960 window.defer_draw(element, origin, 1, Some(window.content_mask()));
9961 },
9962 );
9963
9964 // Do not return an element, since it will already be drawn due to defer_draw.
9965 None
9966 }
9967
9968 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9969 px(30.)
9970 }
9971
9972 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9973 if self.read_only(cx) {
9974 cx.theme().players().read_only()
9975 } else {
9976 self.style.as_ref().unwrap().local_player
9977 }
9978 }
9979
9980 fn render_edit_prediction_inline_keystroke(
9981 &self,
9982 keystroke: &gpui::KeybindingKeystroke,
9983 modifiers_color: Color,
9984 cx: &App,
9985 ) -> AnyElement {
9986 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9987
9988 h_flex()
9989 .px_0p5()
9990 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9991 .font(
9992 theme_settings::ThemeSettings::get_global(cx)
9993 .buffer_font
9994 .clone(),
9995 )
9996 .text_size(TextSize::XSmall.rems(cx))
9997 .child(h_flex().children(ui::render_modifiers(
9998 keystroke.modifiers(),
9999 PlatformStyle::platform(),
10000 Some(modifiers_color),
10001 Some(IconSize::XSmall.rems().into()),
10002 true,
10003 )))
10004 .when(is_platform_style_mac, |parent| {
10005 parent.child(keystroke.key().to_string())
10006 })
10007 .when(!is_platform_style_mac, |parent| {
10008 parent.child(
10009 Key::new(ui::utils::capitalize(keystroke.key()), Some(Color::Default))
10010 .size(Some(IconSize::XSmall.rems().into())),
10011 )
10012 })
10013 .into_any()
10014 }
10015
10016 fn render_edit_prediction_popover_keystroke(
10017 &self,
10018 keystroke: &gpui::KeybindingKeystroke,
10019 color: Color,
10020 cx: &App,
10021 ) -> AnyElement {
10022 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
10023
10024 if keystroke.modifiers().modified() {
10025 h_flex()
10026 .font(
10027 theme_settings::ThemeSettings::get_global(cx)
10028 .buffer_font
10029 .clone(),
10030 )
10031 .when(is_platform_style_mac, |parent| parent.gap_1())
10032 .child(h_flex().children(ui::render_modifiers(
10033 keystroke.modifiers(),
10034 PlatformStyle::platform(),
10035 Some(color),
10036 None,
10037 false,
10038 )))
10039 .into_any()
10040 } else {
10041 Key::new(ui::utils::capitalize(keystroke.key()), Some(color))
10042 .size(Some(IconSize::XSmall.rems().into()))
10043 .into_any_element()
10044 }
10045 }
10046
10047 fn render_edit_prediction_keybind(
10048 &self,
10049 window: &mut Window,
10050 cx: &mut App,
10051 ) -> Option<AnyElement> {
10052 let keybind_display =
10053 self.edit_prediction_keybind_display(EditPredictionKeybindSurface::Inline, window, cx);
10054 let keystroke = keybind_display.displayed_keystroke.as_ref()?;
10055
10056 let modifiers_color = if *keystroke.modifiers() == window.modifiers() {
10057 Color::Accent
10058 } else {
10059 Color::Muted
10060 };
10061
10062 Some(self.render_edit_prediction_inline_keystroke(keystroke, modifiers_color, cx))
10063 }
10064
10065 fn render_edit_prediction_line_popover(
10066 &self,
10067 label: impl Into<SharedString>,
10068 icon: Option<IconName>,
10069 window: &mut Window,
10070 cx: &mut App,
10071 ) -> Stateful<Div> {
10072 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
10073
10074 let keybind = self.render_edit_prediction_keybind(window, cx);
10075 let has_keybind = keybind.is_some();
10076 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10077
10078 h_flex()
10079 .id("ep-line-popover")
10080 .py_0p5()
10081 .pl_1()
10082 .pr(padding_right)
10083 .gap_1()
10084 .rounded_md()
10085 .border_1()
10086 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10087 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
10088 .shadow_xs()
10089 .when(!has_keybind, |el| {
10090 let status_colors = cx.theme().status();
10091
10092 el.bg(status_colors.error_background)
10093 .border_color(status_colors.error.opacity(0.6))
10094 .pl_2()
10095 .child(Icon::new(icons.error).color(Color::Error))
10096 .cursor_default()
10097 .hoverable_tooltip(move |_window, cx| {
10098 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
10099 })
10100 })
10101 .children(keybind)
10102 .child(
10103 Label::new(label)
10104 .size(LabelSize::Small)
10105 .when(!has_keybind, |el| {
10106 el.color(cx.theme().status().error.into()).strikethrough()
10107 }),
10108 )
10109 .when(!has_keybind, |el| {
10110 el.child(
10111 h_flex().ml_1().child(
10112 Icon::new(IconName::Info)
10113 .size(IconSize::Small)
10114 .color(cx.theme().status().error.into()),
10115 ),
10116 )
10117 })
10118 .when_some(icon, |element, icon| {
10119 element.child(
10120 div()
10121 .mt(px(1.5))
10122 .child(Icon::new(icon).size(IconSize::Small)),
10123 )
10124 })
10125 }
10126
10127 fn render_edit_prediction_jump_outside_popover(
10128 &self,
10129 snapshot: &BufferSnapshot,
10130 window: &mut Window,
10131 cx: &mut App,
10132 ) -> Stateful<Div> {
10133 let keybind = self.render_edit_prediction_keybind(window, cx);
10134 let has_keybind = keybind.is_some();
10135 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10136
10137 let file_name = snapshot
10138 .file()
10139 .map(|file| SharedString::new(file.file_name(cx)))
10140 .unwrap_or(SharedString::new_static("untitled"));
10141
10142 h_flex()
10143 .id("ep-jump-outside-popover")
10144 .py_1()
10145 .px_2()
10146 .gap_1()
10147 .rounded_md()
10148 .border_1()
10149 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10150 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
10151 .shadow_xs()
10152 .when(!has_keybind, |el| {
10153 let status_colors = cx.theme().status();
10154
10155 el.bg(status_colors.error_background)
10156 .border_color(status_colors.error.opacity(0.6))
10157 .pl_2()
10158 .child(Icon::new(icons.error).color(Color::Error))
10159 .cursor_default()
10160 .hoverable_tooltip(move |_window, cx| {
10161 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
10162 })
10163 })
10164 .children(keybind)
10165 .child(
10166 Label::new(file_name)
10167 .size(LabelSize::Small)
10168 .buffer_font(cx)
10169 .when(!has_keybind, |el| {
10170 el.color(cx.theme().status().error.into()).strikethrough()
10171 }),
10172 )
10173 .when(!has_keybind, |el| {
10174 el.child(
10175 h_flex().ml_1().child(
10176 Icon::new(IconName::Info)
10177 .size(IconSize::Small)
10178 .color(cx.theme().status().error.into()),
10179 ),
10180 )
10181 })
10182 .child(
10183 div()
10184 .mt(px(1.5))
10185 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
10186 )
10187 }
10188
10189 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
10190 let accent_color = cx.theme().colors().text_accent;
10191 let editor_bg_color = cx.theme().colors().editor_background;
10192 editor_bg_color.blend(accent_color.opacity(0.1))
10193 }
10194
10195 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
10196 let accent_color = cx.theme().colors().text_accent;
10197 let editor_bg_color = cx.theme().colors().editor_background;
10198 editor_bg_color.blend(accent_color.opacity(0.6))
10199 }
10200 fn get_prediction_provider_icons(
10201 provider: &Option<RegisteredEditPredictionDelegate>,
10202 cx: &App,
10203 ) -> edit_prediction_types::EditPredictionIconSet {
10204 match provider {
10205 Some(provider) => provider.provider.icons(cx),
10206 None => edit_prediction_types::EditPredictionIconSet::new(IconName::ZedPredict),
10207 }
10208 }
10209
10210 fn render_edit_prediction_cursor_popover(
10211 &self,
10212 min_width: Pixels,
10213 max_width: Pixels,
10214 cursor_point: Point,
10215 style: &EditorStyle,
10216 window: &mut Window,
10217 cx: &mut Context<Editor>,
10218 ) -> Option<AnyElement> {
10219 let provider = self.edit_prediction_provider.as_ref()?;
10220 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10221
10222 let is_refreshing = provider.provider.is_refreshing(cx);
10223
10224 fn pending_completion_container(icon: IconName) -> Div {
10225 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
10226 }
10227
10228 let completion = match &self.active_edit_prediction {
10229 Some(prediction) => {
10230 if !self.has_visible_completions_menu() {
10231 const RADIUS: Pixels = px(6.);
10232 const BORDER_WIDTH: Pixels = px(1.);
10233 let keybind_display = self.edit_prediction_keybind_display(
10234 EditPredictionKeybindSurface::CursorPopoverCompact,
10235 window,
10236 cx,
10237 );
10238
10239 return Some(
10240 h_flex()
10241 .elevation_2(cx)
10242 .border(BORDER_WIDTH)
10243 .border_color(cx.theme().colors().border)
10244 .when(keybind_display.missing_accept_keystroke, |el| {
10245 el.border_color(cx.theme().status().error)
10246 })
10247 .rounded(RADIUS)
10248 .rounded_tl(px(0.))
10249 .overflow_hidden()
10250 .child(div().px_1p5().child(match &prediction.completion {
10251 EditPrediction::MoveWithin { target, snapshot } => {
10252 use text::ToPoint as _;
10253 if target.text_anchor_in(&snapshot).to_point(snapshot).row
10254 > cursor_point.row
10255 {
10256 Icon::new(icons.down)
10257 } else {
10258 Icon::new(icons.up)
10259 }
10260 }
10261 EditPrediction::MoveOutside { .. } => {
10262 // TODO [zeta2] custom icon for external jump?
10263 Icon::new(icons.base)
10264 }
10265 EditPrediction::Edit { .. } => Icon::new(icons.base),
10266 }))
10267 .child(
10268 h_flex()
10269 .gap_1()
10270 .py_1()
10271 .px_2()
10272 .rounded_r(RADIUS - BORDER_WIDTH)
10273 .border_l_1()
10274 .border_color(cx.theme().colors().border)
10275 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10276 .when(keybind_display.show_hold_label, |el| {
10277 el.child(
10278 Label::new("Hold")
10279 .size(LabelSize::Small)
10280 .when(
10281 keybind_display.missing_accept_keystroke,
10282 |el| el.strikethrough(),
10283 )
10284 .line_height_style(LineHeightStyle::UiLabel),
10285 )
10286 })
10287 .id("edit_prediction_cursor_popover_keybind")
10288 .when(keybind_display.missing_accept_keystroke, |el| {
10289 let status_colors = cx.theme().status();
10290
10291 el.bg(status_colors.error_background)
10292 .border_color(status_colors.error.opacity(0.6))
10293 .child(Icon::new(IconName::Info).color(Color::Error))
10294 .cursor_default()
10295 .hoverable_tooltip(move |_window, cx| {
10296 cx.new(|_| MissingEditPredictionKeybindingTooltip)
10297 .into()
10298 })
10299 })
10300 .when_some(
10301 keybind_display.displayed_keystroke.as_ref(),
10302 |el, compact_keystroke| {
10303 el.child(self.render_edit_prediction_popover_keystroke(
10304 compact_keystroke,
10305 Color::Default,
10306 cx,
10307 ))
10308 },
10309 ),
10310 )
10311 .into_any(),
10312 );
10313 }
10314
10315 self.render_edit_prediction_cursor_popover_preview(
10316 prediction,
10317 cursor_point,
10318 style,
10319 cx,
10320 )?
10321 }
10322
10323 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
10324 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
10325 stale_completion,
10326 cursor_point,
10327 style,
10328 cx,
10329 )?,
10330
10331 None => pending_completion_container(icons.base)
10332 .child(Label::new("...").size(LabelSize::Small)),
10333 },
10334
10335 None => pending_completion_container(icons.base)
10336 .child(Label::new("...").size(LabelSize::Small)),
10337 };
10338
10339 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
10340 completion
10341 .with_animation(
10342 "loading-completion",
10343 Animation::new(Duration::from_secs(2))
10344 .repeat()
10345 .with_easing(pulsating_between(0.4, 0.8)),
10346 |label, delta| label.opacity(delta),
10347 )
10348 .into_any_element()
10349 } else {
10350 completion.into_any_element()
10351 };
10352
10353 let has_completion = self.active_edit_prediction.is_some();
10354 let keybind_display = self.edit_prediction_keybind_display(
10355 EditPredictionKeybindSurface::CursorPopoverExpanded,
10356 window,
10357 cx,
10358 );
10359
10360 Some(
10361 h_flex()
10362 .min_w(min_width)
10363 .max_w(max_width)
10364 .flex_1()
10365 .elevation_2(cx)
10366 .border_color(cx.theme().colors().border)
10367 .child(
10368 div()
10369 .flex_1()
10370 .py_1()
10371 .px_2()
10372 .overflow_hidden()
10373 .child(completion),
10374 )
10375 .when_some(
10376 keybind_display.displayed_keystroke.as_ref(),
10377 |el, keystroke| {
10378 let key_color = if !has_completion {
10379 Color::Muted
10380 } else {
10381 Color::Default
10382 };
10383
10384 if keybind_display.action == EditPredictionKeybindAction::Preview {
10385 el.child(
10386 h_flex()
10387 .h_full()
10388 .border_l_1()
10389 .rounded_r_lg()
10390 .border_color(cx.theme().colors().border)
10391 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10392 .gap_1()
10393 .py_1()
10394 .px_2()
10395 .child(self.render_edit_prediction_popover_keystroke(
10396 keystroke, key_color, cx,
10397 ))
10398 .child(Label::new("Preview").into_any_element())
10399 .opacity(if has_completion { 1.0 } else { 0.4 }),
10400 )
10401 } else {
10402 el.child(
10403 h_flex()
10404 .h_full()
10405 .border_l_1()
10406 .rounded_r_lg()
10407 .border_color(cx.theme().colors().border)
10408 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10409 .gap_1()
10410 .py_1()
10411 .px_2()
10412 .child(self.render_edit_prediction_popover_keystroke(
10413 keystroke, key_color, cx,
10414 ))
10415 .opacity(if has_completion { 1.0 } else { 0.4 }),
10416 )
10417 }
10418 },
10419 )
10420 .into_any(),
10421 )
10422 }
10423
10424 fn render_edit_prediction_cursor_popover_preview(
10425 &self,
10426 completion: &EditPredictionState,
10427 cursor_point: Point,
10428 style: &EditorStyle,
10429 cx: &mut Context<Editor>,
10430 ) -> Option<Div> {
10431 use text::ToPoint as _;
10432
10433 fn render_relative_row_jump(
10434 prefix: impl Into<String>,
10435 current_row: u32,
10436 target_row: u32,
10437 ) -> Div {
10438 let (row_diff, arrow) = if target_row < current_row {
10439 (current_row - target_row, IconName::ArrowUp)
10440 } else {
10441 (target_row - current_row, IconName::ArrowDown)
10442 };
10443
10444 h_flex()
10445 .child(
10446 Label::new(format!("{}{}", prefix.into(), row_diff))
10447 .color(Color::Muted)
10448 .size(LabelSize::Small),
10449 )
10450 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
10451 }
10452
10453 let supports_jump = self
10454 .edit_prediction_provider
10455 .as_ref()
10456 .map(|provider| provider.provider.supports_jump_to_edit())
10457 .unwrap_or(true);
10458
10459 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10460
10461 match &completion.completion {
10462 EditPrediction::MoveWithin {
10463 target, snapshot, ..
10464 } => {
10465 if !supports_jump {
10466 return None;
10467 }
10468 let (target, _) = self.display_snapshot(cx).anchor_to_buffer_anchor(*target)?;
10469
10470 Some(
10471 h_flex()
10472 .px_2()
10473 .gap_2()
10474 .flex_1()
10475 .child(if target.to_point(snapshot).row > cursor_point.row {
10476 Icon::new(icons.down)
10477 } else {
10478 Icon::new(icons.up)
10479 })
10480 .child(Label::new("Jump to Edit")),
10481 )
10482 }
10483 EditPrediction::MoveOutside { snapshot, .. } => {
10484 let file_name = snapshot
10485 .file()
10486 .map(|file| file.file_name(cx))
10487 .unwrap_or("untitled");
10488 Some(
10489 h_flex()
10490 .px_2()
10491 .gap_2()
10492 .flex_1()
10493 .child(Icon::new(icons.base))
10494 .child(Label::new(format!("Jump to {file_name}"))),
10495 )
10496 }
10497 EditPrediction::Edit {
10498 edits,
10499 edit_preview,
10500 snapshot,
10501 ..
10502 } => {
10503 let first_edit_row = self
10504 .display_snapshot(cx)
10505 .anchor_to_buffer_anchor(edits.first()?.0.start)?
10506 .0
10507 .to_point(snapshot)
10508 .row;
10509
10510 let (highlighted_edits, has_more_lines) =
10511 if let Some(edit_preview) = edit_preview.as_ref() {
10512 crate::edit_prediction_edit_text(
10513 snapshot,
10514 edits,
10515 edit_preview,
10516 true,
10517 &self.display_snapshot(cx),
10518 cx,
10519 )
10520 .first_line_preview()
10521 } else {
10522 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
10523 };
10524
10525 let styled_text = gpui::StyledText::new(highlighted_edits.text)
10526 .with_default_highlights(&style.text, highlighted_edits.highlights);
10527
10528 let preview = h_flex()
10529 .gap_1()
10530 .min_w_16()
10531 .child(styled_text)
10532 .when(has_more_lines, |parent| parent.child("…"));
10533
10534 let left = if supports_jump && first_edit_row != cursor_point.row {
10535 render_relative_row_jump("", cursor_point.row, first_edit_row)
10536 .into_any_element()
10537 } else {
10538 Icon::new(icons.base).into_any_element()
10539 };
10540
10541 Some(
10542 h_flex()
10543 .h_full()
10544 .flex_1()
10545 .gap_2()
10546 .pr_1()
10547 .overflow_x_hidden()
10548 .font(
10549 theme_settings::ThemeSettings::get_global(cx)
10550 .buffer_font
10551 .clone(),
10552 )
10553 .child(left)
10554 .child(preview),
10555 )
10556 }
10557 }
10558 }
10559
10560 pub fn render_context_menu(
10561 &mut self,
10562 max_height_in_lines: u32,
10563 window: &mut Window,
10564 cx: &mut Context<Editor>,
10565 ) -> Option<AnyElement> {
10566 let menu = self.context_menu.borrow();
10567 let menu = menu.as_ref()?;
10568 if !menu.visible() {
10569 return None;
10570 };
10571 self.style
10572 .as_ref()
10573 .map(|style| menu.render(style, max_height_in_lines, window, cx))
10574 }
10575
10576 fn render_context_menu_aside(
10577 &mut self,
10578 max_size: Size<Pixels>,
10579 window: &mut Window,
10580 cx: &mut Context<Editor>,
10581 ) -> Option<AnyElement> {
10582 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
10583 if menu.visible() {
10584 menu.render_aside(max_size, window, cx)
10585 } else {
10586 None
10587 }
10588 })
10589 }
10590
10591 fn hide_context_menu(
10592 &mut self,
10593 window: &mut Window,
10594 cx: &mut Context<Self>,
10595 ) -> Option<CodeContextMenu> {
10596 cx.notify();
10597 self.completion_tasks.clear();
10598 let context_menu = self.context_menu.borrow_mut().take();
10599 self.stale_edit_prediction_in_menu.take();
10600 self.update_visible_edit_prediction(window, cx);
10601 if let Some(CodeContextMenu::Completions(_)) = &context_menu
10602 && let Some(completion_provider) = &self.completion_provider
10603 {
10604 completion_provider.selection_changed(None, window, cx);
10605 }
10606 context_menu
10607 }
10608
10609 fn show_snippet_choices(
10610 &mut self,
10611 choices: &Vec<String>,
10612 selection: Range<Anchor>,
10613 cx: &mut Context<Self>,
10614 ) {
10615 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
10616 let Some((buffer_snapshot, range)) =
10617 buffer_snapshot.anchor_range_to_buffer_anchor_range(selection.clone())
10618 else {
10619 return;
10620 };
10621 let Some(buffer) = self.buffer.read(cx).buffer(buffer_snapshot.remote_id()) else {
10622 return;
10623 };
10624
10625 let id = post_inc(&mut self.next_completion_id);
10626 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
10627 let mut context_menu = self.context_menu.borrow_mut();
10628 let old_menu = context_menu.take();
10629 *context_menu = Some(CodeContextMenu::Completions(
10630 CompletionsMenu::new_snippet_choices(
10631 id,
10632 true,
10633 choices,
10634 selection.start,
10635 range,
10636 buffer,
10637 old_menu.map(|menu| menu.primary_scroll_handle()),
10638 snippet_sort_order,
10639 ),
10640 ));
10641 }
10642
10643 pub fn insert_snippet(
10644 &mut self,
10645 insertion_ranges: &[Range<MultiBufferOffset>],
10646 snippet: Snippet,
10647 window: &mut Window,
10648 cx: &mut Context<Self>,
10649 ) -> Result<()> {
10650 struct Tabstop<T> {
10651 is_end_tabstop: bool,
10652 ranges: Vec<Range<T>>,
10653 choices: Option<Vec<String>>,
10654 }
10655
10656 let tabstops = self.buffer.update(cx, |buffer, cx| {
10657 let snippet_text: Arc<str> = snippet.text.clone().into();
10658 let edits = insertion_ranges
10659 .iter()
10660 .cloned()
10661 .map(|range| (range, snippet_text.clone()));
10662 let autoindent_mode = AutoindentMode::Block {
10663 original_indent_columns: Vec::new(),
10664 };
10665 buffer.edit(edits, Some(autoindent_mode), cx);
10666
10667 let snapshot = &*buffer.read(cx);
10668 let snippet = &snippet;
10669 snippet
10670 .tabstops
10671 .iter()
10672 .map(|tabstop| {
10673 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10674 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10675 });
10676 let mut tabstop_ranges = tabstop
10677 .ranges
10678 .iter()
10679 .flat_map(|tabstop_range| {
10680 let mut delta = 0_isize;
10681 insertion_ranges.iter().map(move |insertion_range| {
10682 let insertion_start = insertion_range.start + delta;
10683 delta += snippet.text.len() as isize
10684 - (insertion_range.end - insertion_range.start) as isize;
10685
10686 let start =
10687 (insertion_start + tabstop_range.start).min(snapshot.len());
10688 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10689 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10690 })
10691 })
10692 .collect::<Vec<_>>();
10693 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10694
10695 Tabstop {
10696 is_end_tabstop,
10697 ranges: tabstop_ranges,
10698 choices: tabstop.choices.clone(),
10699 }
10700 })
10701 .collect::<Vec<_>>()
10702 });
10703 if let Some(tabstop) = tabstops.first() {
10704 self.change_selections(Default::default(), window, cx, |s| {
10705 // Reverse order so that the first range is the newest created selection.
10706 // Completions will use it and autoscroll will prioritize it.
10707 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10708 });
10709
10710 if let Some(choices) = &tabstop.choices
10711 && let Some(selection) = tabstop.ranges.first()
10712 {
10713 self.show_snippet_choices(choices, selection.clone(), cx)
10714 }
10715
10716 // If we're already at the last tabstop and it's at the end of the snippet,
10717 // we're done, we don't need to keep the state around.
10718 if !tabstop.is_end_tabstop {
10719 let choices = tabstops
10720 .iter()
10721 .map(|tabstop| tabstop.choices.clone())
10722 .collect();
10723
10724 let ranges = tabstops
10725 .into_iter()
10726 .map(|tabstop| tabstop.ranges)
10727 .collect::<Vec<_>>();
10728
10729 self.snippet_stack.push(SnippetState {
10730 active_index: 0,
10731 ranges,
10732 choices,
10733 });
10734 }
10735
10736 // Check whether the just-entered snippet ends with an auto-closable bracket.
10737 if self.autoclose_regions.is_empty() {
10738 let snapshot = self.buffer.read(cx).snapshot(cx);
10739 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10740 let selection_head = selection.head();
10741 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10742 continue;
10743 };
10744
10745 let mut bracket_pair = None;
10746 let max_lookup_length = scope
10747 .brackets()
10748 .map(|(pair, _)| {
10749 pair.start
10750 .as_str()
10751 .chars()
10752 .count()
10753 .max(pair.end.as_str().chars().count())
10754 })
10755 .max();
10756 if let Some(max_lookup_length) = max_lookup_length {
10757 let next_text = snapshot
10758 .chars_at(selection_head)
10759 .take(max_lookup_length)
10760 .collect::<String>();
10761 let prev_text = snapshot
10762 .reversed_chars_at(selection_head)
10763 .take(max_lookup_length)
10764 .collect::<String>();
10765
10766 for (pair, enabled) in scope.brackets() {
10767 if enabled
10768 && pair.close
10769 && prev_text.starts_with(pair.start.as_str())
10770 && next_text.starts_with(pair.end.as_str())
10771 {
10772 bracket_pair = Some(pair.clone());
10773 break;
10774 }
10775 }
10776 }
10777
10778 if let Some(pair) = bracket_pair {
10779 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10780 let autoclose_enabled =
10781 self.use_autoclose && snapshot_settings.use_autoclose;
10782 if autoclose_enabled {
10783 let start = snapshot.anchor_after(selection_head);
10784 let end = snapshot.anchor_after(selection_head);
10785 self.autoclose_regions.push(AutocloseRegion {
10786 selection_id: selection.id,
10787 range: start..end,
10788 pair,
10789 });
10790 }
10791 }
10792 }
10793 }
10794 }
10795 Ok(())
10796 }
10797
10798 pub fn move_to_next_snippet_tabstop(
10799 &mut self,
10800 window: &mut Window,
10801 cx: &mut Context<Self>,
10802 ) -> bool {
10803 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10804 }
10805
10806 pub fn move_to_prev_snippet_tabstop(
10807 &mut self,
10808 window: &mut Window,
10809 cx: &mut Context<Self>,
10810 ) -> bool {
10811 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10812 }
10813
10814 pub fn move_to_snippet_tabstop(
10815 &mut self,
10816 bias: Bias,
10817 window: &mut Window,
10818 cx: &mut Context<Self>,
10819 ) -> bool {
10820 if let Some(mut snippet) = self.snippet_stack.pop() {
10821 match bias {
10822 Bias::Left => {
10823 if snippet.active_index > 0 {
10824 snippet.active_index -= 1;
10825 } else {
10826 self.snippet_stack.push(snippet);
10827 return false;
10828 }
10829 }
10830 Bias::Right => {
10831 if snippet.active_index + 1 < snippet.ranges.len() {
10832 snippet.active_index += 1;
10833 } else {
10834 self.snippet_stack.push(snippet);
10835 return false;
10836 }
10837 }
10838 }
10839 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10840 self.change_selections(Default::default(), window, cx, |s| {
10841 // Reverse order so that the first range is the newest created selection.
10842 // Completions will use it and autoscroll will prioritize it.
10843 s.select_ranges(current_ranges.iter().rev().cloned())
10844 });
10845
10846 if let Some(choices) = &snippet.choices[snippet.active_index]
10847 && let Some(selection) = current_ranges.first()
10848 {
10849 self.show_snippet_choices(choices, selection.clone(), cx);
10850 }
10851
10852 // If snippet state is not at the last tabstop, push it back on the stack
10853 if snippet.active_index + 1 < snippet.ranges.len() {
10854 self.snippet_stack.push(snippet);
10855 }
10856 return true;
10857 }
10858 }
10859
10860 false
10861 }
10862
10863 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10864 self.transact(window, cx, |this, window, cx| {
10865 this.select_all(&SelectAll, window, cx);
10866 this.insert("", window, cx);
10867 });
10868 }
10869
10870 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10871 if self.read_only(cx) {
10872 return;
10873 }
10874 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10875 self.transact(window, cx, |this, window, cx| {
10876 this.select_autoclose_pair(window, cx);
10877
10878 let linked_edits = this.linked_edits_for_selections(Arc::from(""), cx);
10879
10880 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10881 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10882 for selection in &mut selections {
10883 if selection.is_empty() {
10884 let old_head = selection.head();
10885 let mut new_head =
10886 movement::left(&display_map, old_head.to_display_point(&display_map))
10887 .to_point(&display_map);
10888 if let Some((buffer, line_buffer_range)) = display_map
10889 .buffer_snapshot()
10890 .buffer_line_for_row(MultiBufferRow(old_head.row))
10891 {
10892 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10893 let indent_len = match indent_size.kind {
10894 IndentKind::Space => {
10895 buffer.settings_at(line_buffer_range.start, cx).tab_size
10896 }
10897 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10898 };
10899 if old_head.column <= indent_size.len && old_head.column > 0 {
10900 let indent_len = indent_len.get();
10901 new_head = cmp::min(
10902 new_head,
10903 MultiBufferPoint::new(
10904 old_head.row,
10905 ((old_head.column - 1) / indent_len) * indent_len,
10906 ),
10907 );
10908 }
10909 }
10910
10911 selection.set_head(new_head, SelectionGoal::None);
10912 }
10913 }
10914
10915 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10916 this.insert("", window, cx);
10917 linked_edits.apply_with_left_expansion(cx);
10918 this.refresh_edit_prediction(true, false, window, cx);
10919 refresh_linked_ranges(this, window, cx);
10920 });
10921 }
10922
10923 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10924 if self.read_only(cx) {
10925 return;
10926 }
10927 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10928 self.transact(window, cx, |this, window, cx| {
10929 this.change_selections(Default::default(), window, cx, |s| {
10930 s.move_with(&mut |map, selection| {
10931 if selection.is_empty() {
10932 let cursor = movement::right(map, selection.head());
10933 selection.end = cursor;
10934 selection.reversed = true;
10935 selection.goal = SelectionGoal::None;
10936 }
10937 })
10938 });
10939 let linked_edits = this.linked_edits_for_selections(Arc::from(""), cx);
10940 this.insert("", window, cx);
10941 linked_edits.apply(cx);
10942 this.refresh_edit_prediction(true, false, window, cx);
10943 refresh_linked_ranges(this, window, cx);
10944 });
10945 }
10946
10947 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10948 if self.mode.is_single_line() {
10949 cx.propagate();
10950 return;
10951 }
10952
10953 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10954 if self.move_to_prev_snippet_tabstop(window, cx) {
10955 return;
10956 }
10957 self.outdent(&Outdent, window, cx);
10958 }
10959
10960 pub fn next_snippet_tabstop(
10961 &mut self,
10962 _: &NextSnippetTabstop,
10963 window: &mut Window,
10964 cx: &mut Context<Self>,
10965 ) {
10966 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10967 cx.propagate();
10968 return;
10969 }
10970
10971 if self.move_to_next_snippet_tabstop(window, cx) {
10972 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10973 return;
10974 }
10975 cx.propagate();
10976 }
10977
10978 pub fn previous_snippet_tabstop(
10979 &mut self,
10980 _: &PreviousSnippetTabstop,
10981 window: &mut Window,
10982 cx: &mut Context<Self>,
10983 ) {
10984 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10985 cx.propagate();
10986 return;
10987 }
10988
10989 if self.move_to_prev_snippet_tabstop(window, cx) {
10990 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10991 return;
10992 }
10993 cx.propagate();
10994 }
10995
10996 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10997 if self.mode.is_single_line() {
10998 cx.propagate();
10999 return;
11000 }
11001
11002 if self.move_to_next_snippet_tabstop(window, cx) {
11003 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11004 return;
11005 }
11006 if self.read_only(cx) {
11007 return;
11008 }
11009 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11010 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
11011 let buffer = self.buffer.read(cx);
11012 let snapshot = buffer.snapshot(cx);
11013 let rows_iter = selections.iter().map(|s| s.head().row);
11014 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
11015
11016 let has_some_cursor_in_whitespace = selections
11017 .iter()
11018 .filter(|selection| selection.is_empty())
11019 .any(|selection| {
11020 let cursor = selection.head();
11021 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
11022 cursor.column < current_indent.len
11023 });
11024
11025 let mut edits = Vec::new();
11026 let mut prev_edited_row = 0;
11027 let mut row_delta = 0;
11028 for selection in &mut selections {
11029 if selection.start.row != prev_edited_row {
11030 row_delta = 0;
11031 }
11032 prev_edited_row = selection.end.row;
11033
11034 // If cursor is after a list prefix, make selection non-empty to trigger line indent
11035 if selection.is_empty() {
11036 let cursor = selection.head();
11037 let settings = buffer.language_settings_at(cursor, cx);
11038 if settings.indent_list_on_tab {
11039 if let Some(language) = snapshot.language_scope_at(Point::new(cursor.row, 0)) {
11040 if is_list_prefix_row(MultiBufferRow(cursor.row), &snapshot, &language) {
11041 row_delta = Self::indent_selection(
11042 buffer, &snapshot, selection, &mut edits, row_delta, cx,
11043 );
11044 continue;
11045 }
11046 }
11047 }
11048 }
11049
11050 // If the selection is non-empty, then increase the indentation of the selected lines.
11051 if !selection.is_empty() {
11052 row_delta =
11053 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
11054 continue;
11055 }
11056
11057 let cursor = selection.head();
11058 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
11059 if let Some(suggested_indent) =
11060 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
11061 {
11062 // Don't do anything if already at suggested indent
11063 // and there is any other cursor which is not
11064 if has_some_cursor_in_whitespace
11065 && cursor.column == current_indent.len
11066 && current_indent.len == suggested_indent.len
11067 {
11068 continue;
11069 }
11070
11071 // Adjust line and move cursor to suggested indent
11072 // if cursor is not at suggested indent
11073 if cursor.column < suggested_indent.len
11074 && cursor.column <= current_indent.len
11075 && current_indent.len <= suggested_indent.len
11076 {
11077 selection.start = Point::new(cursor.row, suggested_indent.len);
11078 selection.end = selection.start;
11079 if row_delta == 0 {
11080 edits.extend(Buffer::edit_for_indent_size_adjustment(
11081 cursor.row,
11082 current_indent,
11083 suggested_indent,
11084 ));
11085 row_delta = suggested_indent.len - current_indent.len;
11086 }
11087 continue;
11088 }
11089
11090 // If current indent is more than suggested indent
11091 // only move cursor to current indent and skip indent
11092 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
11093 selection.start = Point::new(cursor.row, current_indent.len);
11094 selection.end = selection.start;
11095 continue;
11096 }
11097 }
11098
11099 // Otherwise, insert a hard or soft tab.
11100 let settings = buffer.language_settings_at(cursor, cx);
11101 let tab_size = if settings.hard_tabs {
11102 IndentSize::tab()
11103 } else {
11104 let tab_size = settings.tab_size.get();
11105 let indent_remainder = snapshot
11106 .text_for_range(Point::new(cursor.row, 0)..cursor)
11107 .flat_map(str::chars)
11108 .fold(row_delta % tab_size, |counter: u32, c| {
11109 if c == '\t' {
11110 0
11111 } else {
11112 (counter + 1) % tab_size
11113 }
11114 });
11115
11116 let chars_to_next_tab_stop = tab_size - indent_remainder;
11117 IndentSize::spaces(chars_to_next_tab_stop)
11118 };
11119 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
11120 selection.end = selection.start;
11121 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
11122 row_delta += tab_size.len;
11123 }
11124
11125 self.transact(window, cx, |this, window, cx| {
11126 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
11127 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11128 this.refresh_edit_prediction(true, false, window, cx);
11129 });
11130 }
11131
11132 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
11133 if self.read_only(cx) {
11134 return;
11135 }
11136 if self.mode.is_single_line() {
11137 cx.propagate();
11138 return;
11139 }
11140
11141 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11142 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
11143 let mut prev_edited_row = 0;
11144 let mut row_delta = 0;
11145 let mut edits = Vec::new();
11146 let buffer = self.buffer.read(cx);
11147 let snapshot = buffer.snapshot(cx);
11148 for selection in &mut selections {
11149 if selection.start.row != prev_edited_row {
11150 row_delta = 0;
11151 }
11152 prev_edited_row = selection.end.row;
11153
11154 row_delta =
11155 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
11156 }
11157
11158 self.transact(window, cx, |this, window, cx| {
11159 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
11160 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11161 });
11162 }
11163
11164 fn indent_selection(
11165 buffer: &MultiBuffer,
11166 snapshot: &MultiBufferSnapshot,
11167 selection: &mut Selection<Point>,
11168 edits: &mut Vec<(Range<Point>, String)>,
11169 delta_for_start_row: u32,
11170 cx: &App,
11171 ) -> u32 {
11172 let settings = buffer.language_settings_at(selection.start, cx);
11173 let tab_size = settings.tab_size.get();
11174 let indent_kind = if settings.hard_tabs {
11175 IndentKind::Tab
11176 } else {
11177 IndentKind::Space
11178 };
11179 let mut start_row = selection.start.row;
11180 let mut end_row = selection.end.row + 1;
11181
11182 // If a selection ends at the beginning of a line, don't indent
11183 // that last line.
11184 if selection.end.column == 0 && selection.end.row > selection.start.row {
11185 end_row -= 1;
11186 }
11187
11188 // Avoid re-indenting a row that has already been indented by a
11189 // previous selection, but still update this selection's column
11190 // to reflect that indentation.
11191 if delta_for_start_row > 0 {
11192 start_row += 1;
11193 selection.start.column += delta_for_start_row;
11194 if selection.end.row == selection.start.row {
11195 selection.end.column += delta_for_start_row;
11196 }
11197 }
11198
11199 let mut delta_for_end_row = 0;
11200 let has_multiple_rows = start_row + 1 != end_row;
11201 for row in start_row..end_row {
11202 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
11203 let indent_delta = match (current_indent.kind, indent_kind) {
11204 (IndentKind::Space, IndentKind::Space) => {
11205 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
11206 IndentSize::spaces(columns_to_next_tab_stop)
11207 }
11208 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
11209 (_, IndentKind::Tab) => IndentSize::tab(),
11210 };
11211
11212 let start = if has_multiple_rows || current_indent.len < selection.start.column {
11213 0
11214 } else {
11215 selection.start.column
11216 };
11217 let row_start = Point::new(row, start);
11218 edits.push((
11219 row_start..row_start,
11220 indent_delta.chars().collect::<String>(),
11221 ));
11222
11223 // Update this selection's endpoints to reflect the indentation.
11224 if row == selection.start.row {
11225 selection.start.column += indent_delta.len;
11226 }
11227 if row == selection.end.row {
11228 selection.end.column += indent_delta.len;
11229 delta_for_end_row = indent_delta.len;
11230 }
11231 }
11232
11233 if selection.start.row == selection.end.row {
11234 delta_for_start_row + delta_for_end_row
11235 } else {
11236 delta_for_end_row
11237 }
11238 }
11239
11240 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
11241 if self.read_only(cx) {
11242 return;
11243 }
11244 if self.mode.is_single_line() {
11245 cx.propagate();
11246 return;
11247 }
11248
11249 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11250 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11251 let selections = self.selections.all::<Point>(&display_map);
11252 let mut deletion_ranges = Vec::new();
11253 let mut last_outdent = None;
11254 {
11255 let buffer = self.buffer.read(cx);
11256 let snapshot = buffer.snapshot(cx);
11257 for selection in &selections {
11258 let settings = buffer.language_settings_at(selection.start, cx);
11259 let tab_size = settings.tab_size.get();
11260 let mut rows = selection.spanned_rows(false, &display_map);
11261
11262 // Avoid re-outdenting a row that has already been outdented by a
11263 // previous selection.
11264 if let Some(last_row) = last_outdent
11265 && last_row == rows.start
11266 {
11267 rows.start = rows.start.next_row();
11268 }
11269 let has_multiple_rows = rows.len() > 1;
11270 for row in rows.iter_rows() {
11271 let indent_size = snapshot.indent_size_for_line(row);
11272 if indent_size.len > 0 {
11273 let deletion_len = match indent_size.kind {
11274 IndentKind::Space => {
11275 let columns_to_prev_tab_stop = indent_size.len % tab_size;
11276 if columns_to_prev_tab_stop == 0 {
11277 tab_size
11278 } else {
11279 columns_to_prev_tab_stop
11280 }
11281 }
11282 IndentKind::Tab => 1,
11283 };
11284 let start = if has_multiple_rows
11285 || deletion_len > selection.start.column
11286 || indent_size.len < selection.start.column
11287 {
11288 0
11289 } else {
11290 selection.start.column - deletion_len
11291 };
11292 deletion_ranges.push(
11293 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
11294 );
11295 last_outdent = Some(row);
11296 }
11297 }
11298 }
11299 }
11300
11301 self.transact(window, cx, |this, window, cx| {
11302 this.buffer.update(cx, |buffer, cx| {
11303 let empty_str: Arc<str> = Arc::default();
11304 buffer.edit(
11305 deletion_ranges
11306 .into_iter()
11307 .map(|range| (range, empty_str.clone())),
11308 None,
11309 cx,
11310 );
11311 });
11312 let selections = this
11313 .selections
11314 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11315 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11316 });
11317 }
11318
11319 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
11320 if self.read_only(cx) {
11321 return;
11322 }
11323 if self.mode.is_single_line() {
11324 cx.propagate();
11325 return;
11326 }
11327
11328 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11329 let selections = self
11330 .selections
11331 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11332 .into_iter()
11333 .map(|s| s.range());
11334
11335 self.transact(window, cx, |this, window, cx| {
11336 this.buffer.update(cx, |buffer, cx| {
11337 buffer.autoindent_ranges(selections, cx);
11338 });
11339 let selections = this
11340 .selections
11341 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11342 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11343 });
11344 }
11345
11346 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
11347 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11348 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11349 let selections = self.selections.all::<Point>(&display_map);
11350
11351 let mut new_cursors = Vec::new();
11352 let mut edit_ranges = Vec::new();
11353 let mut selections = selections.iter().peekable();
11354 while let Some(selection) = selections.next() {
11355 let mut rows = selection.spanned_rows(false, &display_map);
11356
11357 // Accumulate contiguous regions of rows that we want to delete.
11358 while let Some(next_selection) = selections.peek() {
11359 let next_rows = next_selection.spanned_rows(false, &display_map);
11360 if next_rows.start <= rows.end {
11361 rows.end = next_rows.end;
11362 selections.next().unwrap();
11363 } else {
11364 break;
11365 }
11366 }
11367
11368 let buffer = display_map.buffer_snapshot();
11369 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
11370 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
11371 // If there's a line after the range, delete the \n from the end of the row range
11372 (
11373 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
11374 rows.end,
11375 )
11376 } else {
11377 // If there isn't a line after the range, delete the \n from the line before the
11378 // start of the row range
11379 edit_start = edit_start.saturating_sub_usize(1);
11380 (buffer.len(), rows.start.previous_row())
11381 };
11382
11383 let text_layout_details = self.text_layout_details(window, cx);
11384 let x = display_map.x_for_display_point(
11385 selection.head().to_display_point(&display_map),
11386 &text_layout_details,
11387 );
11388 let row = Point::new(target_row.0, 0)
11389 .to_display_point(&display_map)
11390 .row();
11391 let column = display_map.display_column_for_x(row, x, &text_layout_details);
11392
11393 new_cursors.push((
11394 selection.id,
11395 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
11396 SelectionGoal::None,
11397 ));
11398 edit_ranges.push(edit_start..edit_end);
11399 }
11400
11401 self.transact(window, cx, |this, window, cx| {
11402 let buffer = this.buffer.update(cx, |buffer, cx| {
11403 let empty_str: Arc<str> = Arc::default();
11404 buffer.edit(
11405 edit_ranges
11406 .into_iter()
11407 .map(|range| (range, empty_str.clone())),
11408 None,
11409 cx,
11410 );
11411 buffer.snapshot(cx)
11412 });
11413 let new_selections = new_cursors
11414 .into_iter()
11415 .map(|(id, cursor, goal)| {
11416 let cursor = cursor.to_point(&buffer);
11417 Selection {
11418 id,
11419 start: cursor,
11420 end: cursor,
11421 reversed: false,
11422 goal,
11423 }
11424 })
11425 .collect();
11426
11427 this.change_selections(Default::default(), window, cx, |s| {
11428 s.select(new_selections);
11429 });
11430 });
11431 }
11432
11433 pub fn join_lines_impl(
11434 &mut self,
11435 insert_whitespace: bool,
11436 window: &mut Window,
11437 cx: &mut Context<Self>,
11438 ) {
11439 if self.read_only(cx) {
11440 return;
11441 }
11442 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
11443 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
11444 let start = MultiBufferRow(selection.start.row);
11445 // Treat single line selections as if they include the next line. Otherwise this action
11446 // would do nothing for single line selections individual cursors.
11447 let end = if selection.start.row == selection.end.row {
11448 MultiBufferRow(selection.start.row + 1)
11449 } else if selection.end.column == 0 {
11450 // If the selection ends at the start of a line, it's logically at the end of the
11451 // previous line (plus its newline).
11452 // Don't include the end line unless there's only one line selected.
11453 if selection.start.row + 1 == selection.end.row {
11454 MultiBufferRow(selection.end.row)
11455 } else {
11456 MultiBufferRow(selection.end.row - 1)
11457 }
11458 } else {
11459 MultiBufferRow(selection.end.row)
11460 };
11461
11462 if let Some(last_row_range) = row_ranges.last_mut()
11463 && start <= last_row_range.end
11464 {
11465 last_row_range.end = end;
11466 continue;
11467 }
11468 row_ranges.push(start..end);
11469 }
11470
11471 let snapshot = self.buffer.read(cx).snapshot(cx);
11472 let mut cursor_positions = Vec::new();
11473 for row_range in &row_ranges {
11474 let anchor = snapshot.anchor_before(Point::new(
11475 row_range.end.previous_row().0,
11476 snapshot.line_len(row_range.end.previous_row()),
11477 ));
11478 cursor_positions.push(anchor..anchor);
11479 }
11480
11481 self.transact(window, cx, |this, window, cx| {
11482 for row_range in row_ranges.into_iter().rev() {
11483 for row in row_range.iter_rows().rev() {
11484 let end_of_line = Point::new(row.0, snapshot.line_len(row));
11485 let next_line_row = row.next_row();
11486 let indent = snapshot.indent_size_for_line(next_line_row);
11487 let mut join_start_column = indent.len;
11488
11489 if let Some(language_scope) =
11490 snapshot.language_scope_at(Point::new(next_line_row.0, indent.len))
11491 {
11492 let line_end =
11493 Point::new(next_line_row.0, snapshot.line_len(next_line_row));
11494 let line_text_after_indent = snapshot
11495 .text_for_range(Point::new(next_line_row.0, indent.len)..line_end)
11496 .collect::<String>();
11497
11498 if !line_text_after_indent.is_empty() {
11499 let block_prefix = language_scope
11500 .block_comment()
11501 .map(|c| c.prefix.as_ref())
11502 .filter(|p| !p.is_empty());
11503 let doc_prefix = language_scope
11504 .documentation_comment()
11505 .map(|c| c.prefix.as_ref())
11506 .filter(|p| !p.is_empty());
11507 let all_prefixes = language_scope
11508 .line_comment_prefixes()
11509 .iter()
11510 .map(|p| p.as_ref())
11511 .chain(block_prefix)
11512 .chain(doc_prefix)
11513 .chain(language_scope.unordered_list().iter().map(|p| p.as_ref()));
11514
11515 let mut longest_prefix_len = None;
11516 for prefix in all_prefixes {
11517 let trimmed = prefix.trim_end();
11518 if line_text_after_indent.starts_with(trimmed) {
11519 let candidate_len =
11520 if line_text_after_indent.starts_with(prefix) {
11521 prefix.len()
11522 } else {
11523 trimmed.len()
11524 };
11525 if longest_prefix_len.map_or(true, |len| candidate_len > len) {
11526 longest_prefix_len = Some(candidate_len);
11527 }
11528 }
11529 }
11530
11531 if let Some(prefix_len) = longest_prefix_len {
11532 join_start_column =
11533 join_start_column.saturating_add(prefix_len as u32);
11534 }
11535 }
11536 }
11537
11538 let start_of_next_line = Point::new(next_line_row.0, join_start_column);
11539
11540 let replace = if snapshot.line_len(next_line_row) > join_start_column
11541 && insert_whitespace
11542 {
11543 " "
11544 } else {
11545 ""
11546 };
11547
11548 this.buffer.update(cx, |buffer, cx| {
11549 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
11550 });
11551 }
11552 }
11553
11554 this.change_selections(Default::default(), window, cx, |s| {
11555 s.select_anchor_ranges(cursor_positions)
11556 });
11557 });
11558 }
11559
11560 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
11561 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11562 self.join_lines_impl(true, window, cx);
11563 }
11564
11565 pub fn sort_lines_case_sensitive(
11566 &mut self,
11567 _: &SortLinesCaseSensitive,
11568 window: &mut Window,
11569 cx: &mut Context<Self>,
11570 ) {
11571 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
11572 }
11573
11574 pub fn sort_lines_by_length(
11575 &mut self,
11576 _: &SortLinesByLength,
11577 window: &mut Window,
11578 cx: &mut Context<Self>,
11579 ) {
11580 self.manipulate_immutable_lines(window, cx, |lines| {
11581 lines.sort_by_key(|&line| line.chars().count())
11582 })
11583 }
11584
11585 pub fn sort_lines_case_insensitive(
11586 &mut self,
11587 _: &SortLinesCaseInsensitive,
11588 window: &mut Window,
11589 cx: &mut Context<Self>,
11590 ) {
11591 self.manipulate_immutable_lines(window, cx, |lines| {
11592 lines.sort_by_key(|line| line.to_lowercase())
11593 })
11594 }
11595
11596 pub fn unique_lines_case_insensitive(
11597 &mut self,
11598 _: &UniqueLinesCaseInsensitive,
11599 window: &mut Window,
11600 cx: &mut Context<Self>,
11601 ) {
11602 self.manipulate_immutable_lines(window, cx, |lines| {
11603 let mut seen = HashSet::default();
11604 lines.retain(|line| seen.insert(line.to_lowercase()));
11605 })
11606 }
11607
11608 pub fn unique_lines_case_sensitive(
11609 &mut self,
11610 _: &UniqueLinesCaseSensitive,
11611 window: &mut Window,
11612 cx: &mut Context<Self>,
11613 ) {
11614 self.manipulate_immutable_lines(window, cx, |lines| {
11615 let mut seen = HashSet::default();
11616 lines.retain(|line| seen.insert(*line));
11617 })
11618 }
11619
11620 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
11621 let snapshot = self.buffer.read(cx).snapshot(cx);
11622 for selection in self.selections.disjoint_anchors_arc().iter() {
11623 if snapshot
11624 .language_at(selection.start)
11625 .and_then(|lang| lang.config().wrap_characters.as_ref())
11626 .is_some()
11627 {
11628 return true;
11629 }
11630 }
11631 false
11632 }
11633
11634 fn wrap_selections_in_tag(
11635 &mut self,
11636 _: &WrapSelectionsInTag,
11637 window: &mut Window,
11638 cx: &mut Context<Self>,
11639 ) {
11640 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11641
11642 let snapshot = self.buffer.read(cx).snapshot(cx);
11643
11644 let mut edits = Vec::new();
11645 let mut boundaries = Vec::new();
11646
11647 for selection in self
11648 .selections
11649 .all_adjusted(&self.display_snapshot(cx))
11650 .iter()
11651 {
11652 let Some(wrap_config) = snapshot
11653 .language_at(selection.start)
11654 .and_then(|lang| lang.config().wrap_characters.clone())
11655 else {
11656 continue;
11657 };
11658
11659 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
11660 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
11661
11662 let start_before = snapshot.anchor_before(selection.start);
11663 let end_after = snapshot.anchor_after(selection.end);
11664
11665 edits.push((start_before..start_before, open_tag));
11666 edits.push((end_after..end_after, close_tag));
11667
11668 boundaries.push((
11669 start_before,
11670 end_after,
11671 wrap_config.start_prefix.len(),
11672 wrap_config.end_suffix.len(),
11673 ));
11674 }
11675
11676 if edits.is_empty() {
11677 return;
11678 }
11679
11680 self.transact(window, cx, |this, window, cx| {
11681 let buffer = this.buffer.update(cx, |buffer, cx| {
11682 buffer.edit(edits, None, cx);
11683 buffer.snapshot(cx)
11684 });
11685
11686 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
11687 for (start_before, end_after, start_prefix_len, end_suffix_len) in
11688 boundaries.into_iter()
11689 {
11690 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
11691 let close_offset = end_after
11692 .to_offset(&buffer)
11693 .saturating_sub_usize(end_suffix_len);
11694 new_selections.push(open_offset..open_offset);
11695 new_selections.push(close_offset..close_offset);
11696 }
11697
11698 this.change_selections(Default::default(), window, cx, |s| {
11699 s.select_ranges(new_selections);
11700 });
11701
11702 this.request_autoscroll(Autoscroll::fit(), cx);
11703 });
11704 }
11705
11706 pub fn toggle_read_only(
11707 &mut self,
11708 _: &workspace::ToggleReadOnlyFile,
11709 _: &mut Window,
11710 cx: &mut Context<Self>,
11711 ) {
11712 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
11713 buffer.update(cx, |buffer, cx| {
11714 buffer.set_capability(
11715 match buffer.capability() {
11716 Capability::ReadWrite => Capability::Read,
11717 Capability::Read => Capability::ReadWrite,
11718 Capability::ReadOnly => Capability::ReadOnly,
11719 },
11720 cx,
11721 );
11722 })
11723 }
11724 }
11725
11726 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11727 let Some(project) = self.project.clone() else {
11728 return;
11729 };
11730 let task = self.reload(project, window, cx);
11731 self.detach_and_notify_err(task, window, cx);
11732 }
11733
11734 pub fn restore_file(
11735 &mut self,
11736 _: &::git::RestoreFile,
11737 window: &mut Window,
11738 cx: &mut Context<Self>,
11739 ) {
11740 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11741 let mut buffer_ids = HashSet::default();
11742 let snapshot = self.buffer().read(cx).snapshot(cx);
11743 for selection in self
11744 .selections
11745 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11746 {
11747 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11748 }
11749
11750 let ranges = buffer_ids
11751 .into_iter()
11752 .flat_map(|buffer_id| snapshot.range_for_buffer(buffer_id))
11753 .collect::<Vec<_>>();
11754
11755 self.restore_hunks_in_ranges(ranges, window, cx);
11756 }
11757
11758 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11759 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11760 let selections = self
11761 .selections
11762 .all(&self.display_snapshot(cx))
11763 .into_iter()
11764 .map(|s| s.range())
11765 .collect();
11766 self.restore_hunks_in_ranges(selections, window, cx);
11767 }
11768
11769 /// Restores the diff hunks in the editor's selections and moves the cursor
11770 /// to the next diff hunk. Wraps around to the beginning of the buffer if
11771 /// not all diff hunks are expanded.
11772 pub fn restore_and_next(
11773 &mut self,
11774 _: &::git::RestoreAndNext,
11775 window: &mut Window,
11776 cx: &mut Context<Self>,
11777 ) {
11778 let selections = self
11779 .selections
11780 .all(&self.display_snapshot(cx))
11781 .into_iter()
11782 .map(|selection| selection.range())
11783 .collect();
11784
11785 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11786 self.restore_hunks_in_ranges(selections, window, cx);
11787
11788 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
11789 let wrap_around = !all_diff_hunks_expanded;
11790 let snapshot = self.snapshot(window, cx);
11791 let position = self
11792 .selections
11793 .newest::<Point>(&snapshot.display_snapshot)
11794 .head();
11795
11796 self.go_to_hunk_before_or_after_position(
11797 &snapshot,
11798 position,
11799 Direction::Next,
11800 wrap_around,
11801 window,
11802 cx,
11803 );
11804 }
11805
11806 pub fn restore_hunks_in_ranges(
11807 &mut self,
11808 ranges: Vec<Range<Point>>,
11809 window: &mut Window,
11810 cx: &mut Context<Editor>,
11811 ) {
11812 if self.delegate_stage_and_restore {
11813 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11814 if !hunks.is_empty() {
11815 cx.emit(EditorEvent::RestoreRequested { hunks });
11816 }
11817 return;
11818 }
11819 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11820 self.transact(window, cx, |editor, window, cx| {
11821 editor.restore_diff_hunks(hunks, cx);
11822 let selections = editor
11823 .selections
11824 .all::<MultiBufferOffset>(&editor.display_snapshot(cx));
11825 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11826 s.select(selections);
11827 });
11828 });
11829 }
11830
11831 pub(crate) fn restore_diff_hunks(&self, hunks: Vec<MultiBufferDiffHunk>, cx: &mut App) {
11832 let mut revert_changes = HashMap::default();
11833 let chunk_by = hunks.into_iter().chunk_by(|hunk| hunk.buffer_id);
11834 for (buffer_id, hunks) in &chunk_by {
11835 let hunks = hunks.collect::<Vec<_>>();
11836 for hunk in &hunks {
11837 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11838 }
11839 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11840 }
11841 if !revert_changes.is_empty() {
11842 self.buffer().update(cx, |multi_buffer, cx| {
11843 for (buffer_id, changes) in revert_changes {
11844 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
11845 buffer.update(cx, |buffer, cx| {
11846 buffer.edit(
11847 changes
11848 .into_iter()
11849 .map(|(range, text)| (range, text.to_string())),
11850 None,
11851 cx,
11852 );
11853 });
11854 }
11855 }
11856 });
11857 }
11858 }
11859
11860 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11861 if let Some(status) = self
11862 .addons
11863 .iter()
11864 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11865 {
11866 return Some(status);
11867 }
11868 self.project
11869 .as_ref()?
11870 .read(cx)
11871 .status_for_buffer_id(buffer_id, cx)
11872 }
11873
11874 pub fn open_active_item_in_terminal(
11875 &mut self,
11876 _: &OpenInTerminal,
11877 window: &mut Window,
11878 cx: &mut Context<Self>,
11879 ) {
11880 if let Some(working_directory) = self.active_buffer(cx).and_then(|buffer| {
11881 let project_path = buffer.read(cx).project_path(cx)?;
11882 let project = self.project()?.read(cx);
11883 let entry = project.entry_for_path(&project_path, cx)?;
11884 let parent = match &entry.canonical_path {
11885 Some(canonical_path) => canonical_path.to_path_buf(),
11886 None => project.absolute_path(&project_path, cx)?,
11887 }
11888 .parent()?
11889 .to_path_buf();
11890 Some(parent)
11891 }) {
11892 window.dispatch_action(
11893 OpenTerminal {
11894 working_directory,
11895 local: false,
11896 }
11897 .boxed_clone(),
11898 cx,
11899 );
11900 }
11901 }
11902
11903 fn set_breakpoint_context_menu(
11904 &mut self,
11905 display_row: DisplayRow,
11906 position: Option<Anchor>,
11907 clicked_point: gpui::Point<Pixels>,
11908 window: &mut Window,
11909 cx: &mut Context<Self>,
11910 ) {
11911 let source = self
11912 .buffer
11913 .read(cx)
11914 .snapshot(cx)
11915 .anchor_before(Point::new(display_row.0, 0u32));
11916
11917 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11918
11919 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11920 self,
11921 source,
11922 clicked_point,
11923 context_menu,
11924 window,
11925 cx,
11926 );
11927 }
11928
11929 fn add_edit_breakpoint_block(
11930 &mut self,
11931 anchor: Anchor,
11932 breakpoint: &Breakpoint,
11933 edit_action: BreakpointPromptEditAction,
11934 window: &mut Window,
11935 cx: &mut Context<Self>,
11936 ) {
11937 let weak_editor = cx.weak_entity();
11938 let bp_prompt = cx.new(|cx| {
11939 BreakpointPromptEditor::new(
11940 weak_editor,
11941 anchor,
11942 breakpoint.clone(),
11943 edit_action,
11944 window,
11945 cx,
11946 )
11947 });
11948
11949 let height = bp_prompt.update(cx, |this, cx| {
11950 this.prompt
11951 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11952 });
11953 let cloned_prompt = bp_prompt.clone();
11954 let blocks = vec![BlockProperties {
11955 style: BlockStyle::Sticky,
11956 placement: BlockPlacement::Above(anchor),
11957 height: Some(height),
11958 render: Arc::new(move |cx| {
11959 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11960 cloned_prompt.clone().into_any_element()
11961 }),
11962 priority: 0,
11963 }];
11964
11965 let focus_handle = bp_prompt.focus_handle(cx);
11966 window.focus(&focus_handle, cx);
11967
11968 let block_ids = self.insert_blocks(blocks, None, cx);
11969 bp_prompt.update(cx, |prompt, _| {
11970 prompt.add_block_ids(block_ids);
11971 });
11972 }
11973
11974 pub(crate) fn breakpoint_at_row(
11975 &self,
11976 row: u32,
11977 window: &mut Window,
11978 cx: &mut Context<Self>,
11979 ) -> Option<(Anchor, Breakpoint)> {
11980 let snapshot = self.snapshot(window, cx);
11981 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11982
11983 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11984 }
11985
11986 pub(crate) fn breakpoint_at_anchor(
11987 &self,
11988 breakpoint_position: Anchor,
11989 snapshot: &EditorSnapshot,
11990 cx: &mut Context<Self>,
11991 ) -> Option<(Anchor, Breakpoint)> {
11992 let (breakpoint_position, _) = snapshot
11993 .buffer_snapshot()
11994 .anchor_to_buffer_anchor(breakpoint_position)?;
11995 let buffer = self.buffer.read(cx).buffer(breakpoint_position.buffer_id)?;
11996
11997 let buffer_snapshot = buffer.read(cx).snapshot();
11998
11999 let row = buffer_snapshot
12000 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position)
12001 .row;
12002
12003 let line_len = buffer_snapshot.line_len(row);
12004 let anchor_end = buffer_snapshot.anchor_after(Point::new(row, line_len));
12005
12006 self.breakpoint_store
12007 .as_ref()?
12008 .read_with(cx, |breakpoint_store, cx| {
12009 breakpoint_store
12010 .breakpoints(
12011 &buffer,
12012 Some(breakpoint_position..anchor_end),
12013 &buffer_snapshot,
12014 cx,
12015 )
12016 .next()
12017 .and_then(|(bp, _)| {
12018 let breakpoint_row = buffer_snapshot
12019 .summary_for_anchor::<text::PointUtf16>(&bp.position)
12020 .row;
12021
12022 if breakpoint_row == row {
12023 snapshot
12024 .buffer_snapshot()
12025 .anchor_in_excerpt(bp.position)
12026 .map(|position| (position, bp.bp.clone()))
12027 } else {
12028 None
12029 }
12030 })
12031 })
12032 }
12033
12034 pub fn edit_log_breakpoint(
12035 &mut self,
12036 _: &EditLogBreakpoint,
12037 window: &mut Window,
12038 cx: &mut Context<Self>,
12039 ) {
12040 if self.breakpoint_store.is_none() {
12041 return;
12042 }
12043
12044 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
12045 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
12046 message: None,
12047 state: BreakpointState::Enabled,
12048 condition: None,
12049 hit_condition: None,
12050 });
12051
12052 self.add_edit_breakpoint_block(
12053 anchor,
12054 &breakpoint,
12055 BreakpointPromptEditAction::Log,
12056 window,
12057 cx,
12058 );
12059 }
12060 }
12061
12062 fn breakpoints_at_cursors(
12063 &self,
12064 window: &mut Window,
12065 cx: &mut Context<Self>,
12066 ) -> Vec<(Anchor, Option<Breakpoint>)> {
12067 let snapshot = self.snapshot(window, cx);
12068 let cursors = self
12069 .selections
12070 .disjoint_anchors_arc()
12071 .iter()
12072 .map(|selection| {
12073 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
12074
12075 let breakpoint_position = self
12076 .breakpoint_at_row(cursor_position.row, window, cx)
12077 .map(|bp| bp.0)
12078 .unwrap_or_else(|| {
12079 snapshot
12080 .display_snapshot
12081 .buffer_snapshot()
12082 .anchor_after(Point::new(cursor_position.row, 0))
12083 });
12084
12085 let breakpoint = self
12086 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
12087 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
12088
12089 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
12090 })
12091 // 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.
12092 .collect::<HashMap<Anchor, _>>();
12093
12094 cursors.into_iter().collect()
12095 }
12096
12097 pub fn enable_breakpoint(
12098 &mut self,
12099 _: &crate::actions::EnableBreakpoint,
12100 window: &mut Window,
12101 cx: &mut Context<Self>,
12102 ) {
12103 if self.breakpoint_store.is_none() {
12104 return;
12105 }
12106
12107 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
12108 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
12109 continue;
12110 };
12111 self.edit_breakpoint_at_anchor(
12112 anchor,
12113 breakpoint,
12114 BreakpointEditAction::InvertState,
12115 cx,
12116 );
12117 }
12118 }
12119
12120 pub fn align_selections(
12121 &mut self,
12122 _: &crate::actions::AlignSelections,
12123 window: &mut Window,
12124 cx: &mut Context<Self>,
12125 ) {
12126 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12127
12128 let display_snapshot = self.display_snapshot(cx);
12129
12130 struct CursorData {
12131 anchor: Anchor,
12132 point: Point,
12133 }
12134 let cursor_data: Vec<CursorData> = self
12135 .selections
12136 .disjoint_anchors()
12137 .iter()
12138 .map(|selection| {
12139 let anchor = if selection.reversed {
12140 selection.head()
12141 } else {
12142 selection.tail()
12143 };
12144 CursorData {
12145 anchor: anchor,
12146 point: anchor.to_point(&display_snapshot.buffer_snapshot()),
12147 }
12148 })
12149 .collect();
12150
12151 let rows_anchors_count: Vec<usize> = cursor_data
12152 .iter()
12153 .map(|cursor| cursor.point.row)
12154 .chunk_by(|&row| row)
12155 .into_iter()
12156 .map(|(_, group)| group.count())
12157 .collect();
12158 let max_columns = rows_anchors_count.iter().max().copied().unwrap_or(0);
12159 let mut rows_column_offset = vec![0; rows_anchors_count.len()];
12160 let mut edits = Vec::new();
12161
12162 for column_idx in 0..max_columns {
12163 let mut cursor_index = 0;
12164
12165 // Calculate target_column => position that the selections will go
12166 let mut target_column = 0;
12167 for (row_idx, cursor_count) in rows_anchors_count.iter().enumerate() {
12168 // Skip rows that don't have this column
12169 if column_idx >= *cursor_count {
12170 cursor_index += cursor_count;
12171 continue;
12172 }
12173
12174 let point = &cursor_data[cursor_index + column_idx].point;
12175 let adjusted_column = point.column + rows_column_offset[row_idx];
12176 if adjusted_column > target_column {
12177 target_column = adjusted_column;
12178 }
12179 cursor_index += cursor_count;
12180 }
12181
12182 // Collect edits for this column
12183 cursor_index = 0;
12184 for (row_idx, cursor_count) in rows_anchors_count.iter().enumerate() {
12185 // Skip rows that don't have this column
12186 if column_idx >= *cursor_count {
12187 cursor_index += *cursor_count;
12188 continue;
12189 }
12190
12191 let point = &cursor_data[cursor_index + column_idx].point;
12192 let spaces_needed = target_column - point.column - rows_column_offset[row_idx];
12193 if spaces_needed > 0 {
12194 let anchor = cursor_data[cursor_index + column_idx]
12195 .anchor
12196 .bias_left(&display_snapshot);
12197 edits.push((anchor..anchor, " ".repeat(spaces_needed as usize)));
12198 }
12199 rows_column_offset[row_idx] += spaces_needed;
12200
12201 cursor_index += *cursor_count;
12202 }
12203 }
12204
12205 if !edits.is_empty() {
12206 self.transact(window, cx, |editor, _window, cx| {
12207 editor.edit(edits, cx);
12208 });
12209 }
12210 }
12211
12212 pub fn disable_breakpoint(
12213 &mut self,
12214 _: &crate::actions::DisableBreakpoint,
12215 window: &mut Window,
12216 cx: &mut Context<Self>,
12217 ) {
12218 if self.breakpoint_store.is_none() {
12219 return;
12220 }
12221
12222 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
12223 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
12224 continue;
12225 };
12226 self.edit_breakpoint_at_anchor(
12227 anchor,
12228 breakpoint,
12229 BreakpointEditAction::InvertState,
12230 cx,
12231 );
12232 }
12233 }
12234
12235 pub fn toggle_breakpoint(
12236 &mut self,
12237 _: &crate::actions::ToggleBreakpoint,
12238 window: &mut Window,
12239 cx: &mut Context<Self>,
12240 ) {
12241 if self.breakpoint_store.is_none() {
12242 return;
12243 }
12244
12245 let snapshot = self.snapshot(window, cx);
12246 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
12247 if self.gutter_breakpoint_indicator.0.is_some() {
12248 let display_row = anchor
12249 .to_point(snapshot.buffer_snapshot())
12250 .to_display_point(&snapshot.display_snapshot)
12251 .row();
12252 self.update_breakpoint_collision_on_toggle(
12253 display_row,
12254 &BreakpointEditAction::Toggle,
12255 );
12256 }
12257
12258 if let Some(breakpoint) = breakpoint {
12259 self.edit_breakpoint_at_anchor(
12260 anchor,
12261 breakpoint,
12262 BreakpointEditAction::Toggle,
12263 cx,
12264 );
12265 } else {
12266 self.edit_breakpoint_at_anchor(
12267 anchor,
12268 Breakpoint::new_standard(),
12269 BreakpointEditAction::Toggle,
12270 cx,
12271 );
12272 }
12273 }
12274 }
12275
12276 fn update_breakpoint_collision_on_toggle(
12277 &mut self,
12278 display_row: DisplayRow,
12279 edit_action: &BreakpointEditAction,
12280 ) {
12281 if let Some(ref mut breakpoint_indicator) = self.gutter_breakpoint_indicator.0 {
12282 if breakpoint_indicator.display_row == display_row
12283 && matches!(edit_action, BreakpointEditAction::Toggle)
12284 {
12285 breakpoint_indicator.collides_with_existing_breakpoint =
12286 !breakpoint_indicator.collides_with_existing_breakpoint;
12287 }
12288 }
12289 }
12290
12291 pub fn edit_breakpoint_at_anchor(
12292 &mut self,
12293 breakpoint_position: Anchor,
12294 breakpoint: Breakpoint,
12295 edit_action: BreakpointEditAction,
12296 cx: &mut Context<Self>,
12297 ) {
12298 let Some(breakpoint_store) = &self.breakpoint_store else {
12299 return;
12300 };
12301 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
12302 let Some((position, _)) = buffer_snapshot.anchor_to_buffer_anchor(breakpoint_position)
12303 else {
12304 return;
12305 };
12306 let Some(buffer) = self.buffer.read(cx).buffer(position.buffer_id) else {
12307 return;
12308 };
12309
12310 breakpoint_store.update(cx, |breakpoint_store, cx| {
12311 breakpoint_store.toggle_breakpoint(
12312 buffer,
12313 BreakpointWithPosition {
12314 position,
12315 bp: breakpoint,
12316 },
12317 edit_action,
12318 cx,
12319 );
12320 });
12321
12322 cx.notify();
12323 }
12324
12325 #[cfg(any(test, feature = "test-support"))]
12326 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
12327 self.breakpoint_store.clone()
12328 }
12329
12330 pub fn prepare_restore_change(
12331 &self,
12332 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
12333 hunk: &MultiBufferDiffHunk,
12334 cx: &mut App,
12335 ) -> Option<()> {
12336 if hunk.is_created_file() {
12337 return None;
12338 }
12339 let multi_buffer = self.buffer.read(cx);
12340 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
12341 let diff_snapshot = multi_buffer_snapshot.diff_for_buffer_id(hunk.buffer_id)?;
12342 let original_text = diff_snapshot
12343 .base_text()
12344 .as_rope()
12345 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
12346 let buffer = multi_buffer.buffer(hunk.buffer_id)?;
12347 let buffer = buffer.read(cx);
12348 let buffer_snapshot = buffer.snapshot();
12349 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
12350 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
12351 probe
12352 .0
12353 .start
12354 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
12355 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
12356 }) {
12357 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
12358 Some(())
12359 } else {
12360 None
12361 }
12362 }
12363
12364 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
12365 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
12366 }
12367
12368 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
12369 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
12370 }
12371
12372 pub fn rotate_selections_forward(
12373 &mut self,
12374 _: &RotateSelectionsForward,
12375 window: &mut Window,
12376 cx: &mut Context<Self>,
12377 ) {
12378 self.rotate_selections(window, cx, false)
12379 }
12380
12381 pub fn rotate_selections_backward(
12382 &mut self,
12383 _: &RotateSelectionsBackward,
12384 window: &mut Window,
12385 cx: &mut Context<Self>,
12386 ) {
12387 self.rotate_selections(window, cx, true)
12388 }
12389
12390 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
12391 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12392 let display_snapshot = self.display_snapshot(cx);
12393 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
12394
12395 if selections.len() < 2 {
12396 return;
12397 }
12398
12399 let (edits, new_selections) = {
12400 let buffer = self.buffer.read(cx).read(cx);
12401 let has_selections = selections.iter().any(|s| !s.is_empty());
12402 if has_selections {
12403 let mut selected_texts: Vec<String> = selections
12404 .iter()
12405 .map(|selection| {
12406 buffer
12407 .text_for_range(selection.start..selection.end)
12408 .collect()
12409 })
12410 .collect();
12411
12412 if reverse {
12413 selected_texts.rotate_left(1);
12414 } else {
12415 selected_texts.rotate_right(1);
12416 }
12417
12418 let mut offset_delta: i64 = 0;
12419 let mut new_selections = Vec::new();
12420 let edits: Vec<_> = selections
12421 .iter()
12422 .zip(selected_texts.iter())
12423 .map(|(selection, new_text)| {
12424 let old_len = (selection.end.0 - selection.start.0) as i64;
12425 let new_len = new_text.len() as i64;
12426 let adjusted_start =
12427 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
12428 let adjusted_end =
12429 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
12430
12431 new_selections.push(Selection {
12432 id: selection.id,
12433 start: adjusted_start,
12434 end: adjusted_end,
12435 reversed: selection.reversed,
12436 goal: selection.goal,
12437 });
12438
12439 offset_delta += new_len - old_len;
12440 (selection.start..selection.end, new_text.clone())
12441 })
12442 .collect();
12443 (edits, new_selections)
12444 } else {
12445 let mut all_rows: Vec<u32> = selections
12446 .iter()
12447 .map(|selection| buffer.offset_to_point(selection.start).row)
12448 .collect();
12449 all_rows.sort_unstable();
12450 all_rows.dedup();
12451
12452 if all_rows.len() < 2 {
12453 return;
12454 }
12455
12456 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
12457 .iter()
12458 .map(|&row| {
12459 let start = Point::new(row, 0);
12460 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12461 buffer.point_to_offset(start)..buffer.point_to_offset(end)
12462 })
12463 .collect();
12464
12465 let mut line_texts: Vec<String> = line_ranges
12466 .iter()
12467 .map(|range| buffer.text_for_range(range.clone()).collect())
12468 .collect();
12469
12470 if reverse {
12471 line_texts.rotate_left(1);
12472 } else {
12473 line_texts.rotate_right(1);
12474 }
12475
12476 let edits = line_ranges
12477 .iter()
12478 .zip(line_texts.iter())
12479 .map(|(range, new_text)| (range.clone(), new_text.clone()))
12480 .collect();
12481
12482 let num_rows = all_rows.len();
12483 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
12484 .iter()
12485 .enumerate()
12486 .map(|(i, &row)| (row, i))
12487 .collect();
12488
12489 // Compute new line start offsets after rotation (handles CRLF)
12490 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
12491 let first_line_start = line_ranges[0].start.0;
12492 let mut new_line_starts: Vec<usize> = vec![first_line_start];
12493 for text in line_texts.iter().take(num_rows - 1) {
12494 let prev_start = *new_line_starts.last().unwrap();
12495 new_line_starts.push(prev_start + text.len() + newline_len);
12496 }
12497
12498 let new_selections = selections
12499 .iter()
12500 .map(|selection| {
12501 let point = buffer.offset_to_point(selection.start);
12502 let old_index = row_to_index[&point.row];
12503 let new_index = if reverse {
12504 (old_index + num_rows - 1) % num_rows
12505 } else {
12506 (old_index + 1) % num_rows
12507 };
12508 let new_offset =
12509 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
12510 Selection {
12511 id: selection.id,
12512 start: new_offset,
12513 end: new_offset,
12514 reversed: selection.reversed,
12515 goal: selection.goal,
12516 }
12517 })
12518 .collect();
12519
12520 (edits, new_selections)
12521 }
12522 };
12523
12524 self.transact(window, cx, |this, window, cx| {
12525 this.buffer.update(cx, |buffer, cx| {
12526 buffer.edit(edits, None, cx);
12527 });
12528 this.change_selections(Default::default(), window, cx, |s| {
12529 s.select(new_selections);
12530 });
12531 });
12532 }
12533
12534 fn manipulate_lines<M>(
12535 &mut self,
12536 window: &mut Window,
12537 cx: &mut Context<Self>,
12538 mut manipulate: M,
12539 ) where
12540 M: FnMut(&str) -> LineManipulationResult,
12541 {
12542 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12543
12544 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12545 let buffer = self.buffer.read(cx).snapshot(cx);
12546
12547 let mut edits = Vec::new();
12548
12549 let selections = self.selections.all::<Point>(&display_map);
12550 let mut selections = selections.iter().peekable();
12551 let mut contiguous_row_selections = Vec::new();
12552 let mut new_selections = Vec::new();
12553 let mut added_lines = 0;
12554 let mut removed_lines = 0;
12555
12556 while let Some(selection) = selections.next() {
12557 let (start_row, end_row) = consume_contiguous_rows(
12558 &mut contiguous_row_selections,
12559 selection,
12560 &display_map,
12561 &mut selections,
12562 );
12563
12564 let start_point = Point::new(start_row.0, 0);
12565 let end_point = Point::new(
12566 end_row.previous_row().0,
12567 buffer.line_len(end_row.previous_row()),
12568 );
12569 let text = buffer
12570 .text_for_range(start_point..end_point)
12571 .collect::<String>();
12572
12573 let LineManipulationResult {
12574 new_text,
12575 line_count_before,
12576 line_count_after,
12577 } = manipulate(&text);
12578
12579 edits.push((start_point..end_point, new_text));
12580
12581 // Selections must change based on added and removed line count
12582 let start_row =
12583 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
12584 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
12585 new_selections.push(Selection {
12586 id: selection.id,
12587 start: start_row,
12588 end: end_row,
12589 goal: SelectionGoal::None,
12590 reversed: selection.reversed,
12591 });
12592
12593 if line_count_after > line_count_before {
12594 added_lines += line_count_after - line_count_before;
12595 } else if line_count_before > line_count_after {
12596 removed_lines += line_count_before - line_count_after;
12597 }
12598 }
12599
12600 self.transact(window, cx, |this, window, cx| {
12601 let buffer = this.buffer.update(cx, |buffer, cx| {
12602 buffer.edit(edits, None, cx);
12603 buffer.snapshot(cx)
12604 });
12605
12606 // Recalculate offsets on newly edited buffer
12607 let new_selections = new_selections
12608 .iter()
12609 .map(|s| {
12610 let start_point = Point::new(s.start.0, 0);
12611 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
12612 Selection {
12613 id: s.id,
12614 start: buffer.point_to_offset(start_point),
12615 end: buffer.point_to_offset(end_point),
12616 goal: s.goal,
12617 reversed: s.reversed,
12618 }
12619 })
12620 .collect();
12621
12622 this.change_selections(Default::default(), window, cx, |s| {
12623 s.select(new_selections);
12624 });
12625
12626 this.request_autoscroll(Autoscroll::fit(), cx);
12627 });
12628 }
12629
12630 fn manipulate_immutable_lines<Fn>(
12631 &mut self,
12632 window: &mut Window,
12633 cx: &mut Context<Self>,
12634 mut callback: Fn,
12635 ) where
12636 Fn: FnMut(&mut Vec<&str>),
12637 {
12638 self.manipulate_lines(window, cx, |text| {
12639 let mut lines: Vec<&str> = text.split('\n').collect();
12640 let line_count_before = lines.len();
12641
12642 callback(&mut lines);
12643
12644 LineManipulationResult {
12645 new_text: lines.join("\n"),
12646 line_count_before,
12647 line_count_after: lines.len(),
12648 }
12649 });
12650 }
12651
12652 fn manipulate_mutable_lines<Fn>(
12653 &mut self,
12654 window: &mut Window,
12655 cx: &mut Context<Self>,
12656 mut callback: Fn,
12657 ) where
12658 Fn: FnMut(&mut Vec<Cow<'_, str>>),
12659 {
12660 self.manipulate_lines(window, cx, |text| {
12661 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
12662 let line_count_before = lines.len();
12663
12664 callback(&mut lines);
12665
12666 LineManipulationResult {
12667 new_text: lines.join("\n"),
12668 line_count_before,
12669 line_count_after: lines.len(),
12670 }
12671 });
12672 }
12673
12674 pub fn convert_indentation_to_spaces(
12675 &mut self,
12676 _: &ConvertIndentationToSpaces,
12677 window: &mut Window,
12678 cx: &mut Context<Self>,
12679 ) {
12680 let settings = self.buffer.read(cx).language_settings(cx);
12681 let tab_size = settings.tab_size.get() as usize;
12682
12683 self.manipulate_mutable_lines(window, cx, |lines| {
12684 // Allocates a reasonably sized scratch buffer once for the whole loop
12685 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12686 // Avoids recomputing spaces that could be inserted many times
12687 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12688 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12689 .collect();
12690
12691 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12692 let mut chars = line.as_ref().chars();
12693 let mut col = 0;
12694 let mut changed = false;
12695
12696 for ch in chars.by_ref() {
12697 match ch {
12698 ' ' => {
12699 reindented_line.push(' ');
12700 col += 1;
12701 }
12702 '\t' => {
12703 // \t are converted to spaces depending on the current column
12704 let spaces_len = tab_size - (col % tab_size);
12705 reindented_line.extend(&space_cache[spaces_len - 1]);
12706 col += spaces_len;
12707 changed = true;
12708 }
12709 _ => {
12710 // If we dont append before break, the character is consumed
12711 reindented_line.push(ch);
12712 break;
12713 }
12714 }
12715 }
12716
12717 if !changed {
12718 reindented_line.clear();
12719 continue;
12720 }
12721 // Append the rest of the line and replace old reference with new one
12722 reindented_line.extend(chars);
12723 *line = Cow::Owned(reindented_line.clone());
12724 reindented_line.clear();
12725 }
12726 });
12727 }
12728
12729 pub fn convert_indentation_to_tabs(
12730 &mut self,
12731 _: &ConvertIndentationToTabs,
12732 window: &mut Window,
12733 cx: &mut Context<Self>,
12734 ) {
12735 let settings = self.buffer.read(cx).language_settings(cx);
12736 let tab_size = settings.tab_size.get() as usize;
12737
12738 self.manipulate_mutable_lines(window, cx, |lines| {
12739 // Allocates a reasonably sized buffer once for the whole loop
12740 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12741 // Avoids recomputing spaces that could be inserted many times
12742 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12743 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12744 .collect();
12745
12746 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12747 let mut chars = line.chars();
12748 let mut spaces_count = 0;
12749 let mut first_non_indent_char = None;
12750 let mut changed = false;
12751
12752 for ch in chars.by_ref() {
12753 match ch {
12754 ' ' => {
12755 // Keep track of spaces. Append \t when we reach tab_size
12756 spaces_count += 1;
12757 changed = true;
12758 if spaces_count == tab_size {
12759 reindented_line.push('\t');
12760 spaces_count = 0;
12761 }
12762 }
12763 '\t' => {
12764 reindented_line.push('\t');
12765 spaces_count = 0;
12766 }
12767 _ => {
12768 // Dont append it yet, we might have remaining spaces
12769 first_non_indent_char = Some(ch);
12770 break;
12771 }
12772 }
12773 }
12774
12775 if !changed {
12776 reindented_line.clear();
12777 continue;
12778 }
12779 // Remaining spaces that didn't make a full tab stop
12780 if spaces_count > 0 {
12781 reindented_line.extend(&space_cache[spaces_count - 1]);
12782 }
12783 // If we consume an extra character that was not indentation, add it back
12784 if let Some(extra_char) = first_non_indent_char {
12785 reindented_line.push(extra_char);
12786 }
12787 // Append the rest of the line and replace old reference with new one
12788 reindented_line.extend(chars);
12789 *line = Cow::Owned(reindented_line.clone());
12790 reindented_line.clear();
12791 }
12792 });
12793 }
12794
12795 pub fn convert_to_upper_case(
12796 &mut self,
12797 _: &ConvertToUpperCase,
12798 window: &mut Window,
12799 cx: &mut Context<Self>,
12800 ) {
12801 self.manipulate_text(window, cx, |text| text.to_uppercase())
12802 }
12803
12804 pub fn convert_to_lower_case(
12805 &mut self,
12806 _: &ConvertToLowerCase,
12807 window: &mut Window,
12808 cx: &mut Context<Self>,
12809 ) {
12810 self.manipulate_text(window, cx, |text| text.to_lowercase())
12811 }
12812
12813 pub fn convert_to_title_case(
12814 &mut self,
12815 _: &ConvertToTitleCase,
12816 window: &mut Window,
12817 cx: &mut Context<Self>,
12818 ) {
12819 self.manipulate_text(window, cx, |text| {
12820 Self::convert_text_case(text, Case::Title)
12821 })
12822 }
12823
12824 pub fn convert_to_snake_case(
12825 &mut self,
12826 _: &ConvertToSnakeCase,
12827 window: &mut Window,
12828 cx: &mut Context<Self>,
12829 ) {
12830 self.manipulate_text(window, cx, |text| {
12831 Self::convert_text_case(text, Case::Snake)
12832 })
12833 }
12834
12835 pub fn convert_to_kebab_case(
12836 &mut self,
12837 _: &ConvertToKebabCase,
12838 window: &mut Window,
12839 cx: &mut Context<Self>,
12840 ) {
12841 self.manipulate_text(window, cx, |text| {
12842 Self::convert_text_case(text, Case::Kebab)
12843 })
12844 }
12845
12846 pub fn convert_to_upper_camel_case(
12847 &mut self,
12848 _: &ConvertToUpperCamelCase,
12849 window: &mut Window,
12850 cx: &mut Context<Self>,
12851 ) {
12852 self.manipulate_text(window, cx, |text| {
12853 Self::convert_text_case(text, Case::UpperCamel)
12854 })
12855 }
12856
12857 pub fn convert_to_lower_camel_case(
12858 &mut self,
12859 _: &ConvertToLowerCamelCase,
12860 window: &mut Window,
12861 cx: &mut Context<Self>,
12862 ) {
12863 self.manipulate_text(window, cx, |text| {
12864 Self::convert_text_case(text, Case::Camel)
12865 })
12866 }
12867
12868 pub fn convert_to_opposite_case(
12869 &mut self,
12870 _: &ConvertToOppositeCase,
12871 window: &mut Window,
12872 cx: &mut Context<Self>,
12873 ) {
12874 self.manipulate_text(window, cx, |text| {
12875 text.chars()
12876 .fold(String::with_capacity(text.len()), |mut t, c| {
12877 if c.is_uppercase() {
12878 t.extend(c.to_lowercase());
12879 } else {
12880 t.extend(c.to_uppercase());
12881 }
12882 t
12883 })
12884 })
12885 }
12886
12887 pub fn convert_to_sentence_case(
12888 &mut self,
12889 _: &ConvertToSentenceCase,
12890 window: &mut Window,
12891 cx: &mut Context<Self>,
12892 ) {
12893 self.manipulate_text(window, cx, |text| {
12894 Self::convert_text_case(text, Case::Sentence)
12895 })
12896 }
12897
12898 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
12899 self.manipulate_text(window, cx, |text| {
12900 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
12901 if has_upper_case_characters {
12902 text.to_lowercase()
12903 } else {
12904 text.to_uppercase()
12905 }
12906 })
12907 }
12908
12909 pub fn convert_to_rot13(
12910 &mut self,
12911 _: &ConvertToRot13,
12912 window: &mut Window,
12913 cx: &mut Context<Self>,
12914 ) {
12915 self.manipulate_text(window, cx, |text| {
12916 text.chars()
12917 .map(|c| match c {
12918 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
12919 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
12920 _ => c,
12921 })
12922 .collect()
12923 })
12924 }
12925
12926 fn convert_text_case(text: &str, case: Case) -> String {
12927 text.lines()
12928 .map(|line| {
12929 let trimmed_start = line.trim_start();
12930 let leading = &line[..line.len() - trimmed_start.len()];
12931 let trimmed = trimmed_start.trim_end();
12932 let trailing = &trimmed_start[trimmed.len()..];
12933 format!("{}{}{}", leading, trimmed.to_case(case), trailing)
12934 })
12935 .join("\n")
12936 }
12937
12938 pub fn convert_to_rot47(
12939 &mut self,
12940 _: &ConvertToRot47,
12941 window: &mut Window,
12942 cx: &mut Context<Self>,
12943 ) {
12944 self.manipulate_text(window, cx, |text| {
12945 text.chars()
12946 .map(|c| {
12947 let code_point = c as u32;
12948 if code_point >= 33 && code_point <= 126 {
12949 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
12950 }
12951 c
12952 })
12953 .collect()
12954 })
12955 }
12956
12957 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
12958 where
12959 Fn: FnMut(&str) -> String,
12960 {
12961 let buffer = self.buffer.read(cx).snapshot(cx);
12962
12963 let mut new_selections = Vec::new();
12964 let mut edits = Vec::new();
12965 let mut selection_adjustment = 0isize;
12966
12967 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
12968 let selection_is_empty = selection.is_empty();
12969
12970 let (start, end) = if selection_is_empty {
12971 let (word_range, _) = buffer.surrounding_word(selection.start, None);
12972 (word_range.start, word_range.end)
12973 } else {
12974 (
12975 buffer.point_to_offset(selection.start),
12976 buffer.point_to_offset(selection.end),
12977 )
12978 };
12979
12980 let text = buffer.text_for_range(start..end).collect::<String>();
12981 let old_length = text.len() as isize;
12982 let text = callback(&text);
12983
12984 new_selections.push(Selection {
12985 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
12986 end: MultiBufferOffset(
12987 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
12988 ),
12989 goal: SelectionGoal::None,
12990 id: selection.id,
12991 reversed: selection.reversed,
12992 });
12993
12994 selection_adjustment += old_length - text.len() as isize;
12995
12996 edits.push((start..end, text));
12997 }
12998
12999 self.transact(window, cx, |this, window, cx| {
13000 this.buffer.update(cx, |buffer, cx| {
13001 buffer.edit(edits, None, cx);
13002 });
13003
13004 this.change_selections(Default::default(), window, cx, |s| {
13005 s.select(new_selections);
13006 });
13007
13008 this.request_autoscroll(Autoscroll::fit(), cx);
13009 });
13010 }
13011
13012 pub fn move_selection_on_drop(
13013 &mut self,
13014 selection: &Selection<Anchor>,
13015 target: DisplayPoint,
13016 is_cut: bool,
13017 window: &mut Window,
13018 cx: &mut Context<Self>,
13019 ) {
13020 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13021 let buffer = display_map.buffer_snapshot();
13022 let mut edits = Vec::new();
13023 let insert_point = display_map
13024 .clip_point(target, Bias::Left)
13025 .to_point(&display_map);
13026 let text = buffer
13027 .text_for_range(selection.start..selection.end)
13028 .collect::<String>();
13029 if is_cut {
13030 edits.push(((selection.start..selection.end), String::new()));
13031 }
13032 let insert_anchor = buffer.anchor_before(insert_point);
13033 edits.push(((insert_anchor..insert_anchor), text));
13034 let last_edit_start = insert_anchor.bias_left(buffer);
13035 let last_edit_end = insert_anchor.bias_right(buffer);
13036 self.transact(window, cx, |this, window, cx| {
13037 this.buffer.update(cx, |buffer, cx| {
13038 buffer.edit(edits, None, cx);
13039 });
13040 this.change_selections(Default::default(), window, cx, |s| {
13041 s.select_anchor_ranges([last_edit_start..last_edit_end]);
13042 });
13043 });
13044 }
13045
13046 pub fn clear_selection_drag_state(&mut self) {
13047 self.selection_drag_state = SelectionDragState::None;
13048 }
13049
13050 pub fn duplicate(
13051 &mut self,
13052 upwards: bool,
13053 whole_lines: bool,
13054 window: &mut Window,
13055 cx: &mut Context<Self>,
13056 ) {
13057 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13058
13059 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13060 let buffer = display_map.buffer_snapshot();
13061 let selections = self.selections.all::<Point>(&display_map);
13062
13063 let mut edits = Vec::new();
13064 let mut selections_iter = selections.iter().peekable();
13065 while let Some(selection) = selections_iter.next() {
13066 let mut rows = selection.spanned_rows(false, &display_map);
13067 // duplicate line-wise
13068 if whole_lines || selection.start == selection.end {
13069 // Avoid duplicating the same lines twice.
13070 while let Some(next_selection) = selections_iter.peek() {
13071 let next_rows = next_selection.spanned_rows(false, &display_map);
13072 if next_rows.start < rows.end {
13073 rows.end = next_rows.end;
13074 selections_iter.next().unwrap();
13075 } else {
13076 break;
13077 }
13078 }
13079
13080 // Copy the text from the selected row region and splice it either at the start
13081 // or end of the region.
13082 let start = Point::new(rows.start.0, 0);
13083 let end = Point::new(
13084 rows.end.previous_row().0,
13085 buffer.line_len(rows.end.previous_row()),
13086 );
13087
13088 let mut text = buffer.text_for_range(start..end).collect::<String>();
13089
13090 let insert_location = if upwards {
13091 // When duplicating upward, we need to insert before the current line.
13092 // If we're on the last line and it doesn't end with a newline,
13093 // we need to add a newline before the duplicated content.
13094 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
13095 && buffer.max_point().column > 0
13096 && !text.ends_with('\n');
13097
13098 if needs_leading_newline {
13099 text.insert(0, '\n');
13100 end
13101 } else {
13102 text.push('\n');
13103 Point::new(rows.start.0, 0)
13104 }
13105 } else {
13106 text.push('\n');
13107 start
13108 };
13109 edits.push((insert_location..insert_location, text));
13110 } else {
13111 // duplicate character-wise
13112 let start = selection.start;
13113 let end = selection.end;
13114 let text = buffer.text_for_range(start..end).collect::<String>();
13115 edits.push((selection.end..selection.end, text));
13116 }
13117 }
13118
13119 self.transact(window, cx, |this, window, cx| {
13120 this.buffer.update(cx, |buffer, cx| {
13121 buffer.edit(edits, None, cx);
13122 });
13123
13124 // When duplicating upward with whole lines, move the cursor to the duplicated line
13125 if upwards && whole_lines {
13126 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
13127
13128 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13129 let mut new_ranges = Vec::new();
13130 let selections = s.all::<Point>(&display_map);
13131 let mut selections_iter = selections.iter().peekable();
13132
13133 while let Some(first_selection) = selections_iter.next() {
13134 // Group contiguous selections together to find the total row span
13135 let mut group_selections = vec![first_selection];
13136 let mut rows = first_selection.spanned_rows(false, &display_map);
13137
13138 while let Some(next_selection) = selections_iter.peek() {
13139 let next_rows = next_selection.spanned_rows(false, &display_map);
13140 if next_rows.start < rows.end {
13141 rows.end = next_rows.end;
13142 group_selections.push(selections_iter.next().unwrap());
13143 } else {
13144 break;
13145 }
13146 }
13147
13148 let row_count = rows.end.0 - rows.start.0;
13149
13150 // Move all selections in this group up by the total number of duplicated rows
13151 for selection in group_selections {
13152 let new_start = Point::new(
13153 selection.start.row.saturating_sub(row_count),
13154 selection.start.column,
13155 );
13156
13157 let new_end = Point::new(
13158 selection.end.row.saturating_sub(row_count),
13159 selection.end.column,
13160 );
13161
13162 new_ranges.push(new_start..new_end);
13163 }
13164 }
13165
13166 s.select_ranges(new_ranges);
13167 });
13168 }
13169
13170 this.request_autoscroll(Autoscroll::fit(), cx);
13171 });
13172 }
13173
13174 pub fn duplicate_line_up(
13175 &mut self,
13176 _: &DuplicateLineUp,
13177 window: &mut Window,
13178 cx: &mut Context<Self>,
13179 ) {
13180 self.duplicate(true, true, window, cx);
13181 }
13182
13183 pub fn duplicate_line_down(
13184 &mut self,
13185 _: &DuplicateLineDown,
13186 window: &mut Window,
13187 cx: &mut Context<Self>,
13188 ) {
13189 self.duplicate(false, true, window, cx);
13190 }
13191
13192 pub fn duplicate_selection(
13193 &mut self,
13194 _: &DuplicateSelection,
13195 window: &mut Window,
13196 cx: &mut Context<Self>,
13197 ) {
13198 self.duplicate(false, false, window, cx);
13199 }
13200
13201 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
13202 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13203 if self.mode.is_single_line() {
13204 cx.propagate();
13205 return;
13206 }
13207
13208 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13209 let buffer = self.buffer.read(cx).snapshot(cx);
13210
13211 let mut edits = Vec::new();
13212 let mut unfold_ranges = Vec::new();
13213 let mut refold_creases = Vec::new();
13214
13215 let selections = self.selections.all::<Point>(&display_map);
13216 let mut selections = selections.iter().peekable();
13217 let mut contiguous_row_selections = Vec::new();
13218 let mut new_selections = Vec::new();
13219
13220 while let Some(selection) = selections.next() {
13221 // Find all the selections that span a contiguous row range
13222 let (start_row, end_row) = consume_contiguous_rows(
13223 &mut contiguous_row_selections,
13224 selection,
13225 &display_map,
13226 &mut selections,
13227 );
13228
13229 // Move the text spanned by the row range to be before the line preceding the row range
13230 if start_row.0 > 0 {
13231 let range_to_move = Point::new(
13232 start_row.previous_row().0,
13233 buffer.line_len(start_row.previous_row()),
13234 )
13235 ..Point::new(
13236 end_row.previous_row().0,
13237 buffer.line_len(end_row.previous_row()),
13238 );
13239 let insertion_point = display_map
13240 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
13241 .0;
13242
13243 // Don't move lines across excerpts
13244 if buffer
13245 .excerpt_containing(insertion_point..range_to_move.end)
13246 .is_some()
13247 {
13248 let text = buffer
13249 .text_for_range(range_to_move.clone())
13250 .flat_map(|s| s.chars())
13251 .skip(1)
13252 .chain(['\n'])
13253 .collect::<String>();
13254
13255 edits.push((
13256 buffer.anchor_after(range_to_move.start)
13257 ..buffer.anchor_before(range_to_move.end),
13258 String::new(),
13259 ));
13260 let insertion_anchor = buffer.anchor_after(insertion_point);
13261 edits.push((insertion_anchor..insertion_anchor, text));
13262
13263 let row_delta = range_to_move.start.row - insertion_point.row + 1;
13264
13265 // Move selections up
13266 new_selections.extend(contiguous_row_selections.drain(..).map(
13267 |mut selection| {
13268 selection.start.row -= row_delta;
13269 selection.end.row -= row_delta;
13270 selection
13271 },
13272 ));
13273
13274 // Move folds up
13275 unfold_ranges.push(range_to_move.clone());
13276 for fold in display_map.folds_in_range(
13277 buffer.anchor_before(range_to_move.start)
13278 ..buffer.anchor_after(range_to_move.end),
13279 ) {
13280 let mut start = fold.range.start.to_point(&buffer);
13281 let mut end = fold.range.end.to_point(&buffer);
13282 start.row -= row_delta;
13283 end.row -= row_delta;
13284 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
13285 }
13286 }
13287 }
13288
13289 // If we didn't move line(s), preserve the existing selections
13290 new_selections.append(&mut contiguous_row_selections);
13291 }
13292
13293 self.transact(window, cx, |this, window, cx| {
13294 this.unfold_ranges(&unfold_ranges, true, true, cx);
13295 this.buffer.update(cx, |buffer, cx| {
13296 for (range, text) in edits {
13297 buffer.edit([(range, text)], None, cx);
13298 }
13299 });
13300 this.fold_creases(refold_creases, true, window, cx);
13301 this.change_selections(Default::default(), window, cx, |s| {
13302 s.select(new_selections);
13303 })
13304 });
13305 }
13306
13307 pub fn move_line_down(
13308 &mut self,
13309 _: &MoveLineDown,
13310 window: &mut Window,
13311 cx: &mut Context<Self>,
13312 ) {
13313 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13314 if self.mode.is_single_line() {
13315 cx.propagate();
13316 return;
13317 }
13318
13319 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13320 let buffer = self.buffer.read(cx).snapshot(cx);
13321
13322 let mut edits = Vec::new();
13323 let mut unfold_ranges = Vec::new();
13324 let mut refold_creases = Vec::new();
13325
13326 let selections = self.selections.all::<Point>(&display_map);
13327 let mut selections = selections.iter().peekable();
13328 let mut contiguous_row_selections = Vec::new();
13329 let mut new_selections = Vec::new();
13330
13331 while let Some(selection) = selections.next() {
13332 // Find all the selections that span a contiguous row range
13333 let (start_row, end_row) = consume_contiguous_rows(
13334 &mut contiguous_row_selections,
13335 selection,
13336 &display_map,
13337 &mut selections,
13338 );
13339
13340 // Move the text spanned by the row range to be after the last line of the row range
13341 if end_row.0 <= buffer.max_point().row {
13342 let range_to_move =
13343 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
13344 let insertion_point = display_map
13345 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
13346 .0;
13347
13348 // Don't move lines across excerpt boundaries
13349 if buffer
13350 .excerpt_containing(range_to_move.start..insertion_point)
13351 .is_some()
13352 {
13353 let mut text = String::from("\n");
13354 text.extend(buffer.text_for_range(range_to_move.clone()));
13355 text.pop(); // Drop trailing newline
13356 edits.push((
13357 buffer.anchor_after(range_to_move.start)
13358 ..buffer.anchor_before(range_to_move.end),
13359 String::new(),
13360 ));
13361 let insertion_anchor = buffer.anchor_after(insertion_point);
13362 edits.push((insertion_anchor..insertion_anchor, text));
13363
13364 let row_delta = insertion_point.row - range_to_move.end.row + 1;
13365
13366 // Move selections down
13367 new_selections.extend(contiguous_row_selections.drain(..).map(
13368 |mut selection| {
13369 selection.start.row += row_delta;
13370 selection.end.row += row_delta;
13371 selection
13372 },
13373 ));
13374
13375 // Move folds down
13376 unfold_ranges.push(range_to_move.clone());
13377 for fold in display_map.folds_in_range(
13378 buffer.anchor_before(range_to_move.start)
13379 ..buffer.anchor_after(range_to_move.end),
13380 ) {
13381 let mut start = fold.range.start.to_point(&buffer);
13382 let mut end = fold.range.end.to_point(&buffer);
13383 start.row += row_delta;
13384 end.row += row_delta;
13385 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
13386 }
13387 }
13388 }
13389
13390 // If we didn't move line(s), preserve the existing selections
13391 new_selections.append(&mut contiguous_row_selections);
13392 }
13393
13394 self.transact(window, cx, |this, window, cx| {
13395 this.unfold_ranges(&unfold_ranges, true, true, cx);
13396 this.buffer.update(cx, |buffer, cx| {
13397 for (range, text) in edits {
13398 buffer.edit([(range, text)], None, cx);
13399 }
13400 });
13401 this.fold_creases(refold_creases, true, window, cx);
13402 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
13403 });
13404 }
13405
13406 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
13407 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13408 let text_layout_details = &self.text_layout_details(window, cx);
13409 self.transact(window, cx, |this, window, cx| {
13410 let edits = this.change_selections(Default::default(), window, cx, |s| {
13411 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
13412 s.move_with(&mut |display_map, selection| {
13413 if !selection.is_empty() {
13414 return;
13415 }
13416
13417 let mut head = selection.head();
13418 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
13419 if head.column() == display_map.line_len(head.row()) {
13420 transpose_offset = display_map
13421 .buffer_snapshot()
13422 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13423 }
13424
13425 if transpose_offset == MultiBufferOffset(0) {
13426 return;
13427 }
13428
13429 *head.column_mut() += 1;
13430 head = display_map.clip_point(head, Bias::Right);
13431 let goal = SelectionGoal::HorizontalPosition(
13432 display_map
13433 .x_for_display_point(head, text_layout_details)
13434 .into(),
13435 );
13436 selection.collapse_to(head, goal);
13437
13438 let transpose_start = display_map
13439 .buffer_snapshot()
13440 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13441 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
13442 let transpose_end = display_map
13443 .buffer_snapshot()
13444 .clip_offset(transpose_offset + 1usize, Bias::Right);
13445 if let Some(ch) = display_map
13446 .buffer_snapshot()
13447 .chars_at(transpose_start)
13448 .next()
13449 {
13450 edits.push((transpose_start..transpose_offset, String::new()));
13451 edits.push((transpose_end..transpose_end, ch.to_string()));
13452 }
13453 }
13454 });
13455 edits
13456 });
13457 this.buffer
13458 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13459 let selections = this
13460 .selections
13461 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13462 this.change_selections(Default::default(), window, cx, |s| {
13463 s.select(selections);
13464 });
13465 });
13466 }
13467
13468 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
13469 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13470 if self.mode.is_single_line() {
13471 cx.propagate();
13472 return;
13473 }
13474
13475 self.rewrap_impl(RewrapOptions::default(), cx)
13476 }
13477
13478 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
13479 let buffer = self.buffer.read(cx).snapshot(cx);
13480 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13481
13482 #[derive(Clone, Debug, PartialEq)]
13483 enum CommentFormat {
13484 /// single line comment, with prefix for line
13485 Line(String),
13486 /// single line within a block comment, with prefix for line
13487 BlockLine(String),
13488 /// a single line of a block comment that includes the initial delimiter
13489 BlockCommentWithStart(BlockCommentConfig),
13490 /// a single line of a block comment that includes the ending delimiter
13491 BlockCommentWithEnd(BlockCommentConfig),
13492 }
13493
13494 // Split selections to respect paragraph, indent, and comment prefix boundaries.
13495 let wrap_ranges = selections.into_iter().flat_map(|selection| {
13496 let language_settings = buffer.language_settings_at(selection.head(), cx);
13497 let language_scope = buffer.language_scope_at(selection.head());
13498
13499 let indent_and_prefix_for_row =
13500 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
13501 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
13502 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
13503 &language_scope
13504 {
13505 let indent_end = Point::new(row, indent.len);
13506 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13507 let line_text_after_indent = buffer
13508 .text_for_range(indent_end..line_end)
13509 .collect::<String>();
13510
13511 let is_within_comment_override = buffer
13512 .language_scope_at(indent_end)
13513 .is_some_and(|scope| scope.override_name() == Some("comment"));
13514 let comment_delimiters = if is_within_comment_override {
13515 // we are within a comment syntax node, but we don't
13516 // yet know what kind of comment: block, doc or line
13517 match (
13518 language_scope.documentation_comment(),
13519 language_scope.block_comment(),
13520 ) {
13521 (Some(config), _) | (_, Some(config))
13522 if buffer.contains_str_at(indent_end, &config.start) =>
13523 {
13524 Some(CommentFormat::BlockCommentWithStart(config.clone()))
13525 }
13526 (Some(config), _) | (_, Some(config))
13527 if line_text_after_indent.ends_with(config.end.as_ref()) =>
13528 {
13529 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
13530 }
13531 (Some(config), _) | (_, Some(config))
13532 if buffer.contains_str_at(indent_end, &config.prefix) =>
13533 {
13534 Some(CommentFormat::BlockLine(config.prefix.to_string()))
13535 }
13536 (_, _) => language_scope
13537 .line_comment_prefixes()
13538 .iter()
13539 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13540 .map(|prefix| CommentFormat::Line(prefix.to_string())),
13541 }
13542 } else {
13543 // we not in an overridden comment node, but we may
13544 // be within a non-overridden line comment node
13545 language_scope
13546 .line_comment_prefixes()
13547 .iter()
13548 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13549 .map(|prefix| CommentFormat::Line(prefix.to_string()))
13550 };
13551
13552 let rewrap_prefix = language_scope
13553 .rewrap_prefixes()
13554 .iter()
13555 .find_map(|prefix_regex| {
13556 prefix_regex.find(&line_text_after_indent).map(|mat| {
13557 if mat.start() == 0 {
13558 Some(mat.as_str().to_string())
13559 } else {
13560 None
13561 }
13562 })
13563 })
13564 .flatten();
13565 (comment_delimiters, rewrap_prefix)
13566 } else {
13567 (None, None)
13568 };
13569 (indent, comment_prefix, rewrap_prefix)
13570 };
13571
13572 let mut start_row = selection.start.row;
13573 let mut end_row = selection.end.row;
13574
13575 if selection.is_empty() {
13576 let cursor_row = selection.start.row;
13577
13578 let (mut indent_size, comment_prefix, _) = indent_and_prefix_for_row(cursor_row);
13579 let line_prefix = match &comment_prefix {
13580 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
13581 Some(prefix.as_str())
13582 }
13583 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
13584 prefix, ..
13585 })) => Some(prefix.as_ref()),
13586 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13587 start: _,
13588 end: _,
13589 prefix,
13590 tab_size,
13591 })) => {
13592 indent_size.len += tab_size;
13593 Some(prefix.as_ref())
13594 }
13595 None => None,
13596 };
13597 let indent_prefix = indent_size.chars().collect::<String>();
13598 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
13599
13600 'expand_upwards: while start_row > 0 {
13601 let prev_row = start_row - 1;
13602 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
13603 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
13604 && !buffer.is_line_blank(MultiBufferRow(prev_row))
13605 {
13606 start_row = prev_row;
13607 } else {
13608 break 'expand_upwards;
13609 }
13610 }
13611
13612 'expand_downwards: while end_row < buffer.max_point().row {
13613 let next_row = end_row + 1;
13614 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
13615 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
13616 && !buffer.is_line_blank(MultiBufferRow(next_row))
13617 {
13618 end_row = next_row;
13619 } else {
13620 break 'expand_downwards;
13621 }
13622 }
13623 }
13624
13625 let mut non_blank_rows_iter = (start_row..=end_row)
13626 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
13627 .peekable();
13628
13629 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
13630 row
13631 } else {
13632 return Vec::new();
13633 };
13634
13635 let mut ranges = Vec::new();
13636
13637 let mut current_range_start = first_row;
13638 let mut prev_row = first_row;
13639 let (
13640 mut current_range_indent,
13641 mut current_range_comment_delimiters,
13642 mut current_range_rewrap_prefix,
13643 ) = indent_and_prefix_for_row(first_row);
13644
13645 for row in non_blank_rows_iter.skip(1) {
13646 let has_paragraph_break = row > prev_row + 1;
13647
13648 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
13649 indent_and_prefix_for_row(row);
13650
13651 let has_indent_change = row_indent != current_range_indent;
13652 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
13653
13654 let has_boundary_change = has_comment_change
13655 || row_rewrap_prefix.is_some()
13656 || (has_indent_change && current_range_comment_delimiters.is_some());
13657
13658 if has_paragraph_break || has_boundary_change {
13659 ranges.push((
13660 language_settings.clone(),
13661 Point::new(current_range_start, 0)
13662 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13663 current_range_indent,
13664 current_range_comment_delimiters.clone(),
13665 current_range_rewrap_prefix.clone(),
13666 ));
13667 current_range_start = row;
13668 current_range_indent = row_indent;
13669 current_range_comment_delimiters = row_comment_delimiters;
13670 current_range_rewrap_prefix = row_rewrap_prefix;
13671 }
13672 prev_row = row;
13673 }
13674
13675 ranges.push((
13676 language_settings.clone(),
13677 Point::new(current_range_start, 0)
13678 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13679 current_range_indent,
13680 current_range_comment_delimiters,
13681 current_range_rewrap_prefix,
13682 ));
13683
13684 ranges
13685 });
13686
13687 let mut edits = Vec::new();
13688 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
13689
13690 for (language_settings, wrap_range, mut indent_size, comment_prefix, rewrap_prefix) in
13691 wrap_ranges
13692 {
13693 let start_row = wrap_range.start.row;
13694 let end_row = wrap_range.end.row;
13695
13696 // Skip selections that overlap with a range that has already been rewrapped.
13697 let selection_range = start_row..end_row;
13698 if rewrapped_row_ranges
13699 .iter()
13700 .any(|range| range.overlaps(&selection_range))
13701 {
13702 continue;
13703 }
13704
13705 let tab_size = language_settings.tab_size;
13706
13707 let (line_prefix, inside_comment) = match &comment_prefix {
13708 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
13709 (Some(prefix.as_str()), true)
13710 }
13711 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
13712 (Some(prefix.as_ref()), true)
13713 }
13714 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13715 start: _,
13716 end: _,
13717 prefix,
13718 tab_size,
13719 })) => {
13720 indent_size.len += tab_size;
13721 (Some(prefix.as_ref()), true)
13722 }
13723 None => (None, false),
13724 };
13725 let indent_prefix = indent_size.chars().collect::<String>();
13726 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
13727
13728 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
13729 RewrapBehavior::InComments => inside_comment,
13730 RewrapBehavior::InSelections => !wrap_range.is_empty(),
13731 RewrapBehavior::Anywhere => true,
13732 };
13733
13734 let should_rewrap = options.override_language_settings
13735 || allow_rewrap_based_on_language
13736 || self.hard_wrap.is_some();
13737 if !should_rewrap {
13738 continue;
13739 }
13740
13741 let start = Point::new(start_row, 0);
13742 let start_offset = ToOffset::to_offset(&start, &buffer);
13743 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
13744 let selection_text = buffer.text_for_range(start..end).collect::<String>();
13745 let mut first_line_delimiter = None;
13746 let mut last_line_delimiter = None;
13747 let Some(lines_without_prefixes) = selection_text
13748 .lines()
13749 .enumerate()
13750 .map(|(ix, line)| {
13751 let line_trimmed = line.trim_start();
13752 if rewrap_prefix.is_some() && ix > 0 {
13753 Ok(line_trimmed)
13754 } else if let Some(
13755 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13756 start,
13757 prefix,
13758 end,
13759 tab_size,
13760 })
13761 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
13762 start,
13763 prefix,
13764 end,
13765 tab_size,
13766 }),
13767 ) = &comment_prefix
13768 {
13769 let line_trimmed = line_trimmed
13770 .strip_prefix(start.as_ref())
13771 .map(|s| {
13772 let mut indent_size = indent_size;
13773 indent_size.len -= tab_size;
13774 let indent_prefix: String = indent_size.chars().collect();
13775 first_line_delimiter = Some((indent_prefix, start));
13776 s.trim_start()
13777 })
13778 .unwrap_or(line_trimmed);
13779 let line_trimmed = line_trimmed
13780 .strip_suffix(end.as_ref())
13781 .map(|s| {
13782 last_line_delimiter = Some(end);
13783 s.trim_end()
13784 })
13785 .unwrap_or(line_trimmed);
13786 let line_trimmed = line_trimmed
13787 .strip_prefix(prefix.as_ref())
13788 .unwrap_or(line_trimmed);
13789 Ok(line_trimmed)
13790 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
13791 line_trimmed.strip_prefix(prefix).with_context(|| {
13792 format!("line did not start with prefix {prefix:?}: {line:?}")
13793 })
13794 } else {
13795 line_trimmed
13796 .strip_prefix(&line_prefix.trim_start())
13797 .with_context(|| {
13798 format!("line did not start with prefix {line_prefix:?}: {line:?}")
13799 })
13800 }
13801 })
13802 .collect::<Result<Vec<_>, _>>()
13803 .log_err()
13804 else {
13805 continue;
13806 };
13807
13808 let wrap_column = options.line_length.or(self.hard_wrap).unwrap_or_else(|| {
13809 buffer
13810 .language_settings_at(Point::new(start_row, 0), cx)
13811 .preferred_line_length as usize
13812 });
13813
13814 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
13815 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
13816 } else {
13817 line_prefix.clone()
13818 };
13819
13820 let wrapped_text = {
13821 let mut wrapped_text = wrap_with_prefix(
13822 line_prefix,
13823 subsequent_lines_prefix,
13824 lines_without_prefixes.join("\n"),
13825 wrap_column,
13826 tab_size,
13827 options.preserve_existing_whitespace,
13828 );
13829
13830 if let Some((indent, delimiter)) = first_line_delimiter {
13831 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
13832 }
13833 if let Some(last_line) = last_line_delimiter {
13834 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
13835 }
13836
13837 wrapped_text
13838 };
13839
13840 // TODO: should always use char-based diff while still supporting cursor behavior that
13841 // matches vim.
13842 let mut diff_options = DiffOptions::default();
13843 if options.override_language_settings {
13844 diff_options.max_word_diff_len = 0;
13845 diff_options.max_word_diff_line_count = 0;
13846 } else {
13847 diff_options.max_word_diff_len = usize::MAX;
13848 diff_options.max_word_diff_line_count = usize::MAX;
13849 }
13850
13851 for (old_range, new_text) in
13852 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
13853 {
13854 let edit_start = buffer.anchor_after(start_offset + old_range.start);
13855 let edit_end = buffer.anchor_after(start_offset + old_range.end);
13856 edits.push((edit_start..edit_end, new_text));
13857 }
13858
13859 rewrapped_row_ranges.push(start_row..=end_row);
13860 }
13861
13862 self.buffer
13863 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13864 }
13865
13866 pub fn cut_common(
13867 &mut self,
13868 cut_no_selection_line: bool,
13869 window: &mut Window,
13870 cx: &mut Context<Self>,
13871 ) -> ClipboardItem {
13872 let mut text = String::new();
13873 let buffer = self.buffer.read(cx).snapshot(cx);
13874 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13875 let mut clipboard_selections = Vec::with_capacity(selections.len());
13876 {
13877 let max_point = buffer.max_point();
13878 let mut is_first = true;
13879 let mut prev_selection_was_entire_line = false;
13880 for selection in &mut selections {
13881 let is_entire_line =
13882 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
13883 if is_entire_line {
13884 selection.start = Point::new(selection.start.row, 0);
13885 if !selection.is_empty() && selection.end.column == 0 {
13886 selection.end = cmp::min(max_point, selection.end);
13887 } else {
13888 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
13889 }
13890 selection.goal = SelectionGoal::None;
13891 }
13892 if is_first {
13893 is_first = false;
13894 } else if !prev_selection_was_entire_line {
13895 text += "\n";
13896 }
13897 prev_selection_was_entire_line = is_entire_line;
13898 let mut len = 0;
13899 for chunk in buffer.text_for_range(selection.start..selection.end) {
13900 text.push_str(chunk);
13901 len += chunk.len();
13902 }
13903
13904 clipboard_selections.push(ClipboardSelection::for_buffer(
13905 len,
13906 is_entire_line,
13907 selection.range(),
13908 &buffer,
13909 self.project.as_ref(),
13910 cx,
13911 ));
13912 }
13913 }
13914
13915 self.transact(window, cx, |this, window, cx| {
13916 this.change_selections(Default::default(), window, cx, |s| {
13917 s.select(selections);
13918 });
13919 this.insert("", window, cx);
13920 });
13921 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
13922 }
13923
13924 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
13925 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13926 let item = self.cut_common(true, window, cx);
13927 cx.write_to_clipboard(item);
13928 }
13929
13930 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
13931 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13932 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13933 s.move_with(&mut |snapshot, sel| {
13934 if sel.is_empty() {
13935 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
13936 }
13937 if sel.is_empty() {
13938 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13939 }
13940 });
13941 });
13942 let item = self.cut_common(false, window, cx);
13943 cx.set_global(KillRing(item))
13944 }
13945
13946 pub fn kill_ring_yank(
13947 &mut self,
13948 _: &KillRingYank,
13949 window: &mut Window,
13950 cx: &mut Context<Self>,
13951 ) {
13952 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13953 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
13954 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
13955 (kill_ring.text().to_string(), kill_ring.metadata_json())
13956 } else {
13957 return;
13958 }
13959 } else {
13960 return;
13961 };
13962 self.do_paste(&text, metadata, false, window, cx);
13963 }
13964
13965 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
13966 self.do_copy(true, cx);
13967 }
13968
13969 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
13970 self.do_copy(false, cx);
13971 }
13972
13973 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
13974 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13975 let buffer = self.buffer.read(cx).read(cx);
13976 let mut text = String::new();
13977 let mut clipboard_selections = Vec::with_capacity(selections.len());
13978
13979 let max_point = buffer.max_point();
13980 let mut is_first = true;
13981 for selection in &selections {
13982 let mut start = selection.start;
13983 let mut end = selection.end;
13984 let is_entire_line = selection.is_empty() || self.selections.line_mode();
13985 let mut add_trailing_newline = false;
13986 if is_entire_line {
13987 start = Point::new(start.row, 0);
13988 let next_line_start = Point::new(end.row + 1, 0);
13989 if next_line_start <= max_point {
13990 end = next_line_start;
13991 } else {
13992 // We're on the last line without a trailing newline.
13993 // Copy to the end of the line and add a newline afterwards.
13994 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
13995 add_trailing_newline = true;
13996 }
13997 }
13998
13999 let mut trimmed_selections = Vec::new();
14000 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
14001 let row = MultiBufferRow(start.row);
14002 let first_indent = buffer.indent_size_for_line(row);
14003 if first_indent.len == 0 || start.column > first_indent.len {
14004 trimmed_selections.push(start..end);
14005 } else {
14006 trimmed_selections.push(
14007 Point::new(row.0, first_indent.len)
14008 ..Point::new(row.0, buffer.line_len(row)),
14009 );
14010 for row in start.row + 1..=end.row {
14011 let mut line_len = buffer.line_len(MultiBufferRow(row));
14012 if row == end.row {
14013 line_len = end.column;
14014 }
14015 if line_len == 0 {
14016 trimmed_selections.push(Point::new(row, 0)..Point::new(row, line_len));
14017 continue;
14018 }
14019 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
14020 if row_indent_size.len >= first_indent.len {
14021 trimmed_selections
14022 .push(Point::new(row, first_indent.len)..Point::new(row, line_len));
14023 } else {
14024 trimmed_selections.clear();
14025 trimmed_selections.push(start..end);
14026 break;
14027 }
14028 }
14029 }
14030 } else {
14031 trimmed_selections.push(start..end);
14032 }
14033
14034 let is_multiline_trim = trimmed_selections.len() > 1;
14035 let mut selection_len: usize = 0;
14036 let prev_selection_was_entire_line = is_entire_line && !is_multiline_trim;
14037
14038 for trimmed_range in trimmed_selections {
14039 if is_first {
14040 is_first = false;
14041 } else if is_multiline_trim || !prev_selection_was_entire_line {
14042 text.push('\n');
14043 if is_multiline_trim {
14044 selection_len += 1;
14045 }
14046 }
14047 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
14048 text.push_str(chunk);
14049 selection_len += chunk.len();
14050 }
14051 if add_trailing_newline {
14052 text.push('\n');
14053 selection_len += 1;
14054 }
14055 }
14056
14057 clipboard_selections.push(ClipboardSelection::for_buffer(
14058 selection_len,
14059 is_entire_line,
14060 start..end,
14061 &buffer,
14062 self.project.as_ref(),
14063 cx,
14064 ));
14065 }
14066
14067 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
14068 text,
14069 clipboard_selections,
14070 ));
14071 }
14072
14073 pub fn do_paste(
14074 &mut self,
14075 text: &String,
14076 clipboard_selections: Option<Vec<ClipboardSelection>>,
14077 handle_entire_lines: bool,
14078 window: &mut Window,
14079 cx: &mut Context<Self>,
14080 ) {
14081 if self.read_only(cx) {
14082 return;
14083 }
14084
14085 self.finalize_last_transaction(cx);
14086
14087 let clipboard_text = Cow::Borrowed(text.as_str());
14088
14089 self.transact(window, cx, |this, window, cx| {
14090 let had_active_edit_prediction = this.has_active_edit_prediction();
14091 let display_map = this.display_snapshot(cx);
14092 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
14093 let cursor_offset = this
14094 .selections
14095 .last::<MultiBufferOffset>(&display_map)
14096 .head();
14097
14098 if let Some(mut clipboard_selections) = clipboard_selections {
14099 let all_selections_were_entire_line =
14100 clipboard_selections.iter().all(|s| s.is_entire_line);
14101 let first_selection_indent_column =
14102 clipboard_selections.first().map(|s| s.first_line_indent);
14103 if clipboard_selections.len() != old_selections.len() {
14104 clipboard_selections.drain(..);
14105 }
14106 let mut auto_indent_on_paste = true;
14107
14108 this.buffer.update(cx, |buffer, cx| {
14109 let snapshot = buffer.read(cx);
14110 auto_indent_on_paste = snapshot
14111 .language_settings_at(cursor_offset, cx)
14112 .auto_indent_on_paste;
14113
14114 let mut start_offset = 0;
14115 let mut edits = Vec::new();
14116 let mut original_indent_columns = Vec::new();
14117 for (ix, selection) in old_selections.iter().enumerate() {
14118 let to_insert;
14119 let entire_line;
14120 let original_indent_column;
14121 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
14122 let end_offset = start_offset + clipboard_selection.len;
14123 to_insert = &clipboard_text[start_offset..end_offset];
14124 entire_line = clipboard_selection.is_entire_line;
14125 start_offset = if entire_line {
14126 end_offset
14127 } else {
14128 end_offset + 1
14129 };
14130 original_indent_column = Some(clipboard_selection.first_line_indent);
14131 } else {
14132 to_insert = &*clipboard_text;
14133 entire_line = all_selections_were_entire_line;
14134 original_indent_column = first_selection_indent_column
14135 }
14136
14137 let (range, to_insert) =
14138 if selection.is_empty() && handle_entire_lines && entire_line {
14139 // If the corresponding selection was empty when this slice of the
14140 // clipboard text was written, then the entire line containing the
14141 // selection was copied. If this selection is also currently empty,
14142 // then paste the line before the current line of the buffer.
14143 let column = selection.start.to_point(&snapshot).column as usize;
14144 let line_start = selection.start - column;
14145 (line_start..line_start, Cow::Borrowed(to_insert))
14146 } else {
14147 let language = snapshot.language_at(selection.head());
14148 let range = selection.range();
14149 if let Some(language) = language
14150 && language.name() == "Markdown"
14151 {
14152 edit_for_markdown_paste(
14153 &snapshot,
14154 range,
14155 to_insert,
14156 url::Url::parse(to_insert).ok(),
14157 )
14158 } else {
14159 (range, Cow::Borrowed(to_insert))
14160 }
14161 };
14162
14163 edits.push((range, to_insert));
14164 original_indent_columns.push(original_indent_column);
14165 }
14166 drop(snapshot);
14167
14168 buffer.edit(
14169 edits,
14170 if auto_indent_on_paste {
14171 Some(AutoindentMode::Block {
14172 original_indent_columns,
14173 })
14174 } else {
14175 None
14176 },
14177 cx,
14178 );
14179 });
14180
14181 let selections = this
14182 .selections
14183 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
14184 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14185 } else {
14186 let url = url::Url::parse(&clipboard_text).ok();
14187
14188 let auto_indent_mode = if !clipboard_text.is_empty() {
14189 Some(AutoindentMode::Block {
14190 original_indent_columns: Vec::new(),
14191 })
14192 } else {
14193 None
14194 };
14195
14196 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
14197 let snapshot = buffer.snapshot(cx);
14198
14199 let anchors = old_selections
14200 .iter()
14201 .map(|s| {
14202 let anchor = snapshot.anchor_after(s.head());
14203 s.map(|_| anchor)
14204 })
14205 .collect::<Vec<_>>();
14206
14207 let mut edits = Vec::new();
14208
14209 // When pasting text without metadata (e.g. copied from an
14210 // external editor using multiple cursors) and the number of
14211 // lines matches the number of selections, distribute one
14212 // line per cursor instead of pasting the whole text at each.
14213 let lines: Vec<&str> = clipboard_text.split('\n').collect();
14214 let distribute_lines =
14215 old_selections.len() > 1 && lines.len() == old_selections.len();
14216
14217 for (ix, selection) in old_selections.iter().enumerate() {
14218 let language = snapshot.language_at(selection.head());
14219 let range = selection.range();
14220
14221 let text_for_cursor: &str = if distribute_lines {
14222 lines[ix]
14223 } else {
14224 &clipboard_text
14225 };
14226
14227 let (edit_range, edit_text) = if let Some(language) = language
14228 && language.name() == "Markdown"
14229 {
14230 edit_for_markdown_paste(&snapshot, range, text_for_cursor, url.clone())
14231 } else {
14232 (range, Cow::Borrowed(text_for_cursor))
14233 };
14234
14235 edits.push((edit_range, edit_text));
14236 }
14237
14238 drop(snapshot);
14239 buffer.edit(edits, auto_indent_mode, cx);
14240
14241 anchors
14242 });
14243
14244 this.change_selections(Default::default(), window, cx, |s| {
14245 s.select_anchors(selection_anchors);
14246 });
14247 }
14248
14249 // 🤔 | .. | show_in_menu |
14250 // | .. | true true
14251 // | had_edit_prediction | false true
14252
14253 let trigger_in_words =
14254 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
14255
14256 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
14257 });
14258 }
14259
14260 pub fn diff_clipboard_with_selection(
14261 &mut self,
14262 _: &DiffClipboardWithSelection,
14263 window: &mut Window,
14264 cx: &mut Context<Self>,
14265 ) {
14266 let selections = self
14267 .selections
14268 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
14269
14270 if selections.is_empty() {
14271 log::warn!("There should always be at least one selection in Zed. This is a bug.");
14272 return;
14273 };
14274
14275 let clipboard_text = cx.read_from_clipboard().and_then(|item| {
14276 item.entries().iter().find_map(|entry| match entry {
14277 ClipboardEntry::String(text) => Some(text.text().to_string()),
14278 _ => None,
14279 })
14280 });
14281
14282 let Some(clipboard_text) = clipboard_text else {
14283 log::warn!("Clipboard doesn't contain text.");
14284 return;
14285 };
14286
14287 window.dispatch_action(
14288 Box::new(DiffClipboardWithSelectionData {
14289 clipboard_text,
14290 editor: cx.entity(),
14291 }),
14292 cx,
14293 );
14294 }
14295
14296 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
14297 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14298 if let Some(item) = cx.read_from_clipboard() {
14299 let clipboard_string = item.entries().iter().find_map(|entry| match entry {
14300 ClipboardEntry::String(s) => Some(s),
14301 _ => None,
14302 });
14303 match clipboard_string {
14304 Some(clipboard_string) => self.do_paste(
14305 clipboard_string.text(),
14306 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
14307 true,
14308 window,
14309 cx,
14310 ),
14311 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
14312 }
14313 }
14314 }
14315
14316 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
14317 if self.read_only(cx) {
14318 return;
14319 }
14320
14321 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14322
14323 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
14324 if let Some((selections, _)) =
14325 self.selection_history.transaction(transaction_id).cloned()
14326 {
14327 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14328 s.select_anchors(selections.to_vec());
14329 });
14330 } else {
14331 log::error!(
14332 "No entry in selection_history found for undo. \
14333 This may correspond to a bug where undo does not update the selection. \
14334 If this is occurring, please add details to \
14335 https://github.com/zed-industries/zed/issues/22692"
14336 );
14337 }
14338 self.request_autoscroll(Autoscroll::fit(), cx);
14339 self.unmark_text(window, cx);
14340 self.refresh_edit_prediction(true, false, window, cx);
14341 cx.emit(EditorEvent::Edited { transaction_id });
14342 cx.emit(EditorEvent::TransactionUndone { transaction_id });
14343 }
14344 }
14345
14346 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
14347 if self.read_only(cx) {
14348 return;
14349 }
14350
14351 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14352
14353 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
14354 if let Some((_, Some(selections))) =
14355 self.selection_history.transaction(transaction_id).cloned()
14356 {
14357 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14358 s.select_anchors(selections.to_vec());
14359 });
14360 } else {
14361 log::error!(
14362 "No entry in selection_history found for redo. \
14363 This may correspond to a bug where undo does not update the selection. \
14364 If this is occurring, please add details to \
14365 https://github.com/zed-industries/zed/issues/22692"
14366 );
14367 }
14368 self.request_autoscroll(Autoscroll::fit(), cx);
14369 self.unmark_text(window, cx);
14370 self.refresh_edit_prediction(true, false, window, cx);
14371 cx.emit(EditorEvent::Edited { transaction_id });
14372 }
14373 }
14374
14375 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
14376 self.buffer
14377 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
14378 }
14379
14380 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
14381 self.buffer
14382 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
14383 }
14384
14385 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
14386 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14387 self.change_selections(Default::default(), window, cx, |s| {
14388 s.move_with(&mut |map, selection| {
14389 let cursor = if selection.is_empty() {
14390 movement::left(map, selection.start)
14391 } else {
14392 selection.start
14393 };
14394 selection.collapse_to(cursor, SelectionGoal::None);
14395 });
14396 })
14397 }
14398
14399 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
14400 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14401 self.change_selections(Default::default(), window, cx, |s| {
14402 s.move_heads_with(&mut |map, head, _| (movement::left(map, head), SelectionGoal::None));
14403 })
14404 }
14405
14406 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
14407 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14408 self.change_selections(Default::default(), window, cx, |s| {
14409 s.move_with(&mut |map, selection| {
14410 let cursor = if selection.is_empty() {
14411 movement::right(map, selection.end)
14412 } else {
14413 selection.end
14414 };
14415 selection.collapse_to(cursor, SelectionGoal::None)
14416 });
14417 })
14418 }
14419
14420 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
14421 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14422 self.change_selections(Default::default(), window, cx, |s| {
14423 s.move_heads_with(&mut |map, head, _| {
14424 (movement::right(map, head), SelectionGoal::None)
14425 });
14426 });
14427 }
14428
14429 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
14430 if self.take_rename(true, window, cx).is_some() {
14431 return;
14432 }
14433
14434 if self.mode.is_single_line() {
14435 cx.propagate();
14436 return;
14437 }
14438
14439 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14440
14441 let text_layout_details = &self.text_layout_details(window, cx);
14442 let selection_count = self.selections.count();
14443 let first_selection = self.selections.first_anchor();
14444
14445 self.change_selections(Default::default(), window, cx, |s| {
14446 s.move_with(&mut |map, selection| {
14447 if !selection.is_empty() {
14448 selection.goal = SelectionGoal::None;
14449 }
14450 let (cursor, goal) = movement::up(
14451 map,
14452 selection.start,
14453 selection.goal,
14454 false,
14455 text_layout_details,
14456 );
14457 selection.collapse_to(cursor, goal);
14458 });
14459 });
14460
14461 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14462 {
14463 cx.propagate();
14464 }
14465 }
14466
14467 pub fn move_up_by_lines(
14468 &mut self,
14469 action: &MoveUpByLines,
14470 window: &mut Window,
14471 cx: &mut Context<Self>,
14472 ) {
14473 if self.take_rename(true, window, cx).is_some() {
14474 return;
14475 }
14476
14477 if self.mode.is_single_line() {
14478 cx.propagate();
14479 return;
14480 }
14481
14482 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14483
14484 let text_layout_details = &self.text_layout_details(window, cx);
14485
14486 self.change_selections(Default::default(), window, cx, |s| {
14487 s.move_with(&mut |map, selection| {
14488 if !selection.is_empty() {
14489 selection.goal = SelectionGoal::None;
14490 }
14491 let (cursor, goal) = movement::up_by_rows(
14492 map,
14493 selection.start,
14494 action.lines,
14495 selection.goal,
14496 false,
14497 text_layout_details,
14498 );
14499 selection.collapse_to(cursor, goal);
14500 });
14501 })
14502 }
14503
14504 pub fn move_down_by_lines(
14505 &mut self,
14506 action: &MoveDownByLines,
14507 window: &mut Window,
14508 cx: &mut Context<Self>,
14509 ) {
14510 if self.take_rename(true, window, cx).is_some() {
14511 return;
14512 }
14513
14514 if self.mode.is_single_line() {
14515 cx.propagate();
14516 return;
14517 }
14518
14519 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14520
14521 let text_layout_details = &self.text_layout_details(window, cx);
14522
14523 self.change_selections(Default::default(), window, cx, |s| {
14524 s.move_with(&mut |map, selection| {
14525 if !selection.is_empty() {
14526 selection.goal = SelectionGoal::None;
14527 }
14528 let (cursor, goal) = movement::down_by_rows(
14529 map,
14530 selection.start,
14531 action.lines,
14532 selection.goal,
14533 false,
14534 text_layout_details,
14535 );
14536 selection.collapse_to(cursor, goal);
14537 });
14538 })
14539 }
14540
14541 pub fn select_down_by_lines(
14542 &mut self,
14543 action: &SelectDownByLines,
14544 window: &mut Window,
14545 cx: &mut Context<Self>,
14546 ) {
14547 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14548 let text_layout_details = &self.text_layout_details(window, cx);
14549 self.change_selections(Default::default(), window, cx, |s| {
14550 s.move_heads_with(&mut |map, head, goal| {
14551 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
14552 })
14553 })
14554 }
14555
14556 pub fn select_up_by_lines(
14557 &mut self,
14558 action: &SelectUpByLines,
14559 window: &mut Window,
14560 cx: &mut Context<Self>,
14561 ) {
14562 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14563 let text_layout_details = &self.text_layout_details(window, cx);
14564 self.change_selections(Default::default(), window, cx, |s| {
14565 s.move_heads_with(&mut |map, head, goal| {
14566 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
14567 })
14568 })
14569 }
14570
14571 pub fn select_page_up(
14572 &mut self,
14573 _: &SelectPageUp,
14574 window: &mut Window,
14575 cx: &mut Context<Self>,
14576 ) {
14577 let Some(row_count) = self.visible_row_count() else {
14578 return;
14579 };
14580
14581 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14582
14583 let text_layout_details = &self.text_layout_details(window, cx);
14584
14585 self.change_selections(Default::default(), window, cx, |s| {
14586 s.move_heads_with(&mut |map, head, goal| {
14587 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
14588 })
14589 })
14590 }
14591
14592 pub fn move_page_up(
14593 &mut self,
14594 action: &MovePageUp,
14595 window: &mut Window,
14596 cx: &mut Context<Self>,
14597 ) {
14598 if self.take_rename(true, window, cx).is_some() {
14599 return;
14600 }
14601
14602 if self
14603 .context_menu
14604 .borrow_mut()
14605 .as_mut()
14606 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
14607 .unwrap_or(false)
14608 {
14609 return;
14610 }
14611
14612 if matches!(self.mode, EditorMode::SingleLine) {
14613 cx.propagate();
14614 return;
14615 }
14616
14617 let Some(row_count) = self.visible_row_count() else {
14618 return;
14619 };
14620
14621 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14622
14623 let effects = if action.center_cursor {
14624 SelectionEffects::scroll(Autoscroll::center())
14625 } else {
14626 SelectionEffects::default()
14627 };
14628
14629 let text_layout_details = &self.text_layout_details(window, cx);
14630
14631 self.change_selections(effects, window, cx, |s| {
14632 s.move_with(&mut |map, selection| {
14633 if !selection.is_empty() {
14634 selection.goal = SelectionGoal::None;
14635 }
14636 let (cursor, goal) = movement::up_by_rows(
14637 map,
14638 selection.end,
14639 row_count,
14640 selection.goal,
14641 false,
14642 text_layout_details,
14643 );
14644 selection.collapse_to(cursor, goal);
14645 });
14646 });
14647 }
14648
14649 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
14650 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14651 let text_layout_details = &self.text_layout_details(window, cx);
14652 self.change_selections(Default::default(), window, cx, |s| {
14653 s.move_heads_with(&mut |map, head, goal| {
14654 movement::up(map, head, goal, false, text_layout_details)
14655 })
14656 })
14657 }
14658
14659 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
14660 self.take_rename(true, window, cx);
14661
14662 if self.mode.is_single_line() {
14663 cx.propagate();
14664 return;
14665 }
14666
14667 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14668
14669 let text_layout_details = &self.text_layout_details(window, cx);
14670 let selection_count = self.selections.count();
14671 let first_selection = self.selections.first_anchor();
14672
14673 self.change_selections(Default::default(), window, cx, |s| {
14674 s.move_with(&mut |map, selection| {
14675 if !selection.is_empty() {
14676 selection.goal = SelectionGoal::None;
14677 }
14678 let (cursor, goal) = movement::down(
14679 map,
14680 selection.end,
14681 selection.goal,
14682 false,
14683 text_layout_details,
14684 );
14685 selection.collapse_to(cursor, goal);
14686 });
14687 });
14688
14689 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14690 {
14691 cx.propagate();
14692 }
14693 }
14694
14695 pub fn select_page_down(
14696 &mut self,
14697 _: &SelectPageDown,
14698 window: &mut Window,
14699 cx: &mut Context<Self>,
14700 ) {
14701 let Some(row_count) = self.visible_row_count() else {
14702 return;
14703 };
14704
14705 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14706
14707 let text_layout_details = &self.text_layout_details(window, cx);
14708
14709 self.change_selections(Default::default(), window, cx, |s| {
14710 s.move_heads_with(&mut |map, head, goal| {
14711 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
14712 })
14713 })
14714 }
14715
14716 pub fn move_page_down(
14717 &mut self,
14718 action: &MovePageDown,
14719 window: &mut Window,
14720 cx: &mut Context<Self>,
14721 ) {
14722 if self.take_rename(true, window, cx).is_some() {
14723 return;
14724 }
14725
14726 if self
14727 .context_menu
14728 .borrow_mut()
14729 .as_mut()
14730 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
14731 .unwrap_or(false)
14732 {
14733 return;
14734 }
14735
14736 if matches!(self.mode, EditorMode::SingleLine) {
14737 cx.propagate();
14738 return;
14739 }
14740
14741 let Some(row_count) = self.visible_row_count() else {
14742 return;
14743 };
14744
14745 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14746
14747 let effects = if action.center_cursor {
14748 SelectionEffects::scroll(Autoscroll::center())
14749 } else {
14750 SelectionEffects::default()
14751 };
14752
14753 let text_layout_details = &self.text_layout_details(window, cx);
14754 self.change_selections(effects, window, cx, |s| {
14755 s.move_with(&mut |map, selection| {
14756 if !selection.is_empty() {
14757 selection.goal = SelectionGoal::None;
14758 }
14759 let (cursor, goal) = movement::down_by_rows(
14760 map,
14761 selection.end,
14762 row_count,
14763 selection.goal,
14764 false,
14765 text_layout_details,
14766 );
14767 selection.collapse_to(cursor, goal);
14768 });
14769 });
14770 }
14771
14772 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
14773 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14774 let text_layout_details = &self.text_layout_details(window, cx);
14775 self.change_selections(Default::default(), window, cx, |s| {
14776 s.move_heads_with(&mut |map, head, goal| {
14777 movement::down(map, head, goal, false, text_layout_details)
14778 })
14779 });
14780 }
14781
14782 pub fn context_menu_first(
14783 &mut self,
14784 _: &ContextMenuFirst,
14785 window: &mut Window,
14786 cx: &mut Context<Self>,
14787 ) {
14788 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14789 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
14790 }
14791 }
14792
14793 pub fn context_menu_prev(
14794 &mut self,
14795 _: &ContextMenuPrevious,
14796 window: &mut Window,
14797 cx: &mut Context<Self>,
14798 ) {
14799 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14800 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
14801 }
14802 }
14803
14804 pub fn context_menu_next(
14805 &mut self,
14806 _: &ContextMenuNext,
14807 window: &mut Window,
14808 cx: &mut Context<Self>,
14809 ) {
14810 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14811 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
14812 }
14813 }
14814
14815 pub fn context_menu_last(
14816 &mut self,
14817 _: &ContextMenuLast,
14818 window: &mut Window,
14819 cx: &mut Context<Self>,
14820 ) {
14821 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14822 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
14823 }
14824 }
14825
14826 pub fn signature_help_prev(
14827 &mut self,
14828 _: &SignatureHelpPrevious,
14829 _: &mut Window,
14830 cx: &mut Context<Self>,
14831 ) {
14832 if let Some(popover) = self.signature_help_state.popover_mut() {
14833 if popover.current_signature == 0 {
14834 popover.current_signature = popover.signatures.len() - 1;
14835 } else {
14836 popover.current_signature -= 1;
14837 }
14838 cx.notify();
14839 }
14840 }
14841
14842 pub fn signature_help_next(
14843 &mut self,
14844 _: &SignatureHelpNext,
14845 _: &mut Window,
14846 cx: &mut Context<Self>,
14847 ) {
14848 if let Some(popover) = self.signature_help_state.popover_mut() {
14849 if popover.current_signature + 1 == popover.signatures.len() {
14850 popover.current_signature = 0;
14851 } else {
14852 popover.current_signature += 1;
14853 }
14854 cx.notify();
14855 }
14856 }
14857
14858 pub fn move_to_previous_word_start(
14859 &mut self,
14860 _: &MoveToPreviousWordStart,
14861 window: &mut Window,
14862 cx: &mut Context<Self>,
14863 ) {
14864 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14865 self.change_selections(Default::default(), window, cx, |s| {
14866 s.move_cursors_with(&mut |map, head, _| {
14867 (
14868 movement::previous_word_start(map, head),
14869 SelectionGoal::None,
14870 )
14871 });
14872 })
14873 }
14874
14875 pub fn move_to_previous_subword_start(
14876 &mut self,
14877 _: &MoveToPreviousSubwordStart,
14878 window: &mut Window,
14879 cx: &mut Context<Self>,
14880 ) {
14881 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14882 self.change_selections(Default::default(), window, cx, |s| {
14883 s.move_cursors_with(&mut |map, head, _| {
14884 (
14885 movement::previous_subword_start(map, head),
14886 SelectionGoal::None,
14887 )
14888 });
14889 })
14890 }
14891
14892 pub fn select_to_previous_word_start(
14893 &mut self,
14894 _: &SelectToPreviousWordStart,
14895 window: &mut Window,
14896 cx: &mut Context<Self>,
14897 ) {
14898 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14899 self.change_selections(Default::default(), window, cx, |s| {
14900 s.move_heads_with(&mut |map, head, _| {
14901 (
14902 movement::previous_word_start(map, head),
14903 SelectionGoal::None,
14904 )
14905 });
14906 })
14907 }
14908
14909 pub fn select_to_previous_subword_start(
14910 &mut self,
14911 _: &SelectToPreviousSubwordStart,
14912 window: &mut Window,
14913 cx: &mut Context<Self>,
14914 ) {
14915 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14916 self.change_selections(Default::default(), window, cx, |s| {
14917 s.move_heads_with(&mut |map, head, _| {
14918 (
14919 movement::previous_subword_start(map, head),
14920 SelectionGoal::None,
14921 )
14922 });
14923 })
14924 }
14925
14926 pub fn delete_to_previous_word_start(
14927 &mut self,
14928 action: &DeleteToPreviousWordStart,
14929 window: &mut Window,
14930 cx: &mut Context<Self>,
14931 ) {
14932 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14933 self.transact(window, cx, |this, window, cx| {
14934 this.select_autoclose_pair(window, cx);
14935 this.change_selections(Default::default(), window, cx, |s| {
14936 s.move_with(&mut |map, selection| {
14937 if selection.is_empty() {
14938 let mut cursor = if action.ignore_newlines {
14939 movement::previous_word_start(map, selection.head())
14940 } else {
14941 movement::previous_word_start_or_newline(map, selection.head())
14942 };
14943 cursor = movement::adjust_greedy_deletion(
14944 map,
14945 selection.head(),
14946 cursor,
14947 action.ignore_brackets,
14948 );
14949 selection.set_head(cursor, SelectionGoal::None);
14950 }
14951 });
14952 });
14953 this.insert("", window, cx);
14954 });
14955 }
14956
14957 pub fn delete_to_previous_subword_start(
14958 &mut self,
14959 action: &DeleteToPreviousSubwordStart,
14960 window: &mut Window,
14961 cx: &mut Context<Self>,
14962 ) {
14963 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14964 self.transact(window, cx, |this, window, cx| {
14965 this.select_autoclose_pair(window, cx);
14966 this.change_selections(Default::default(), window, cx, |s| {
14967 s.move_with(&mut |map, selection| {
14968 if selection.is_empty() {
14969 let mut cursor = if action.ignore_newlines {
14970 movement::previous_subword_start(map, selection.head())
14971 } else {
14972 movement::previous_subword_start_or_newline(map, selection.head())
14973 };
14974 cursor = movement::adjust_greedy_deletion(
14975 map,
14976 selection.head(),
14977 cursor,
14978 action.ignore_brackets,
14979 );
14980 selection.set_head(cursor, SelectionGoal::None);
14981 }
14982 });
14983 });
14984 this.insert("", window, cx);
14985 });
14986 }
14987
14988 pub fn move_to_next_word_end(
14989 &mut self,
14990 _: &MoveToNextWordEnd,
14991 window: &mut Window,
14992 cx: &mut Context<Self>,
14993 ) {
14994 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14995 self.change_selections(Default::default(), window, cx, |s| {
14996 s.move_cursors_with(&mut |map, head, _| {
14997 (movement::next_word_end(map, head), SelectionGoal::None)
14998 });
14999 })
15000 }
15001
15002 pub fn move_to_next_subword_end(
15003 &mut self,
15004 _: &MoveToNextSubwordEnd,
15005 window: &mut Window,
15006 cx: &mut Context<Self>,
15007 ) {
15008 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15009 self.change_selections(Default::default(), window, cx, |s| {
15010 s.move_cursors_with(&mut |map, head, _| {
15011 (movement::next_subword_end(map, head), SelectionGoal::None)
15012 });
15013 })
15014 }
15015
15016 pub fn select_to_next_word_end(
15017 &mut self,
15018 _: &SelectToNextWordEnd,
15019 window: &mut Window,
15020 cx: &mut Context<Self>,
15021 ) {
15022 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15023 self.change_selections(Default::default(), window, cx, |s| {
15024 s.move_heads_with(&mut |map, head, _| {
15025 (movement::next_word_end(map, head), SelectionGoal::None)
15026 });
15027 })
15028 }
15029
15030 pub fn select_to_next_subword_end(
15031 &mut self,
15032 _: &SelectToNextSubwordEnd,
15033 window: &mut Window,
15034 cx: &mut Context<Self>,
15035 ) {
15036 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15037 self.change_selections(Default::default(), window, cx, |s| {
15038 s.move_heads_with(&mut |map, head, _| {
15039 (movement::next_subword_end(map, head), SelectionGoal::None)
15040 });
15041 })
15042 }
15043
15044 pub fn delete_to_next_word_end(
15045 &mut self,
15046 action: &DeleteToNextWordEnd,
15047 window: &mut Window,
15048 cx: &mut Context<Self>,
15049 ) {
15050 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15051 self.transact(window, cx, |this, window, cx| {
15052 this.change_selections(Default::default(), window, cx, |s| {
15053 s.move_with(&mut |map, selection| {
15054 if selection.is_empty() {
15055 let mut cursor = if action.ignore_newlines {
15056 movement::next_word_end(map, selection.head())
15057 } else {
15058 movement::next_word_end_or_newline(map, selection.head())
15059 };
15060 cursor = movement::adjust_greedy_deletion(
15061 map,
15062 selection.head(),
15063 cursor,
15064 action.ignore_brackets,
15065 );
15066 selection.set_head(cursor, SelectionGoal::None);
15067 }
15068 });
15069 });
15070 this.insert("", window, cx);
15071 });
15072 }
15073
15074 pub fn delete_to_next_subword_end(
15075 &mut self,
15076 action: &DeleteToNextSubwordEnd,
15077 window: &mut Window,
15078 cx: &mut Context<Self>,
15079 ) {
15080 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15081 self.transact(window, cx, |this, window, cx| {
15082 this.change_selections(Default::default(), window, cx, |s| {
15083 s.move_with(&mut |map, selection| {
15084 if selection.is_empty() {
15085 let mut cursor = if action.ignore_newlines {
15086 movement::next_subword_end(map, selection.head())
15087 } else {
15088 movement::next_subword_end_or_newline(map, selection.head())
15089 };
15090 cursor = movement::adjust_greedy_deletion(
15091 map,
15092 selection.head(),
15093 cursor,
15094 action.ignore_brackets,
15095 );
15096 selection.set_head(cursor, SelectionGoal::None);
15097 }
15098 });
15099 });
15100 this.insert("", window, cx);
15101 });
15102 }
15103
15104 pub fn move_to_beginning_of_line(
15105 &mut self,
15106 action: &MoveToBeginningOfLine,
15107 window: &mut Window,
15108 cx: &mut Context<Self>,
15109 ) {
15110 let stop_at_indent = action.stop_at_indent && !self.mode.is_single_line();
15111 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15112 self.change_selections(Default::default(), window, cx, |s| {
15113 s.move_cursors_with(&mut |map, head, _| {
15114 (
15115 movement::indented_line_beginning(
15116 map,
15117 head,
15118 action.stop_at_soft_wraps,
15119 stop_at_indent,
15120 ),
15121 SelectionGoal::None,
15122 )
15123 });
15124 })
15125 }
15126
15127 pub fn select_to_beginning_of_line(
15128 &mut self,
15129 action: &SelectToBeginningOfLine,
15130 window: &mut Window,
15131 cx: &mut Context<Self>,
15132 ) {
15133 let stop_at_indent = action.stop_at_indent && !self.mode.is_single_line();
15134 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15135 self.change_selections(Default::default(), window, cx, |s| {
15136 s.move_heads_with(&mut |map, head, _| {
15137 (
15138 movement::indented_line_beginning(
15139 map,
15140 head,
15141 action.stop_at_soft_wraps,
15142 stop_at_indent,
15143 ),
15144 SelectionGoal::None,
15145 )
15146 });
15147 });
15148 }
15149
15150 pub fn delete_to_beginning_of_line(
15151 &mut self,
15152 action: &DeleteToBeginningOfLine,
15153 window: &mut Window,
15154 cx: &mut Context<Self>,
15155 ) {
15156 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15157 self.transact(window, cx, |this, window, cx| {
15158 this.change_selections(Default::default(), window, cx, |s| {
15159 s.move_with(&mut |_, selection| {
15160 selection.reversed = true;
15161 });
15162 });
15163
15164 this.select_to_beginning_of_line(
15165 &SelectToBeginningOfLine {
15166 stop_at_soft_wraps: false,
15167 stop_at_indent: action.stop_at_indent,
15168 },
15169 window,
15170 cx,
15171 );
15172 this.backspace(&Backspace, window, cx);
15173 });
15174 }
15175
15176 pub fn move_to_end_of_line(
15177 &mut self,
15178 action: &MoveToEndOfLine,
15179 window: &mut Window,
15180 cx: &mut Context<Self>,
15181 ) {
15182 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15183 self.change_selections(Default::default(), window, cx, |s| {
15184 s.move_cursors_with(&mut |map, head, _| {
15185 (
15186 movement::line_end(map, head, action.stop_at_soft_wraps),
15187 SelectionGoal::None,
15188 )
15189 });
15190 })
15191 }
15192
15193 pub fn select_to_end_of_line(
15194 &mut self,
15195 action: &SelectToEndOfLine,
15196 window: &mut Window,
15197 cx: &mut Context<Self>,
15198 ) {
15199 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15200 self.change_selections(Default::default(), window, cx, |s| {
15201 s.move_heads_with(&mut |map, head, _| {
15202 (
15203 movement::line_end(map, head, action.stop_at_soft_wraps),
15204 SelectionGoal::None,
15205 )
15206 });
15207 })
15208 }
15209
15210 pub fn delete_to_end_of_line(
15211 &mut self,
15212 _: &DeleteToEndOfLine,
15213 window: &mut Window,
15214 cx: &mut Context<Self>,
15215 ) {
15216 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15217 self.transact(window, cx, |this, window, cx| {
15218 this.select_to_end_of_line(
15219 &SelectToEndOfLine {
15220 stop_at_soft_wraps: false,
15221 },
15222 window,
15223 cx,
15224 );
15225 this.delete(&Delete, window, cx);
15226 });
15227 }
15228
15229 pub fn cut_to_end_of_line(
15230 &mut self,
15231 action: &CutToEndOfLine,
15232 window: &mut Window,
15233 cx: &mut Context<Self>,
15234 ) {
15235 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15236 self.transact(window, cx, |this, window, cx| {
15237 this.select_to_end_of_line(
15238 &SelectToEndOfLine {
15239 stop_at_soft_wraps: false,
15240 },
15241 window,
15242 cx,
15243 );
15244 if !action.stop_at_newlines {
15245 this.change_selections(Default::default(), window, cx, |s| {
15246 s.move_with(&mut |_, sel| {
15247 if sel.is_empty() {
15248 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
15249 }
15250 });
15251 });
15252 }
15253 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15254 let item = this.cut_common(false, window, cx);
15255 cx.write_to_clipboard(item);
15256 });
15257 }
15258
15259 pub fn move_to_start_of_paragraph(
15260 &mut self,
15261 _: &MoveToStartOfParagraph,
15262 window: &mut Window,
15263 cx: &mut Context<Self>,
15264 ) {
15265 if matches!(self.mode, EditorMode::SingleLine) {
15266 cx.propagate();
15267 return;
15268 }
15269 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15270 self.change_selections(Default::default(), window, cx, |s| {
15271 s.move_with(&mut |map, selection| {
15272 selection.collapse_to(
15273 movement::start_of_paragraph(map, selection.head(), 1),
15274 SelectionGoal::None,
15275 )
15276 });
15277 })
15278 }
15279
15280 pub fn move_to_end_of_paragraph(
15281 &mut self,
15282 _: &MoveToEndOfParagraph,
15283 window: &mut Window,
15284 cx: &mut Context<Self>,
15285 ) {
15286 if matches!(self.mode, EditorMode::SingleLine) {
15287 cx.propagate();
15288 return;
15289 }
15290 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15291 self.change_selections(Default::default(), window, cx, |s| {
15292 s.move_with(&mut |map, selection| {
15293 selection.collapse_to(
15294 movement::end_of_paragraph(map, selection.head(), 1),
15295 SelectionGoal::None,
15296 )
15297 });
15298 })
15299 }
15300
15301 pub fn select_to_start_of_paragraph(
15302 &mut self,
15303 _: &SelectToStartOfParagraph,
15304 window: &mut Window,
15305 cx: &mut Context<Self>,
15306 ) {
15307 if matches!(self.mode, EditorMode::SingleLine) {
15308 cx.propagate();
15309 return;
15310 }
15311 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15312 self.change_selections(Default::default(), window, cx, |s| {
15313 s.move_heads_with(&mut |map, head, _| {
15314 (
15315 movement::start_of_paragraph(map, head, 1),
15316 SelectionGoal::None,
15317 )
15318 });
15319 })
15320 }
15321
15322 pub fn select_to_end_of_paragraph(
15323 &mut self,
15324 _: &SelectToEndOfParagraph,
15325 window: &mut Window,
15326 cx: &mut Context<Self>,
15327 ) {
15328 if matches!(self.mode, EditorMode::SingleLine) {
15329 cx.propagate();
15330 return;
15331 }
15332 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15333 self.change_selections(Default::default(), window, cx, |s| {
15334 s.move_heads_with(&mut |map, head, _| {
15335 (
15336 movement::end_of_paragraph(map, head, 1),
15337 SelectionGoal::None,
15338 )
15339 });
15340 })
15341 }
15342
15343 pub fn move_to_start_of_excerpt(
15344 &mut self,
15345 _: &MoveToStartOfExcerpt,
15346 window: &mut Window,
15347 cx: &mut Context<Self>,
15348 ) {
15349 if matches!(self.mode, EditorMode::SingleLine) {
15350 cx.propagate();
15351 return;
15352 }
15353 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15354 self.change_selections(Default::default(), window, cx, |s| {
15355 s.move_with(&mut |map, selection| {
15356 selection.collapse_to(
15357 movement::start_of_excerpt(
15358 map,
15359 selection.head(),
15360 workspace::searchable::Direction::Prev,
15361 ),
15362 SelectionGoal::None,
15363 )
15364 });
15365 })
15366 }
15367
15368 pub fn move_to_start_of_next_excerpt(
15369 &mut self,
15370 _: &MoveToStartOfNextExcerpt,
15371 window: &mut Window,
15372 cx: &mut Context<Self>,
15373 ) {
15374 if matches!(self.mode, EditorMode::SingleLine) {
15375 cx.propagate();
15376 return;
15377 }
15378
15379 self.change_selections(Default::default(), window, cx, |s| {
15380 s.move_with(&mut |map, selection| {
15381 selection.collapse_to(
15382 movement::start_of_excerpt(
15383 map,
15384 selection.head(),
15385 workspace::searchable::Direction::Next,
15386 ),
15387 SelectionGoal::None,
15388 )
15389 });
15390 })
15391 }
15392
15393 pub fn move_to_end_of_excerpt(
15394 &mut self,
15395 _: &MoveToEndOfExcerpt,
15396 window: &mut Window,
15397 cx: &mut Context<Self>,
15398 ) {
15399 if matches!(self.mode, EditorMode::SingleLine) {
15400 cx.propagate();
15401 return;
15402 }
15403 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15404 self.change_selections(Default::default(), window, cx, |s| {
15405 s.move_with(&mut |map, selection| {
15406 selection.collapse_to(
15407 movement::end_of_excerpt(
15408 map,
15409 selection.head(),
15410 workspace::searchable::Direction::Next,
15411 ),
15412 SelectionGoal::None,
15413 )
15414 });
15415 })
15416 }
15417
15418 pub fn move_to_end_of_previous_excerpt(
15419 &mut self,
15420 _: &MoveToEndOfPreviousExcerpt,
15421 window: &mut Window,
15422 cx: &mut Context<Self>,
15423 ) {
15424 if matches!(self.mode, EditorMode::SingleLine) {
15425 cx.propagate();
15426 return;
15427 }
15428 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15429 self.change_selections(Default::default(), window, cx, |s| {
15430 s.move_with(&mut |map, selection| {
15431 selection.collapse_to(
15432 movement::end_of_excerpt(
15433 map,
15434 selection.head(),
15435 workspace::searchable::Direction::Prev,
15436 ),
15437 SelectionGoal::None,
15438 )
15439 });
15440 })
15441 }
15442
15443 pub fn select_to_start_of_excerpt(
15444 &mut self,
15445 _: &SelectToStartOfExcerpt,
15446 window: &mut Window,
15447 cx: &mut Context<Self>,
15448 ) {
15449 if matches!(self.mode, EditorMode::SingleLine) {
15450 cx.propagate();
15451 return;
15452 }
15453 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15454 self.change_selections(Default::default(), window, cx, |s| {
15455 s.move_heads_with(&mut |map, head, _| {
15456 (
15457 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15458 SelectionGoal::None,
15459 )
15460 });
15461 })
15462 }
15463
15464 pub fn select_to_start_of_next_excerpt(
15465 &mut self,
15466 _: &SelectToStartOfNextExcerpt,
15467 window: &mut Window,
15468 cx: &mut Context<Self>,
15469 ) {
15470 if matches!(self.mode, EditorMode::SingleLine) {
15471 cx.propagate();
15472 return;
15473 }
15474 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15475 self.change_selections(Default::default(), window, cx, |s| {
15476 s.move_heads_with(&mut |map, head, _| {
15477 (
15478 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
15479 SelectionGoal::None,
15480 )
15481 });
15482 })
15483 }
15484
15485 pub fn select_to_end_of_excerpt(
15486 &mut self,
15487 _: &SelectToEndOfExcerpt,
15488 window: &mut Window,
15489 cx: &mut Context<Self>,
15490 ) {
15491 if matches!(self.mode, EditorMode::SingleLine) {
15492 cx.propagate();
15493 return;
15494 }
15495 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15496 self.change_selections(Default::default(), window, cx, |s| {
15497 s.move_heads_with(&mut |map, head, _| {
15498 (
15499 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
15500 SelectionGoal::None,
15501 )
15502 });
15503 })
15504 }
15505
15506 pub fn select_to_end_of_previous_excerpt(
15507 &mut self,
15508 _: &SelectToEndOfPreviousExcerpt,
15509 window: &mut Window,
15510 cx: &mut Context<Self>,
15511 ) {
15512 if matches!(self.mode, EditorMode::SingleLine) {
15513 cx.propagate();
15514 return;
15515 }
15516 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15517 self.change_selections(Default::default(), window, cx, |s| {
15518 s.move_heads_with(&mut |map, head, _| {
15519 (
15520 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15521 SelectionGoal::None,
15522 )
15523 });
15524 })
15525 }
15526
15527 pub fn move_to_beginning(
15528 &mut self,
15529 _: &MoveToBeginning,
15530 window: &mut Window,
15531 cx: &mut Context<Self>,
15532 ) {
15533 if matches!(self.mode, EditorMode::SingleLine) {
15534 cx.propagate();
15535 return;
15536 }
15537 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15538 self.change_selections(Default::default(), window, cx, |s| {
15539 s.select_ranges(vec![Anchor::Min..Anchor::Min]);
15540 });
15541 }
15542
15543 pub fn select_to_beginning(
15544 &mut self,
15545 _: &SelectToBeginning,
15546 window: &mut Window,
15547 cx: &mut Context<Self>,
15548 ) {
15549 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
15550 selection.set_head(Point::zero(), SelectionGoal::None);
15551 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15552 self.change_selections(Default::default(), window, cx, |s| {
15553 s.select(vec![selection]);
15554 });
15555 }
15556
15557 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
15558 if matches!(self.mode, EditorMode::SingleLine) {
15559 cx.propagate();
15560 return;
15561 }
15562 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15563 let cursor = self.buffer.read(cx).read(cx).len();
15564 self.change_selections(Default::default(), window, cx, |s| {
15565 s.select_ranges(vec![cursor..cursor])
15566 });
15567 }
15568
15569 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
15570 self.nav_history = nav_history;
15571 }
15572
15573 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
15574 self.nav_history.as_ref()
15575 }
15576
15577 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
15578 self.push_to_nav_history(
15579 self.selections.newest_anchor().head(),
15580 None,
15581 false,
15582 true,
15583 cx,
15584 );
15585 }
15586
15587 fn navigation_data(&self, cursor_anchor: Anchor, cx: &mut Context<Self>) -> NavigationData {
15588 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15589 let buffer = self.buffer.read(cx).read(cx);
15590 let cursor_position = cursor_anchor.to_point(&buffer);
15591 let scroll_anchor = self.scroll_manager.native_anchor(&display_snapshot, cx);
15592 let scroll_top_row = scroll_anchor.top_row(&buffer);
15593 drop(buffer);
15594
15595 NavigationData {
15596 cursor_anchor,
15597 cursor_position,
15598 scroll_anchor,
15599 scroll_top_row,
15600 }
15601 }
15602
15603 fn navigation_entry(
15604 &self,
15605 cursor_anchor: Anchor,
15606 cx: &mut Context<Self>,
15607 ) -> Option<NavigationEntry> {
15608 let Some(history) = self.nav_history.clone() else {
15609 return None;
15610 };
15611 let data = self.navigation_data(cursor_anchor, cx);
15612 Some(history.navigation_entry(Some(Arc::new(data) as Arc<dyn Any + Send + Sync>)))
15613 }
15614
15615 fn push_to_nav_history(
15616 &mut self,
15617 cursor_anchor: Anchor,
15618 new_position: Option<Point>,
15619 is_deactivate: bool,
15620 always: bool,
15621 cx: &mut Context<Self>,
15622 ) {
15623 let data = self.navigation_data(cursor_anchor, cx);
15624 if let Some(nav_history) = self.nav_history.as_mut() {
15625 if let Some(new_position) = new_position {
15626 let row_delta = (new_position.row as i64 - data.cursor_position.row as i64).abs();
15627 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
15628 return;
15629 }
15630 }
15631
15632 let cursor_row = data.cursor_position.row;
15633 nav_history.push(Some(data), Some(cursor_row), cx);
15634 cx.emit(EditorEvent::PushedToNavHistory {
15635 anchor: cursor_anchor,
15636 is_deactivate,
15637 })
15638 }
15639 }
15640
15641 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
15642 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15643 let buffer = self.buffer.read(cx).snapshot(cx);
15644 let mut selection = self
15645 .selections
15646 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
15647 selection.set_head(buffer.len(), SelectionGoal::None);
15648 self.change_selections(Default::default(), window, cx, |s| {
15649 s.select(vec![selection]);
15650 });
15651 }
15652
15653 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
15654 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15655 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15656 s.select_ranges(vec![Anchor::Min..Anchor::Max]);
15657 });
15658 }
15659
15660 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
15661 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15662 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15663 let mut selections = self.selections.all::<Point>(&display_map);
15664 let max_point = display_map.buffer_snapshot().max_point();
15665 for selection in &mut selections {
15666 let rows = selection.spanned_rows(true, &display_map);
15667 selection.start = Point::new(rows.start.0, 0);
15668 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
15669 selection.reversed = false;
15670 }
15671 self.change_selections(Default::default(), window, cx, |s| {
15672 s.select(selections);
15673 });
15674 }
15675
15676 pub fn split_selection_into_lines(
15677 &mut self,
15678 action: &SplitSelectionIntoLines,
15679 window: &mut Window,
15680 cx: &mut Context<Self>,
15681 ) {
15682 let selections = self
15683 .selections
15684 .all::<Point>(&self.display_snapshot(cx))
15685 .into_iter()
15686 .map(|selection| selection.start..selection.end)
15687 .collect::<Vec<_>>();
15688 self.unfold_ranges(&selections, true, false, cx);
15689
15690 let mut new_selection_ranges = Vec::new();
15691 {
15692 let buffer = self.buffer.read(cx).read(cx);
15693 for selection in selections {
15694 for row in selection.start.row..selection.end.row {
15695 let line_start = Point::new(row, 0);
15696 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
15697
15698 if action.keep_selections {
15699 // Keep the selection range for each line
15700 let selection_start = if row == selection.start.row {
15701 selection.start
15702 } else {
15703 line_start
15704 };
15705 new_selection_ranges.push(selection_start..line_end);
15706 } else {
15707 // Collapse to cursor at end of line
15708 new_selection_ranges.push(line_end..line_end);
15709 }
15710 }
15711
15712 let is_multiline_selection = selection.start.row != selection.end.row;
15713 // Don't insert last one if it's a multi-line selection ending at the start of a line,
15714 // so this action feels more ergonomic when paired with other selection operations
15715 let should_skip_last = is_multiline_selection && selection.end.column == 0;
15716 if !should_skip_last {
15717 if action.keep_selections {
15718 if is_multiline_selection {
15719 let line_start = Point::new(selection.end.row, 0);
15720 new_selection_ranges.push(line_start..selection.end);
15721 } else {
15722 new_selection_ranges.push(selection.start..selection.end);
15723 }
15724 } else {
15725 new_selection_ranges.push(selection.end..selection.end);
15726 }
15727 }
15728 }
15729 }
15730 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15731 s.select_ranges(new_selection_ranges);
15732 });
15733 }
15734
15735 pub fn add_selection_above(
15736 &mut self,
15737 action: &AddSelectionAbove,
15738 window: &mut Window,
15739 cx: &mut Context<Self>,
15740 ) {
15741 self.add_selection(true, action.skip_soft_wrap, window, cx);
15742 }
15743
15744 pub fn add_selection_below(
15745 &mut self,
15746 action: &AddSelectionBelow,
15747 window: &mut Window,
15748 cx: &mut Context<Self>,
15749 ) {
15750 self.add_selection(false, action.skip_soft_wrap, window, cx);
15751 }
15752
15753 fn add_selection(
15754 &mut self,
15755 above: bool,
15756 skip_soft_wrap: bool,
15757 window: &mut Window,
15758 cx: &mut Context<Self>,
15759 ) {
15760 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15761
15762 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15763 let all_selections = self.selections.all::<Point>(&display_map);
15764 let text_layout_details = self.text_layout_details(window, cx);
15765
15766 let (mut columnar_selections, new_selections_to_columnarize) = {
15767 if let Some(state) = self.add_selections_state.as_ref() {
15768 let columnar_selection_ids: HashSet<_> = state
15769 .groups
15770 .iter()
15771 .flat_map(|group| group.stack.iter())
15772 .copied()
15773 .collect();
15774
15775 all_selections
15776 .into_iter()
15777 .partition(|s| columnar_selection_ids.contains(&s.id))
15778 } else {
15779 (Vec::new(), all_selections)
15780 }
15781 };
15782
15783 let mut state = self
15784 .add_selections_state
15785 .take()
15786 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
15787
15788 for selection in new_selections_to_columnarize {
15789 let range = selection.display_range(&display_map).sorted();
15790 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
15791 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
15792 let positions = start_x.min(end_x)..start_x.max(end_x);
15793 let mut stack = Vec::new();
15794 for row in range.start.row().0..=range.end.row().0 {
15795 if let Some(selection) = self.selections.build_columnar_selection(
15796 &display_map,
15797 DisplayRow(row),
15798 &positions,
15799 selection.reversed,
15800 &text_layout_details,
15801 ) {
15802 stack.push(selection.id);
15803 columnar_selections.push(selection);
15804 }
15805 }
15806 if !stack.is_empty() {
15807 if above {
15808 stack.reverse();
15809 }
15810 state.groups.push(AddSelectionsGroup { above, stack });
15811 }
15812 }
15813
15814 let mut final_selections = Vec::new();
15815 let end_row = if above {
15816 DisplayRow(0)
15817 } else {
15818 display_map.max_point().row()
15819 };
15820
15821 // When `skip_soft_wrap` is true, we use UTF-16 columns instead of pixel
15822 // positions to place new selections, so we need to keep track of the
15823 // column range of the oldest selection in each group, because
15824 // intermediate selections may have been clamped to shorter lines.
15825 let mut goal_columns_by_selection_id = if skip_soft_wrap {
15826 let mut map = HashMap::default();
15827 for group in state.groups.iter() {
15828 if let Some(oldest_id) = group.stack.first() {
15829 if let Some(oldest_selection) =
15830 columnar_selections.iter().find(|s| s.id == *oldest_id)
15831 {
15832 let snapshot = display_map.buffer_snapshot();
15833 let start_col =
15834 snapshot.point_to_point_utf16(oldest_selection.start).column;
15835 let end_col = snapshot.point_to_point_utf16(oldest_selection.end).column;
15836 let goal_columns = start_col.min(end_col)..start_col.max(end_col);
15837 for id in &group.stack {
15838 map.insert(*id, goal_columns.clone());
15839 }
15840 }
15841 }
15842 }
15843 map
15844 } else {
15845 HashMap::default()
15846 };
15847
15848 let mut last_added_item_per_group = HashMap::default();
15849 for group in state.groups.iter_mut() {
15850 if let Some(last_id) = group.stack.last() {
15851 last_added_item_per_group.insert(*last_id, group);
15852 }
15853 }
15854
15855 for selection in columnar_selections {
15856 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
15857 if above == group.above {
15858 let range = selection.display_range(&display_map).sorted();
15859 debug_assert_eq!(range.start.row(), range.end.row());
15860 let row = range.start.row();
15861 let positions =
15862 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
15863 Pixels::from(start)..Pixels::from(end)
15864 } else {
15865 let start_x =
15866 display_map.x_for_display_point(range.start, &text_layout_details);
15867 let end_x =
15868 display_map.x_for_display_point(range.end, &text_layout_details);
15869 start_x.min(end_x)..start_x.max(end_x)
15870 };
15871
15872 let maybe_new_selection = if skip_soft_wrap {
15873 let goal_columns = goal_columns_by_selection_id
15874 .remove(&selection.id)
15875 .unwrap_or_else(|| {
15876 let snapshot = display_map.buffer_snapshot();
15877 let start_col =
15878 snapshot.point_to_point_utf16(selection.start).column;
15879 let end_col = snapshot.point_to_point_utf16(selection.end).column;
15880 start_col.min(end_col)..start_col.max(end_col)
15881 });
15882 self.selections.find_next_columnar_selection_by_buffer_row(
15883 &display_map,
15884 row,
15885 end_row,
15886 above,
15887 &goal_columns,
15888 selection.reversed,
15889 &text_layout_details,
15890 )
15891 } else {
15892 self.selections.find_next_columnar_selection_by_display_row(
15893 &display_map,
15894 row,
15895 end_row,
15896 above,
15897 &positions,
15898 selection.reversed,
15899 &text_layout_details,
15900 )
15901 };
15902
15903 if let Some(new_selection) = maybe_new_selection {
15904 group.stack.push(new_selection.id);
15905 if above {
15906 final_selections.push(new_selection);
15907 final_selections.push(selection);
15908 } else {
15909 final_selections.push(selection);
15910 final_selections.push(new_selection);
15911 }
15912 } else {
15913 final_selections.push(selection);
15914 }
15915 } else {
15916 group.stack.pop();
15917 }
15918 } else {
15919 final_selections.push(selection);
15920 }
15921 }
15922
15923 self.change_selections(Default::default(), window, cx, |s| {
15924 s.select(final_selections);
15925 });
15926
15927 let final_selection_ids: HashSet<_> = self
15928 .selections
15929 .all::<Point>(&display_map)
15930 .iter()
15931 .map(|s| s.id)
15932 .collect();
15933 state.groups.retain_mut(|group| {
15934 // selections might get merged above so we remove invalid items from stacks
15935 group.stack.retain(|id| final_selection_ids.contains(id));
15936
15937 // single selection in stack can be treated as initial state
15938 group.stack.len() > 1
15939 });
15940
15941 if !state.groups.is_empty() {
15942 self.add_selections_state = Some(state);
15943 }
15944 }
15945
15946 pub fn insert_snippet_at_selections(
15947 &mut self,
15948 action: &InsertSnippet,
15949 window: &mut Window,
15950 cx: &mut Context<Self>,
15951 ) {
15952 self.try_insert_snippet_at_selections(action, window, cx)
15953 .log_err();
15954 }
15955
15956 fn try_insert_snippet_at_selections(
15957 &mut self,
15958 action: &InsertSnippet,
15959 window: &mut Window,
15960 cx: &mut Context<Self>,
15961 ) -> Result<()> {
15962 let insertion_ranges = self
15963 .selections
15964 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15965 .into_iter()
15966 .map(|selection| selection.range())
15967 .collect_vec();
15968
15969 let snippet = if let Some(snippet_body) = &action.snippet {
15970 if action.language.is_none() && action.name.is_none() {
15971 Snippet::parse(snippet_body)?
15972 } else {
15973 bail!("`snippet` is mutually exclusive with `language` and `name`")
15974 }
15975 } else if let Some(name) = &action.name {
15976 let project = self.project().context("no project")?;
15977 let snippet_store = project.read(cx).snippets().read(cx);
15978 let snippet = snippet_store
15979 .snippets_for(action.language.clone(), cx)
15980 .into_iter()
15981 .find(|snippet| snippet.name == *name)
15982 .context("snippet not found")?;
15983 Snippet::parse(&snippet.body)?
15984 } else {
15985 // todo(andrew): open modal to select snippet
15986 bail!("`name` or `snippet` is required")
15987 };
15988
15989 self.insert_snippet(&insertion_ranges, snippet, window, cx)
15990 }
15991
15992 fn select_match_ranges(
15993 &mut self,
15994 range: Range<MultiBufferOffset>,
15995 reversed: bool,
15996 replace_newest: bool,
15997 auto_scroll: Option<Autoscroll>,
15998 window: &mut Window,
15999 cx: &mut Context<Editor>,
16000 ) {
16001 self.unfold_ranges(
16002 std::slice::from_ref(&range),
16003 false,
16004 auto_scroll.is_some(),
16005 cx,
16006 );
16007 let effects = if let Some(scroll) = auto_scroll {
16008 SelectionEffects::scroll(scroll)
16009 } else {
16010 SelectionEffects::no_scroll()
16011 };
16012 self.change_selections(effects, window, cx, |s| {
16013 if replace_newest {
16014 s.delete(s.newest_anchor().id);
16015 }
16016 if reversed {
16017 s.insert_range(range.end..range.start);
16018 } else {
16019 s.insert_range(range);
16020 }
16021 });
16022 }
16023
16024 pub fn select_next_match_internal(
16025 &mut self,
16026 display_map: &DisplaySnapshot,
16027 replace_newest: bool,
16028 autoscroll: Option<Autoscroll>,
16029 window: &mut Window,
16030 cx: &mut Context<Self>,
16031 ) -> Result<()> {
16032 let buffer = display_map.buffer_snapshot();
16033 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
16034 if let Some(mut select_next_state) = self.select_next_state.take() {
16035 let query = &select_next_state.query;
16036 if !select_next_state.done {
16037 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
16038 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
16039 let mut next_selected_range = None;
16040
16041 let bytes_after_last_selection =
16042 buffer.bytes_in_range(last_selection.end..buffer.len());
16043 let bytes_before_first_selection =
16044 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
16045 let query_matches = query
16046 .stream_find_iter(bytes_after_last_selection)
16047 .map(|result| (last_selection.end, result))
16048 .chain(
16049 query
16050 .stream_find_iter(bytes_before_first_selection)
16051 .map(|result| (MultiBufferOffset(0), result)),
16052 );
16053
16054 for (start_offset, query_match) in query_matches {
16055 let query_match = query_match.unwrap(); // can only fail due to I/O
16056 let offset_range =
16057 start_offset + query_match.start()..start_offset + query_match.end();
16058
16059 if !select_next_state.wordwise
16060 || (!buffer.is_inside_word(offset_range.start, None)
16061 && !buffer.is_inside_word(offset_range.end, None))
16062 {
16063 let idx = selections
16064 .partition_point(|selection| selection.end <= offset_range.start);
16065 let overlaps = selections
16066 .get(idx)
16067 .map_or(false, |selection| selection.start < offset_range.end);
16068
16069 if !overlaps {
16070 next_selected_range = Some(offset_range);
16071 break;
16072 }
16073 }
16074 }
16075
16076 if let Some(next_selected_range) = next_selected_range {
16077 self.select_match_ranges(
16078 next_selected_range,
16079 last_selection.reversed,
16080 replace_newest,
16081 autoscroll,
16082 window,
16083 cx,
16084 );
16085 } else {
16086 select_next_state.done = true;
16087 }
16088 }
16089
16090 self.select_next_state = Some(select_next_state);
16091 } else {
16092 let mut only_carets = true;
16093 let mut same_text_selected = true;
16094 let mut selected_text = None;
16095
16096 let mut selections_iter = selections.iter().peekable();
16097 while let Some(selection) = selections_iter.next() {
16098 if selection.start != selection.end {
16099 only_carets = false;
16100 }
16101
16102 if same_text_selected {
16103 if selected_text.is_none() {
16104 selected_text =
16105 Some(buffer.text_for_range(selection.range()).collect::<String>());
16106 }
16107
16108 if let Some(next_selection) = selections_iter.peek() {
16109 if next_selection.len() == selection.len() {
16110 let next_selected_text = buffer
16111 .text_for_range(next_selection.range())
16112 .collect::<String>();
16113 if Some(next_selected_text) != selected_text {
16114 same_text_selected = false;
16115 selected_text = None;
16116 }
16117 } else {
16118 same_text_selected = false;
16119 selected_text = None;
16120 }
16121 }
16122 }
16123 }
16124
16125 if only_carets {
16126 for selection in &mut selections {
16127 let (word_range, _) = buffer.surrounding_word(selection.start, None);
16128 selection.start = word_range.start;
16129 selection.end = word_range.end;
16130 selection.goal = SelectionGoal::None;
16131 selection.reversed = false;
16132 self.select_match_ranges(
16133 selection.start..selection.end,
16134 selection.reversed,
16135 replace_newest,
16136 autoscroll,
16137 window,
16138 cx,
16139 );
16140 }
16141
16142 if selections.len() == 1 {
16143 let selection = selections
16144 .last()
16145 .expect("ensured that there's only one selection");
16146 let query = buffer
16147 .text_for_range(selection.start..selection.end)
16148 .collect::<String>();
16149 let is_empty = query.is_empty();
16150 let select_state = SelectNextState {
16151 query: self.build_query(&[query], cx)?,
16152 wordwise: true,
16153 done: is_empty,
16154 };
16155 self.select_next_state = Some(select_state);
16156 } else {
16157 self.select_next_state = None;
16158 }
16159 } else if let Some(selected_text) = selected_text {
16160 self.select_next_state = Some(SelectNextState {
16161 query: self.build_query(&[selected_text], cx)?,
16162 wordwise: false,
16163 done: false,
16164 });
16165 self.select_next_match_internal(
16166 display_map,
16167 replace_newest,
16168 autoscroll,
16169 window,
16170 cx,
16171 )?;
16172 }
16173 }
16174 Ok(())
16175 }
16176
16177 pub fn select_all_matches(
16178 &mut self,
16179 _action: &SelectAllMatches,
16180 window: &mut Window,
16181 cx: &mut Context<Self>,
16182 ) -> Result<()> {
16183 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16184
16185 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16186
16187 self.select_next_match_internal(&display_map, false, None, window, cx)?;
16188 let Some(select_next_state) = self.select_next_state.as_mut().filter(|state| !state.done)
16189 else {
16190 return Ok(());
16191 };
16192
16193 let mut new_selections = Vec::new();
16194 let initial_selection = self.selections.oldest::<MultiBufferOffset>(&display_map);
16195 let reversed = initial_selection.reversed;
16196 let buffer = display_map.buffer_snapshot();
16197 let query_matches = select_next_state
16198 .query
16199 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
16200
16201 for query_match in query_matches.into_iter() {
16202 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
16203 let offset_range = if reversed {
16204 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
16205 } else {
16206 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
16207 };
16208
16209 let is_partial_word_match = select_next_state.wordwise
16210 && (buffer.is_inside_word(offset_range.start, None)
16211 || buffer.is_inside_word(offset_range.end, None));
16212
16213 let is_initial_selection = MultiBufferOffset(query_match.start())
16214 == initial_selection.start
16215 && MultiBufferOffset(query_match.end()) == initial_selection.end;
16216
16217 if !is_partial_word_match && !is_initial_selection {
16218 new_selections.push(offset_range);
16219 }
16220 }
16221
16222 // Ensure that the initial range is the last selection, as
16223 // `MutableSelectionsCollection::select_ranges` makes the last selection
16224 // the newest selection, which the editor then relies on as the primary
16225 // cursor for scroll targeting. Without this, the last match would then
16226 // be automatically focused when the user started editing the selected
16227 // matches.
16228 let initial_directed_range = if reversed {
16229 initial_selection.end..initial_selection.start
16230 } else {
16231 initial_selection.start..initial_selection.end
16232 };
16233 new_selections.push(initial_directed_range);
16234
16235 select_next_state.done = true;
16236 self.unfold_ranges(&new_selections, false, false, cx);
16237 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
16238 selections.select_ranges(new_selections)
16239 });
16240
16241 Ok(())
16242 }
16243
16244 pub fn select_next(
16245 &mut self,
16246 action: &SelectNext,
16247 window: &mut Window,
16248 cx: &mut Context<Self>,
16249 ) -> Result<()> {
16250 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16251 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16252 self.select_next_match_internal(
16253 &display_map,
16254 action.replace_newest,
16255 Some(Autoscroll::newest()),
16256 window,
16257 cx,
16258 )
16259 }
16260
16261 pub fn select_previous(
16262 &mut self,
16263 action: &SelectPrevious,
16264 window: &mut Window,
16265 cx: &mut Context<Self>,
16266 ) -> Result<()> {
16267 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16268 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16269 let buffer = display_map.buffer_snapshot();
16270 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
16271 if let Some(mut select_prev_state) = self.select_prev_state.take() {
16272 let query = &select_prev_state.query;
16273 if !select_prev_state.done {
16274 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
16275 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
16276 let mut next_selected_range = None;
16277 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
16278 let bytes_before_last_selection =
16279 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
16280 let bytes_after_first_selection =
16281 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
16282 let query_matches = query
16283 .stream_find_iter(bytes_before_last_selection)
16284 .map(|result| (last_selection.start, result))
16285 .chain(
16286 query
16287 .stream_find_iter(bytes_after_first_selection)
16288 .map(|result| (buffer.len(), result)),
16289 );
16290 for (end_offset, query_match) in query_matches {
16291 let query_match = query_match.unwrap(); // can only fail due to I/O
16292 let offset_range =
16293 end_offset - query_match.end()..end_offset - query_match.start();
16294
16295 if !select_prev_state.wordwise
16296 || (!buffer.is_inside_word(offset_range.start, None)
16297 && !buffer.is_inside_word(offset_range.end, None))
16298 {
16299 next_selected_range = Some(offset_range);
16300 break;
16301 }
16302 }
16303
16304 if let Some(next_selected_range) = next_selected_range {
16305 self.select_match_ranges(
16306 next_selected_range,
16307 last_selection.reversed,
16308 action.replace_newest,
16309 Some(Autoscroll::newest()),
16310 window,
16311 cx,
16312 );
16313 } else {
16314 select_prev_state.done = true;
16315 }
16316 }
16317
16318 self.select_prev_state = Some(select_prev_state);
16319 } else {
16320 let mut only_carets = true;
16321 let mut same_text_selected = true;
16322 let mut selected_text = None;
16323
16324 let mut selections_iter = selections.iter().peekable();
16325 while let Some(selection) = selections_iter.next() {
16326 if selection.start != selection.end {
16327 only_carets = false;
16328 }
16329
16330 if same_text_selected {
16331 if selected_text.is_none() {
16332 selected_text =
16333 Some(buffer.text_for_range(selection.range()).collect::<String>());
16334 }
16335
16336 if let Some(next_selection) = selections_iter.peek() {
16337 if next_selection.len() == selection.len() {
16338 let next_selected_text = buffer
16339 .text_for_range(next_selection.range())
16340 .collect::<String>();
16341 if Some(next_selected_text) != selected_text {
16342 same_text_selected = false;
16343 selected_text = None;
16344 }
16345 } else {
16346 same_text_selected = false;
16347 selected_text = None;
16348 }
16349 }
16350 }
16351 }
16352
16353 if only_carets {
16354 for selection in &mut selections {
16355 let (word_range, _) = buffer.surrounding_word(selection.start, None);
16356 selection.start = word_range.start;
16357 selection.end = word_range.end;
16358 selection.goal = SelectionGoal::None;
16359 selection.reversed = false;
16360 self.select_match_ranges(
16361 selection.start..selection.end,
16362 selection.reversed,
16363 action.replace_newest,
16364 Some(Autoscroll::newest()),
16365 window,
16366 cx,
16367 );
16368 }
16369 if selections.len() == 1 {
16370 let selection = selections
16371 .last()
16372 .expect("ensured that there's only one selection");
16373 let query = buffer
16374 .text_for_range(selection.start..selection.end)
16375 .collect::<String>();
16376 let is_empty = query.is_empty();
16377 let select_state = SelectNextState {
16378 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
16379 wordwise: true,
16380 done: is_empty,
16381 };
16382 self.select_prev_state = Some(select_state);
16383 } else {
16384 self.select_prev_state = None;
16385 }
16386 } else if let Some(selected_text) = selected_text {
16387 self.select_prev_state = Some(SelectNextState {
16388 query: self
16389 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
16390 wordwise: false,
16391 done: false,
16392 });
16393 self.select_previous(action, window, cx)?;
16394 }
16395 }
16396 Ok(())
16397 }
16398
16399 /// Builds an `AhoCorasick` automaton from the provided patterns, while
16400 /// setting the case sensitivity based on the global
16401 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
16402 /// editor's settings.
16403 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
16404 where
16405 I: IntoIterator<Item = P>,
16406 P: AsRef<[u8]>,
16407 {
16408 let case_sensitive = self
16409 .select_next_is_case_sensitive
16410 .unwrap_or_else(|| EditorSettings::get_global(cx).search.case_sensitive);
16411
16412 let mut builder = AhoCorasickBuilder::new();
16413 builder.ascii_case_insensitive(!case_sensitive);
16414 builder.build(patterns)
16415 }
16416
16417 pub fn find_next_match(
16418 &mut self,
16419 _: &FindNextMatch,
16420 window: &mut Window,
16421 cx: &mut Context<Self>,
16422 ) -> Result<()> {
16423 let selections = self.selections.disjoint_anchors_arc();
16424 match selections.first() {
16425 Some(first) if selections.len() >= 2 => {
16426 self.change_selections(Default::default(), window, cx, |s| {
16427 s.select_ranges([first.range()]);
16428 });
16429 }
16430 _ => self.select_next(
16431 &SelectNext {
16432 replace_newest: true,
16433 },
16434 window,
16435 cx,
16436 )?,
16437 }
16438 Ok(())
16439 }
16440
16441 pub fn find_previous_match(
16442 &mut self,
16443 _: &FindPreviousMatch,
16444 window: &mut Window,
16445 cx: &mut Context<Self>,
16446 ) -> Result<()> {
16447 let selections = self.selections.disjoint_anchors_arc();
16448 match selections.last() {
16449 Some(last) if selections.len() >= 2 => {
16450 self.change_selections(Default::default(), window, cx, |s| {
16451 s.select_ranges([last.range()]);
16452 });
16453 }
16454 _ => self.select_previous(
16455 &SelectPrevious {
16456 replace_newest: true,
16457 },
16458 window,
16459 cx,
16460 )?,
16461 }
16462 Ok(())
16463 }
16464
16465 pub fn toggle_comments(
16466 &mut self,
16467 action: &ToggleComments,
16468 window: &mut Window,
16469 cx: &mut Context<Self>,
16470 ) {
16471 if self.read_only(cx) {
16472 return;
16473 }
16474 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16475 let text_layout_details = &self.text_layout_details(window, cx);
16476 self.transact(window, cx, |this, window, cx| {
16477 let mut selections = this
16478 .selections
16479 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
16480 let mut edits = Vec::new();
16481 let mut selection_edit_ranges = Vec::new();
16482 let mut last_toggled_row = None;
16483 let snapshot = this.buffer.read(cx).read(cx);
16484 let empty_str: Arc<str> = Arc::default();
16485 let mut suffixes_inserted = Vec::new();
16486 let ignore_indent = action.ignore_indent;
16487
16488 fn comment_prefix_range(
16489 snapshot: &MultiBufferSnapshot,
16490 row: MultiBufferRow,
16491 comment_prefix: &str,
16492 comment_prefix_whitespace: &str,
16493 ignore_indent: bool,
16494 ) -> Range<Point> {
16495 let indent_size = if ignore_indent {
16496 0
16497 } else {
16498 snapshot.indent_size_for_line(row).len
16499 };
16500
16501 let start = Point::new(row.0, indent_size);
16502
16503 let mut line_bytes = snapshot
16504 .bytes_in_range(start..snapshot.max_point())
16505 .flatten()
16506 .copied();
16507
16508 // If this line currently begins with the line comment prefix, then record
16509 // the range containing the prefix.
16510 if line_bytes
16511 .by_ref()
16512 .take(comment_prefix.len())
16513 .eq(comment_prefix.bytes())
16514 {
16515 // Include any whitespace that matches the comment prefix.
16516 let matching_whitespace_len = line_bytes
16517 .zip(comment_prefix_whitespace.bytes())
16518 .take_while(|(a, b)| a == b)
16519 .count() as u32;
16520 let end = Point::new(
16521 start.row,
16522 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
16523 );
16524 start..end
16525 } else {
16526 start..start
16527 }
16528 }
16529
16530 fn comment_suffix_range(
16531 snapshot: &MultiBufferSnapshot,
16532 row: MultiBufferRow,
16533 comment_suffix: &str,
16534 comment_suffix_has_leading_space: bool,
16535 ) -> Range<Point> {
16536 let end = Point::new(row.0, snapshot.line_len(row));
16537 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
16538
16539 let mut line_end_bytes = snapshot
16540 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
16541 .flatten()
16542 .copied();
16543
16544 let leading_space_len = if suffix_start_column > 0
16545 && line_end_bytes.next() == Some(b' ')
16546 && comment_suffix_has_leading_space
16547 {
16548 1
16549 } else {
16550 0
16551 };
16552
16553 // If this line currently begins with the line comment prefix, then record
16554 // the range containing the prefix.
16555 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
16556 let start = Point::new(end.row, suffix_start_column - leading_space_len);
16557 start..end
16558 } else {
16559 end..end
16560 }
16561 }
16562
16563 // TODO: Handle selections that cross excerpts
16564 for selection in &mut selections {
16565 let start_column = snapshot
16566 .indent_size_for_line(MultiBufferRow(selection.start.row))
16567 .len;
16568 let language = if let Some(language) =
16569 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
16570 {
16571 language
16572 } else {
16573 continue;
16574 };
16575
16576 selection_edit_ranges.clear();
16577
16578 // If multiple selections contain a given row, avoid processing that
16579 // row more than once.
16580 let mut start_row = MultiBufferRow(selection.start.row);
16581 if last_toggled_row == Some(start_row) {
16582 start_row = start_row.next_row();
16583 }
16584 let end_row =
16585 if selection.end.row > selection.start.row && selection.end.column == 0 {
16586 MultiBufferRow(selection.end.row - 1)
16587 } else {
16588 MultiBufferRow(selection.end.row)
16589 };
16590 last_toggled_row = Some(end_row);
16591
16592 if start_row > end_row {
16593 continue;
16594 }
16595
16596 // If the language has line comments, toggle those.
16597 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
16598
16599 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
16600 if ignore_indent {
16601 full_comment_prefixes = full_comment_prefixes
16602 .into_iter()
16603 .map(|s| Arc::from(s.trim_end()))
16604 .collect();
16605 }
16606
16607 if !full_comment_prefixes.is_empty() {
16608 let first_prefix = full_comment_prefixes
16609 .first()
16610 .expect("prefixes is non-empty");
16611 let prefix_trimmed_lengths = full_comment_prefixes
16612 .iter()
16613 .map(|p| p.trim_end_matches(' ').len())
16614 .collect::<SmallVec<[usize; 4]>>();
16615
16616 let mut all_selection_lines_are_comments = true;
16617
16618 for row in start_row.0..=end_row.0 {
16619 let row = MultiBufferRow(row);
16620 if start_row < end_row && snapshot.is_line_blank(row) {
16621 continue;
16622 }
16623
16624 let prefix_range = full_comment_prefixes
16625 .iter()
16626 .zip(prefix_trimmed_lengths.iter().copied())
16627 .map(|(prefix, trimmed_prefix_len)| {
16628 comment_prefix_range(
16629 snapshot.deref(),
16630 row,
16631 &prefix[..trimmed_prefix_len],
16632 &prefix[trimmed_prefix_len..],
16633 ignore_indent,
16634 )
16635 })
16636 .max_by_key(|range| range.end.column - range.start.column)
16637 .expect("prefixes is non-empty");
16638
16639 if prefix_range.is_empty() {
16640 all_selection_lines_are_comments = false;
16641 }
16642
16643 selection_edit_ranges.push(prefix_range);
16644 }
16645
16646 if all_selection_lines_are_comments {
16647 edits.extend(
16648 selection_edit_ranges
16649 .iter()
16650 .cloned()
16651 .map(|range| (range, empty_str.clone())),
16652 );
16653 } else {
16654 let min_column = selection_edit_ranges
16655 .iter()
16656 .map(|range| range.start.column)
16657 .min()
16658 .unwrap_or(0);
16659 edits.extend(selection_edit_ranges.iter().map(|range| {
16660 let position = Point::new(range.start.row, min_column);
16661 (position..position, first_prefix.clone())
16662 }));
16663 }
16664 } else if let Some(BlockCommentConfig {
16665 start: full_comment_prefix,
16666 end: comment_suffix,
16667 ..
16668 }) = language.block_comment()
16669 {
16670 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
16671 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
16672 let prefix_range = comment_prefix_range(
16673 snapshot.deref(),
16674 start_row,
16675 comment_prefix,
16676 comment_prefix_whitespace,
16677 ignore_indent,
16678 );
16679 let suffix_range = comment_suffix_range(
16680 snapshot.deref(),
16681 end_row,
16682 comment_suffix.trim_start_matches(' '),
16683 comment_suffix.starts_with(' '),
16684 );
16685
16686 if prefix_range.is_empty() || suffix_range.is_empty() {
16687 edits.push((
16688 prefix_range.start..prefix_range.start,
16689 full_comment_prefix.clone(),
16690 ));
16691 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
16692 suffixes_inserted.push((end_row, comment_suffix.len()));
16693 } else {
16694 edits.push((prefix_range, empty_str.clone()));
16695 edits.push((suffix_range, empty_str.clone()));
16696 }
16697 } else {
16698 continue;
16699 }
16700 }
16701
16702 drop(snapshot);
16703 this.buffer.update(cx, |buffer, cx| {
16704 buffer.edit(edits, None, cx);
16705 });
16706
16707 // Adjust selections so that they end before any comment suffixes that
16708 // were inserted.
16709 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
16710 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16711 let snapshot = this.buffer.read(cx).read(cx);
16712 for selection in &mut selections {
16713 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
16714 match row.cmp(&MultiBufferRow(selection.end.row)) {
16715 Ordering::Less => {
16716 suffixes_inserted.next();
16717 continue;
16718 }
16719 Ordering::Greater => break,
16720 Ordering::Equal => {
16721 if selection.end.column == snapshot.line_len(row) {
16722 if selection.is_empty() {
16723 selection.start.column -= suffix_len as u32;
16724 }
16725 selection.end.column -= suffix_len as u32;
16726 }
16727 break;
16728 }
16729 }
16730 }
16731 }
16732
16733 drop(snapshot);
16734 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
16735
16736 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16737 let selections_on_single_row = selections.windows(2).all(|selections| {
16738 selections[0].start.row == selections[1].start.row
16739 && selections[0].end.row == selections[1].end.row
16740 && selections[0].start.row == selections[0].end.row
16741 });
16742 let selections_selecting = selections
16743 .iter()
16744 .any(|selection| selection.start != selection.end);
16745 let advance_downwards = action.advance_downwards
16746 && selections_on_single_row
16747 && !selections_selecting
16748 && !matches!(this.mode, EditorMode::SingleLine);
16749
16750 if advance_downwards {
16751 let snapshot = this.buffer.read(cx).snapshot(cx);
16752
16753 this.change_selections(Default::default(), window, cx, |s| {
16754 s.move_cursors_with(&mut |display_snapshot, display_point, _| {
16755 let mut point = display_point.to_point(display_snapshot);
16756 point.row += 1;
16757 point = snapshot.clip_point(point, Bias::Left);
16758 let display_point = point.to_display_point(display_snapshot);
16759 let goal = SelectionGoal::HorizontalPosition(
16760 display_snapshot
16761 .x_for_display_point(display_point, text_layout_details)
16762 .into(),
16763 );
16764 (display_point, goal)
16765 })
16766 });
16767 }
16768 });
16769 }
16770
16771 pub fn select_enclosing_symbol(
16772 &mut self,
16773 _: &SelectEnclosingSymbol,
16774 window: &mut Window,
16775 cx: &mut Context<Self>,
16776 ) {
16777 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16778
16779 let buffer = self.buffer.read(cx).snapshot(cx);
16780 let old_selections = self
16781 .selections
16782 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16783 .into_boxed_slice();
16784
16785 fn update_selection(
16786 selection: &Selection<MultiBufferOffset>,
16787 buffer_snap: &MultiBufferSnapshot,
16788 ) -> Option<Selection<MultiBufferOffset>> {
16789 let cursor = selection.head();
16790 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
16791 for symbol in symbols.iter().rev() {
16792 let start = symbol.range.start.to_offset(buffer_snap);
16793 let end = symbol.range.end.to_offset(buffer_snap);
16794 let new_range = start..end;
16795 if start < selection.start || end > selection.end {
16796 return Some(Selection {
16797 id: selection.id,
16798 start: new_range.start,
16799 end: new_range.end,
16800 goal: SelectionGoal::None,
16801 reversed: selection.reversed,
16802 });
16803 }
16804 }
16805 None
16806 }
16807
16808 let mut selected_larger_symbol = false;
16809 let new_selections = old_selections
16810 .iter()
16811 .map(|selection| match update_selection(selection, &buffer) {
16812 Some(new_selection) => {
16813 if new_selection.range() != selection.range() {
16814 selected_larger_symbol = true;
16815 }
16816 new_selection
16817 }
16818 None => selection.clone(),
16819 })
16820 .collect::<Vec<_>>();
16821
16822 if selected_larger_symbol {
16823 self.change_selections(Default::default(), window, cx, |s| {
16824 s.select(new_selections);
16825 });
16826 }
16827 }
16828
16829 pub fn select_larger_syntax_node(
16830 &mut self,
16831 _: &SelectLargerSyntaxNode,
16832 window: &mut Window,
16833 cx: &mut Context<Self>,
16834 ) {
16835 let Some(visible_row_count) = self.visible_row_count() else {
16836 return;
16837 };
16838 let old_selections: Box<[_]> = self
16839 .selections
16840 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16841 .into();
16842 if old_selections.is_empty() {
16843 return;
16844 }
16845
16846 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16847
16848 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16849 let buffer = self.buffer.read(cx).snapshot(cx);
16850
16851 let mut selected_larger_node = false;
16852 let mut new_selections = old_selections
16853 .iter()
16854 .map(|selection| {
16855 let old_range = selection.start..selection.end;
16856
16857 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
16858 // manually select word at selection
16859 if ["string_content", "inline"].contains(&node.kind()) {
16860 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
16861 // ignore if word is already selected
16862 if !word_range.is_empty() && old_range != word_range {
16863 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
16864 // only select word if start and end point belongs to same word
16865 if word_range == last_word_range {
16866 selected_larger_node = true;
16867 return Selection {
16868 id: selection.id,
16869 start: word_range.start,
16870 end: word_range.end,
16871 goal: SelectionGoal::None,
16872 reversed: selection.reversed,
16873 };
16874 }
16875 }
16876 }
16877 }
16878
16879 let mut new_range = old_range.clone();
16880 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
16881 new_range = range;
16882 if !node.is_named() {
16883 continue;
16884 }
16885 if !display_map.intersects_fold(new_range.start)
16886 && !display_map.intersects_fold(new_range.end)
16887 {
16888 break;
16889 }
16890 }
16891
16892 selected_larger_node |= new_range != old_range;
16893 Selection {
16894 id: selection.id,
16895 start: new_range.start,
16896 end: new_range.end,
16897 goal: SelectionGoal::None,
16898 reversed: selection.reversed,
16899 }
16900 })
16901 .collect::<Vec<_>>();
16902
16903 if !selected_larger_node {
16904 return; // don't put this call in the history
16905 }
16906
16907 // scroll based on transformation done to the last selection created by the user
16908 let (last_old, last_new) = old_selections
16909 .last()
16910 .zip(new_selections.last().cloned())
16911 .expect("old_selections isn't empty");
16912
16913 let is_selection_reversed = if new_selections.len() == 1 {
16914 let should_be_reversed = last_old.start != last_new.start;
16915 new_selections.last_mut().expect("checked above").reversed = should_be_reversed;
16916 should_be_reversed
16917 } else {
16918 last_new.reversed
16919 };
16920
16921 if selected_larger_node {
16922 self.select_syntax_node_history.disable_clearing = true;
16923 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16924 s.select(new_selections.clone());
16925 });
16926 self.select_syntax_node_history.disable_clearing = false;
16927 }
16928
16929 let start_row = last_new.start.to_display_point(&display_map).row().0;
16930 let end_row = last_new.end.to_display_point(&display_map).row().0;
16931 let selection_height = end_row - start_row + 1;
16932 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
16933
16934 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
16935 let scroll_behavior = if fits_on_the_screen {
16936 self.request_autoscroll(Autoscroll::fit(), cx);
16937 SelectSyntaxNodeScrollBehavior::FitSelection
16938 } else if is_selection_reversed {
16939 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16940 SelectSyntaxNodeScrollBehavior::CursorTop
16941 } else {
16942 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16943 SelectSyntaxNodeScrollBehavior::CursorBottom
16944 };
16945
16946 let old_selections: Box<[Selection<Anchor>]> = old_selections
16947 .iter()
16948 .map(|s| s.map(|offset| buffer.anchor_before(offset)))
16949 .collect();
16950 self.select_syntax_node_history.push((
16951 old_selections,
16952 scroll_behavior,
16953 is_selection_reversed,
16954 ));
16955 }
16956
16957 pub fn select_smaller_syntax_node(
16958 &mut self,
16959 _: &SelectSmallerSyntaxNode,
16960 window: &mut Window,
16961 cx: &mut Context<Self>,
16962 ) {
16963 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16964
16965 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
16966 self.select_syntax_node_history.pop()
16967 {
16968 if let Some(selection) = selections.last_mut() {
16969 selection.reversed = is_selection_reversed;
16970 }
16971
16972 let snapshot = self.buffer.read(cx).snapshot(cx);
16973 let selections: Vec<Selection<MultiBufferOffset>> = selections
16974 .iter()
16975 .map(|s| s.map(|anchor| anchor.to_offset(&snapshot)))
16976 .collect();
16977
16978 self.select_syntax_node_history.disable_clearing = true;
16979 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16980 s.select(selections);
16981 });
16982 self.select_syntax_node_history.disable_clearing = false;
16983
16984 match scroll_behavior {
16985 SelectSyntaxNodeScrollBehavior::CursorTop => {
16986 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16987 }
16988 SelectSyntaxNodeScrollBehavior::FitSelection => {
16989 self.request_autoscroll(Autoscroll::fit(), cx);
16990 }
16991 SelectSyntaxNodeScrollBehavior::CursorBottom => {
16992 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16993 }
16994 }
16995 }
16996 }
16997
16998 pub fn unwrap_syntax_node(
16999 &mut self,
17000 _: &UnwrapSyntaxNode,
17001 window: &mut Window,
17002 cx: &mut Context<Self>,
17003 ) {
17004 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17005
17006 let buffer = self.buffer.read(cx).snapshot(cx);
17007 let selections = self
17008 .selections
17009 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
17010 .into_iter()
17011 // subtracting the offset requires sorting
17012 .sorted_by_key(|i| i.start);
17013
17014 let full_edits = selections
17015 .into_iter()
17016 .filter_map(|selection| {
17017 let child = if selection.is_empty()
17018 && let Some((_, ancestor_range)) =
17019 buffer.syntax_ancestor(selection.start..selection.end)
17020 {
17021 ancestor_range
17022 } else {
17023 selection.range()
17024 };
17025
17026 let mut parent = child.clone();
17027 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
17028 parent = ancestor_range;
17029 if parent.start < child.start || parent.end > child.end {
17030 break;
17031 }
17032 }
17033
17034 if parent == child {
17035 return None;
17036 }
17037 let text = buffer.text_for_range(child).collect::<String>();
17038 Some((selection.id, parent, text))
17039 })
17040 .collect::<Vec<_>>();
17041 if full_edits.is_empty() {
17042 return;
17043 }
17044
17045 self.transact(window, cx, |this, window, cx| {
17046 this.buffer.update(cx, |buffer, cx| {
17047 buffer.edit(
17048 full_edits
17049 .iter()
17050 .map(|(_, p, t)| (p.clone(), t.clone()))
17051 .collect::<Vec<_>>(),
17052 None,
17053 cx,
17054 );
17055 });
17056 this.change_selections(Default::default(), window, cx, |s| {
17057 let mut offset = 0;
17058 let mut selections = vec![];
17059 for (id, parent, text) in full_edits {
17060 let start = parent.start - offset;
17061 offset += (parent.end - parent.start) - text.len();
17062 selections.push(Selection {
17063 id,
17064 start,
17065 end: start + text.len(),
17066 reversed: false,
17067 goal: Default::default(),
17068 });
17069 }
17070 s.select(selections);
17071 });
17072 });
17073 }
17074
17075 pub fn select_next_syntax_node(
17076 &mut self,
17077 _: &SelectNextSyntaxNode,
17078 window: &mut Window,
17079 cx: &mut Context<Self>,
17080 ) {
17081 let old_selections = self.selections.all_anchors(&self.display_snapshot(cx));
17082 if old_selections.is_empty() {
17083 return;
17084 }
17085
17086 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17087
17088 let buffer = self.buffer.read(cx).snapshot(cx);
17089 let mut selected_sibling = false;
17090
17091 let new_selections = old_selections
17092 .iter()
17093 .map(|selection| {
17094 let old_range =
17095 selection.start.to_offset(&buffer)..selection.end.to_offset(&buffer);
17096 if let Some(results) = buffer.map_excerpt_ranges(
17097 old_range,
17098 |buf, _excerpt_range, input_buffer_range| {
17099 let Some(node) = buf.syntax_next_sibling(input_buffer_range) else {
17100 return Vec::new();
17101 };
17102 vec![(
17103 BufferOffset(node.byte_range().start)
17104 ..BufferOffset(node.byte_range().end),
17105 (),
17106 )]
17107 },
17108 ) && let [(new_range, _)] = results.as_slice()
17109 {
17110 selected_sibling = true;
17111 let new_range =
17112 buffer.anchor_after(new_range.start)..buffer.anchor_before(new_range.end);
17113 Selection {
17114 id: selection.id,
17115 start: new_range.start,
17116 end: new_range.end,
17117 goal: SelectionGoal::None,
17118 reversed: selection.reversed,
17119 }
17120 } else {
17121 selection.clone()
17122 }
17123 })
17124 .collect::<Vec<_>>();
17125
17126 if selected_sibling {
17127 self.change_selections(
17128 SelectionEffects::scroll(Autoscroll::fit()),
17129 window,
17130 cx,
17131 |s| {
17132 s.select(new_selections);
17133 },
17134 );
17135 }
17136 }
17137
17138 pub fn select_prev_syntax_node(
17139 &mut self,
17140 _: &SelectPreviousSyntaxNode,
17141 window: &mut Window,
17142 cx: &mut Context<Self>,
17143 ) {
17144 let old_selections: Arc<[_]> = self.selections.all_anchors(&self.display_snapshot(cx));
17145
17146 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17147
17148 let multibuffer_snapshot = self.buffer.read(cx).snapshot(cx);
17149 let mut selected_sibling = false;
17150
17151 let new_selections = old_selections
17152 .iter()
17153 .map(|selection| {
17154 let old_range = selection.start.to_offset(&multibuffer_snapshot)
17155 ..selection.end.to_offset(&multibuffer_snapshot);
17156 if let Some(results) = multibuffer_snapshot.map_excerpt_ranges(
17157 old_range,
17158 |buf, _excerpt_range, input_buffer_range| {
17159 let Some(node) = buf.syntax_prev_sibling(input_buffer_range) else {
17160 return Vec::new();
17161 };
17162 vec![(
17163 BufferOffset(node.byte_range().start)
17164 ..BufferOffset(node.byte_range().end),
17165 (),
17166 )]
17167 },
17168 ) && let [(new_range, _)] = results.as_slice()
17169 {
17170 selected_sibling = true;
17171 let new_range = multibuffer_snapshot.anchor_after(new_range.start)
17172 ..multibuffer_snapshot.anchor_before(new_range.end);
17173 Selection {
17174 id: selection.id,
17175 start: new_range.start,
17176 end: new_range.end,
17177 goal: SelectionGoal::None,
17178 reversed: selection.reversed,
17179 }
17180 } else {
17181 selection.clone()
17182 }
17183 })
17184 .collect::<Vec<_>>();
17185
17186 if selected_sibling {
17187 self.change_selections(
17188 SelectionEffects::scroll(Autoscroll::fit()),
17189 window,
17190 cx,
17191 |s| {
17192 s.select(new_selections);
17193 },
17194 );
17195 }
17196 }
17197
17198 pub fn move_to_start_of_larger_syntax_node(
17199 &mut self,
17200 _: &MoveToStartOfLargerSyntaxNode,
17201 window: &mut Window,
17202 cx: &mut Context<Self>,
17203 ) {
17204 self.move_cursors_to_syntax_nodes(window, cx, false);
17205 }
17206
17207 pub fn move_to_end_of_larger_syntax_node(
17208 &mut self,
17209 _: &MoveToEndOfLargerSyntaxNode,
17210 window: &mut Window,
17211 cx: &mut Context<Self>,
17212 ) {
17213 self.move_cursors_to_syntax_nodes(window, cx, true);
17214 }
17215
17216 fn find_syntax_node_boundary(
17217 &self,
17218 selection_pos: MultiBufferOffset,
17219 move_to_end: bool,
17220 display_map: &DisplaySnapshot,
17221 buffer: &MultiBufferSnapshot,
17222 ) -> MultiBufferOffset {
17223 let old_range = selection_pos..selection_pos;
17224 let mut new_pos = selection_pos;
17225 let mut search_range = old_range;
17226 while let Some((node, range)) = buffer.syntax_ancestor(search_range.clone()) {
17227 search_range = range.clone();
17228 if !node.is_named()
17229 || display_map.intersects_fold(range.start)
17230 || display_map.intersects_fold(range.end)
17231 // If cursor is already at the end of the syntax node, continue searching
17232 || (move_to_end && range.end == selection_pos)
17233 // If cursor is already at the start of the syntax node, continue searching
17234 || (!move_to_end && range.start == selection_pos)
17235 {
17236 continue;
17237 }
17238
17239 // If we found a string_content node, find the largest parent that is still string_content
17240 // Enables us to skip to the end of strings without taking multiple steps inside the string
17241 let (_, final_range) = if node.kind() == "string_content" {
17242 let mut current_node = node;
17243 let mut current_range = range;
17244 while let Some((parent, parent_range)) =
17245 buffer.syntax_ancestor(current_range.clone())
17246 {
17247 if parent.kind() == "string_content" {
17248 current_node = parent;
17249 current_range = parent_range;
17250 } else {
17251 break;
17252 }
17253 }
17254
17255 (current_node, current_range)
17256 } else {
17257 (node, range)
17258 };
17259
17260 new_pos = if move_to_end {
17261 final_range.end
17262 } else {
17263 final_range.start
17264 };
17265
17266 break;
17267 }
17268
17269 new_pos
17270 }
17271
17272 fn move_cursors_to_syntax_nodes(
17273 &mut self,
17274 window: &mut Window,
17275 cx: &mut Context<Self>,
17276 move_to_end: bool,
17277 ) -> bool {
17278 let old_selections: Box<[_]> = self
17279 .selections
17280 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
17281 .into();
17282 if old_selections.is_empty() {
17283 return false;
17284 }
17285
17286 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17287
17288 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17289 let buffer = self.buffer.read(cx).snapshot(cx);
17290
17291 let mut any_cursor_moved = false;
17292 let new_selections = old_selections
17293 .iter()
17294 .map(|selection| {
17295 if !selection.is_empty() {
17296 return selection.clone();
17297 }
17298
17299 let selection_pos = selection.head();
17300 let new_pos = self.find_syntax_node_boundary(
17301 selection_pos,
17302 move_to_end,
17303 &display_map,
17304 &buffer,
17305 );
17306
17307 any_cursor_moved |= new_pos != selection_pos;
17308
17309 Selection {
17310 id: selection.id,
17311 start: new_pos,
17312 end: new_pos,
17313 goal: SelectionGoal::None,
17314 reversed: false,
17315 }
17316 })
17317 .collect::<Vec<_>>();
17318
17319 self.change_selections(Default::default(), window, cx, |s| {
17320 s.select(new_selections);
17321 });
17322 self.request_autoscroll(Autoscroll::newest(), cx);
17323
17324 any_cursor_moved
17325 }
17326
17327 pub fn select_to_start_of_larger_syntax_node(
17328 &mut self,
17329 _: &SelectToStartOfLargerSyntaxNode,
17330 window: &mut Window,
17331 cx: &mut Context<Self>,
17332 ) {
17333 self.select_to_syntax_nodes(window, cx, false);
17334 }
17335
17336 pub fn select_to_end_of_larger_syntax_node(
17337 &mut self,
17338 _: &SelectToEndOfLargerSyntaxNode,
17339 window: &mut Window,
17340 cx: &mut Context<Self>,
17341 ) {
17342 self.select_to_syntax_nodes(window, cx, true);
17343 }
17344
17345 fn select_to_syntax_nodes(
17346 &mut self,
17347 window: &mut Window,
17348 cx: &mut Context<Self>,
17349 move_to_end: bool,
17350 ) {
17351 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17352
17353 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17354 let buffer = self.buffer.read(cx).snapshot(cx);
17355 let old_selections = self.selections.all::<MultiBufferOffset>(&display_map);
17356
17357 let new_selections = old_selections
17358 .iter()
17359 .map(|selection| {
17360 let new_pos = self.find_syntax_node_boundary(
17361 selection.head(),
17362 move_to_end,
17363 &display_map,
17364 &buffer,
17365 );
17366
17367 let mut new_selection = selection.clone();
17368 new_selection.set_head(new_pos, SelectionGoal::None);
17369 new_selection
17370 })
17371 .collect::<Vec<_>>();
17372
17373 self.change_selections(Default::default(), window, cx, |s| {
17374 s.select(new_selections);
17375 });
17376 }
17377
17378 pub fn move_to_enclosing_bracket(
17379 &mut self,
17380 _: &MoveToEnclosingBracket,
17381 window: &mut Window,
17382 cx: &mut Context<Self>,
17383 ) {
17384 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17385 self.change_selections(Default::default(), window, cx, |s| {
17386 s.move_offsets_with(&mut |snapshot, selection| {
17387 let Some(enclosing_bracket_ranges) =
17388 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
17389 else {
17390 return;
17391 };
17392
17393 let mut best_length = usize::MAX;
17394 let mut best_inside = false;
17395 let mut best_in_bracket_range = false;
17396 let mut best_destination = None;
17397 for (open, close) in enclosing_bracket_ranges {
17398 let close = close.to_inclusive();
17399 let length = *close.end() - open.start;
17400 let inside = selection.start >= open.end && selection.end <= *close.start();
17401 let in_bracket_range = open.to_inclusive().contains(&selection.head())
17402 || close.contains(&selection.head());
17403
17404 // If best is next to a bracket and current isn't, skip
17405 if !in_bracket_range && best_in_bracket_range {
17406 continue;
17407 }
17408
17409 // Prefer smaller lengths unless best is inside and current isn't
17410 if length > best_length && (best_inside || !inside) {
17411 continue;
17412 }
17413
17414 best_length = length;
17415 best_inside = inside;
17416 best_in_bracket_range = in_bracket_range;
17417 best_destination = Some(
17418 if close.contains(&selection.start) && close.contains(&selection.end) {
17419 if inside { open.end } else { open.start }
17420 } else if inside {
17421 *close.start()
17422 } else {
17423 *close.end()
17424 },
17425 );
17426 }
17427
17428 if let Some(destination) = best_destination {
17429 selection.collapse_to(destination, SelectionGoal::None);
17430 }
17431 })
17432 });
17433 }
17434
17435 pub fn undo_selection(
17436 &mut self,
17437 _: &UndoSelection,
17438 window: &mut Window,
17439 cx: &mut Context<Self>,
17440 ) {
17441 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17442 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
17443 self.selection_history.mode = SelectionHistoryMode::Undoing;
17444 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17445 this.end_selection(window, cx);
17446 this.change_selections(
17447 SelectionEffects::scroll(Autoscroll::newest()),
17448 window,
17449 cx,
17450 |s| s.select_anchors(entry.selections.to_vec()),
17451 );
17452 });
17453 self.selection_history.mode = SelectionHistoryMode::Normal;
17454
17455 self.select_next_state = entry.select_next_state;
17456 self.select_prev_state = entry.select_prev_state;
17457 self.add_selections_state = entry.add_selections_state;
17458 }
17459 }
17460
17461 pub fn redo_selection(
17462 &mut self,
17463 _: &RedoSelection,
17464 window: &mut Window,
17465 cx: &mut Context<Self>,
17466 ) {
17467 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17468 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
17469 self.selection_history.mode = SelectionHistoryMode::Redoing;
17470 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17471 this.end_selection(window, cx);
17472 this.change_selections(
17473 SelectionEffects::scroll(Autoscroll::newest()),
17474 window,
17475 cx,
17476 |s| s.select_anchors(entry.selections.to_vec()),
17477 );
17478 });
17479 self.selection_history.mode = SelectionHistoryMode::Normal;
17480
17481 self.select_next_state = entry.select_next_state;
17482 self.select_prev_state = entry.select_prev_state;
17483 self.add_selections_state = entry.add_selections_state;
17484 }
17485 }
17486
17487 pub fn expand_excerpts(
17488 &mut self,
17489 action: &ExpandExcerpts,
17490 _: &mut Window,
17491 cx: &mut Context<Self>,
17492 ) {
17493 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
17494 }
17495
17496 pub fn expand_excerpts_down(
17497 &mut self,
17498 action: &ExpandExcerptsDown,
17499 _: &mut Window,
17500 cx: &mut Context<Self>,
17501 ) {
17502 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
17503 }
17504
17505 pub fn expand_excerpts_up(
17506 &mut self,
17507 action: &ExpandExcerptsUp,
17508 _: &mut Window,
17509 cx: &mut Context<Self>,
17510 ) {
17511 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
17512 }
17513
17514 pub fn expand_excerpts_for_direction(
17515 &mut self,
17516 lines: u32,
17517 direction: ExpandExcerptDirection,
17518 cx: &mut Context<Self>,
17519 ) {
17520 let selections = self.selections.disjoint_anchors_arc();
17521
17522 let lines = if lines == 0 {
17523 EditorSettings::get_global(cx).expand_excerpt_lines
17524 } else {
17525 lines
17526 };
17527
17528 let snapshot = self.buffer.read(cx).snapshot(cx);
17529 let excerpt_anchors = selections
17530 .iter()
17531 .flat_map(|selection| {
17532 snapshot
17533 .range_to_buffer_ranges(selection.range())
17534 .into_iter()
17535 .filter_map(|(buffer_snapshot, range, _)| {
17536 snapshot.anchor_in_excerpt(buffer_snapshot.anchor_after(range.start))
17537 })
17538 })
17539 .collect::<Vec<_>>();
17540
17541 if self.delegate_expand_excerpts {
17542 cx.emit(EditorEvent::ExpandExcerptsRequested {
17543 excerpt_anchors,
17544 lines,
17545 direction,
17546 });
17547 return;
17548 }
17549
17550 self.buffer.update(cx, |buffer, cx| {
17551 buffer.expand_excerpts(excerpt_anchors, lines, direction, cx)
17552 })
17553 }
17554
17555 pub(crate) fn expand_excerpt(
17556 &mut self,
17557 excerpt_anchor: Anchor,
17558 direction: ExpandExcerptDirection,
17559 window: &mut Window,
17560 cx: &mut Context<Self>,
17561 ) {
17562 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
17563
17564 if self.delegate_expand_excerpts {
17565 cx.emit(EditorEvent::ExpandExcerptsRequested {
17566 excerpt_anchors: vec![excerpt_anchor],
17567 lines: lines_to_expand,
17568 direction,
17569 });
17570 return;
17571 }
17572
17573 let current_scroll_position = self.scroll_position(cx);
17574 let mut scroll = None;
17575
17576 if direction == ExpandExcerptDirection::Down {
17577 let multi_buffer = self.buffer.read(cx);
17578 let snapshot = multi_buffer.snapshot(cx);
17579 if let Some((buffer_snapshot, excerpt_range)) =
17580 snapshot.excerpt_containing(excerpt_anchor..excerpt_anchor)
17581 {
17582 let excerpt_end_row =
17583 Point::from_anchor(&excerpt_range.context.end, &buffer_snapshot).row;
17584 let last_row = buffer_snapshot.max_point().row;
17585 let lines_below = last_row.saturating_sub(excerpt_end_row);
17586 if lines_below >= lines_to_expand {
17587 scroll = Some(
17588 current_scroll_position
17589 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
17590 );
17591 }
17592 }
17593 }
17594 if direction == ExpandExcerptDirection::Up
17595 && self
17596 .buffer
17597 .read(cx)
17598 .snapshot(cx)
17599 .excerpt_before(excerpt_anchor)
17600 .is_none()
17601 {
17602 scroll = Some(current_scroll_position);
17603 }
17604
17605 self.buffer.update(cx, |buffer, cx| {
17606 buffer.expand_excerpts([excerpt_anchor], lines_to_expand, direction, cx)
17607 });
17608
17609 if let Some(new_scroll_position) = scroll {
17610 self.set_scroll_position(new_scroll_position, window, cx);
17611 }
17612 }
17613
17614 pub fn go_to_singleton_buffer_point(
17615 &mut self,
17616 point: Point,
17617 window: &mut Window,
17618 cx: &mut Context<Self>,
17619 ) {
17620 self.go_to_singleton_buffer_range(point..point, window, cx);
17621 }
17622
17623 pub fn go_to_singleton_buffer_range(
17624 &mut self,
17625 range: Range<Point>,
17626 window: &mut Window,
17627 cx: &mut Context<Self>,
17628 ) {
17629 let multibuffer = self.buffer().read(cx);
17630 if !multibuffer.is_singleton() {
17631 return;
17632 };
17633 let anchor_range = range.to_anchors(&multibuffer.snapshot(cx));
17634 self.change_selections(
17635 SelectionEffects::default().nav_history(true),
17636 window,
17637 cx,
17638 |s| s.select_anchor_ranges([anchor_range]),
17639 );
17640 }
17641
17642 pub fn go_to_diagnostic(
17643 &mut self,
17644 action: &GoToDiagnostic,
17645 window: &mut Window,
17646 cx: &mut Context<Self>,
17647 ) {
17648 if !self.diagnostics_enabled() {
17649 return;
17650 }
17651 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17652 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
17653 }
17654
17655 pub fn go_to_prev_diagnostic(
17656 &mut self,
17657 action: &GoToPreviousDiagnostic,
17658 window: &mut Window,
17659 cx: &mut Context<Self>,
17660 ) {
17661 if !self.diagnostics_enabled() {
17662 return;
17663 }
17664 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17665 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
17666 }
17667
17668 pub fn go_to_diagnostic_impl(
17669 &mut self,
17670 direction: Direction,
17671 severity: GoToDiagnosticSeverityFilter,
17672 window: &mut Window,
17673 cx: &mut Context<Self>,
17674 ) {
17675 let buffer = self.buffer.read(cx).snapshot(cx);
17676 let selection = self
17677 .selections
17678 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
17679
17680 let mut active_group_id = None;
17681 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
17682 && active_group.active_range.start.to_offset(&buffer) == selection.start
17683 {
17684 active_group_id = Some(active_group.group_id);
17685 }
17686
17687 fn filtered<'a>(
17688 severity: GoToDiagnosticSeverityFilter,
17689 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
17690 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
17691 diagnostics
17692 .filter(move |entry| severity.matches(entry.diagnostic.severity))
17693 .filter(|entry| entry.range.start != entry.range.end)
17694 .filter(|entry| !entry.diagnostic.is_unnecessary)
17695 }
17696
17697 let before = filtered(
17698 severity,
17699 buffer
17700 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
17701 .filter(|entry| entry.range.start <= selection.start),
17702 );
17703 let after = filtered(
17704 severity,
17705 buffer
17706 .diagnostics_in_range(selection.start..buffer.len())
17707 .filter(|entry| entry.range.start >= selection.start),
17708 );
17709
17710 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
17711 if direction == Direction::Prev {
17712 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
17713 {
17714 for diagnostic in prev_diagnostics.into_iter().rev() {
17715 if diagnostic.range.start != selection.start
17716 || active_group_id
17717 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
17718 {
17719 found = Some(diagnostic);
17720 break 'outer;
17721 }
17722 }
17723 }
17724 } else {
17725 for diagnostic in after.chain(before) {
17726 if diagnostic.range.start != selection.start
17727 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
17728 {
17729 found = Some(diagnostic);
17730 break;
17731 }
17732 }
17733 }
17734 let Some(next_diagnostic) = found else {
17735 return;
17736 };
17737
17738 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
17739 let Some((buffer_anchor, _)) = buffer.anchor_to_buffer_anchor(next_diagnostic_start) else {
17740 return;
17741 };
17742 let buffer_id = buffer_anchor.buffer_id;
17743 let snapshot = self.snapshot(window, cx);
17744 if snapshot.intersects_fold(next_diagnostic.range.start) {
17745 self.unfold_ranges(
17746 std::slice::from_ref(&next_diagnostic.range),
17747 true,
17748 false,
17749 cx,
17750 );
17751 }
17752 self.change_selections(Default::default(), window, cx, |s| {
17753 s.select_ranges(vec![
17754 next_diagnostic.range.start..next_diagnostic.range.start,
17755 ])
17756 });
17757 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
17758 self.refresh_edit_prediction(false, true, window, cx);
17759 }
17760
17761 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
17762 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17763 let snapshot = self.snapshot(window, cx);
17764 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
17765 self.go_to_hunk_before_or_after_position(
17766 &snapshot,
17767 selection.head(),
17768 Direction::Next,
17769 true,
17770 window,
17771 cx,
17772 );
17773 }
17774
17775 pub fn go_to_hunk_before_or_after_position(
17776 &mut self,
17777 snapshot: &EditorSnapshot,
17778 position: Point,
17779 direction: Direction,
17780 wrap_around: bool,
17781 window: &mut Window,
17782 cx: &mut Context<Editor>,
17783 ) {
17784 let row = if direction == Direction::Next {
17785 self.hunk_after_position(snapshot, position, wrap_around)
17786 .map(|hunk| hunk.row_range.start)
17787 } else {
17788 self.hunk_before_position(snapshot, position, wrap_around)
17789 };
17790
17791 if let Some(row) = row {
17792 let destination = Point::new(row.0, 0);
17793 let autoscroll = Autoscroll::center();
17794
17795 self.unfold_ranges(&[destination..destination], false, false, cx);
17796 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17797 s.select_ranges([destination..destination]);
17798 });
17799 }
17800 }
17801
17802 fn hunk_after_position(
17803 &mut self,
17804 snapshot: &EditorSnapshot,
17805 position: Point,
17806 wrap_around: bool,
17807 ) -> Option<MultiBufferDiffHunk> {
17808 let result = snapshot
17809 .buffer_snapshot()
17810 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
17811 .find(|hunk| hunk.row_range.start.0 > position.row);
17812
17813 if wrap_around {
17814 result.or_else(|| {
17815 snapshot
17816 .buffer_snapshot()
17817 .diff_hunks_in_range(Point::zero()..position)
17818 .find(|hunk| hunk.row_range.end.0 < position.row)
17819 })
17820 } else {
17821 result
17822 }
17823 }
17824
17825 fn go_to_prev_hunk(
17826 &mut self,
17827 _: &GoToPreviousHunk,
17828 window: &mut Window,
17829 cx: &mut Context<Self>,
17830 ) {
17831 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17832 let snapshot = self.snapshot(window, cx);
17833 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
17834 self.go_to_hunk_before_or_after_position(
17835 &snapshot,
17836 selection.head(),
17837 Direction::Prev,
17838 true,
17839 window,
17840 cx,
17841 );
17842 }
17843
17844 fn hunk_before_position(
17845 &mut self,
17846 snapshot: &EditorSnapshot,
17847 position: Point,
17848 wrap_around: bool,
17849 ) -> Option<MultiBufferRow> {
17850 let result = snapshot.buffer_snapshot().diff_hunk_before(position);
17851
17852 if wrap_around {
17853 result.or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
17854 } else {
17855 result
17856 }
17857 }
17858
17859 fn go_to_next_change(
17860 &mut self,
17861 _: &GoToNextChange,
17862 window: &mut Window,
17863 cx: &mut Context<Self>,
17864 ) {
17865 if let Some(selections) = self
17866 .change_list
17867 .next_change(1, Direction::Next)
17868 .map(|s| s.to_vec())
17869 {
17870 self.change_selections(Default::default(), window, cx, |s| {
17871 let map = s.display_snapshot();
17872 s.select_display_ranges(selections.iter().map(|a| {
17873 let point = a.to_display_point(&map);
17874 point..point
17875 }))
17876 })
17877 }
17878 }
17879
17880 fn go_to_previous_change(
17881 &mut self,
17882 _: &GoToPreviousChange,
17883 window: &mut Window,
17884 cx: &mut Context<Self>,
17885 ) {
17886 if let Some(selections) = self
17887 .change_list
17888 .next_change(1, Direction::Prev)
17889 .map(|s| s.to_vec())
17890 {
17891 self.change_selections(Default::default(), window, cx, |s| {
17892 let map = s.display_snapshot();
17893 s.select_display_ranges(selections.iter().map(|a| {
17894 let point = a.to_display_point(&map);
17895 point..point
17896 }))
17897 })
17898 }
17899 }
17900
17901 pub fn go_to_next_document_highlight(
17902 &mut self,
17903 _: &GoToNextDocumentHighlight,
17904 window: &mut Window,
17905 cx: &mut Context<Self>,
17906 ) {
17907 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
17908 }
17909
17910 pub fn go_to_prev_document_highlight(
17911 &mut self,
17912 _: &GoToPreviousDocumentHighlight,
17913 window: &mut Window,
17914 cx: &mut Context<Self>,
17915 ) {
17916 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
17917 }
17918
17919 pub fn go_to_document_highlight_before_or_after_position(
17920 &mut self,
17921 direction: Direction,
17922 window: &mut Window,
17923 cx: &mut Context<Editor>,
17924 ) {
17925 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17926 let snapshot = self.snapshot(window, cx);
17927 let buffer = &snapshot.buffer_snapshot();
17928 let position = self
17929 .selections
17930 .newest::<Point>(&snapshot.display_snapshot)
17931 .head();
17932 let anchor_position = buffer.anchor_after(position);
17933
17934 // Get all document highlights (both read and write)
17935 let mut all_highlights = Vec::new();
17936
17937 if let Some((_, read_highlights)) = self
17938 .background_highlights
17939 .get(&HighlightKey::DocumentHighlightRead)
17940 {
17941 all_highlights.extend(read_highlights.iter());
17942 }
17943
17944 if let Some((_, write_highlights)) = self
17945 .background_highlights
17946 .get(&HighlightKey::DocumentHighlightWrite)
17947 {
17948 all_highlights.extend(write_highlights.iter());
17949 }
17950
17951 if all_highlights.is_empty() {
17952 return;
17953 }
17954
17955 // Sort highlights by position
17956 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
17957
17958 let target_highlight = match direction {
17959 Direction::Next => {
17960 // Find the first highlight after the current position
17961 all_highlights
17962 .iter()
17963 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
17964 }
17965 Direction::Prev => {
17966 // Find the last highlight before the current position
17967 all_highlights
17968 .iter()
17969 .rev()
17970 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
17971 }
17972 };
17973
17974 if let Some(highlight) = target_highlight {
17975 let destination = highlight.start.to_point(buffer);
17976 let autoscroll = Autoscroll::center();
17977
17978 self.unfold_ranges(&[destination..destination], false, false, cx);
17979 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17980 s.select_ranges([destination..destination]);
17981 });
17982 }
17983 }
17984
17985 fn go_to_line<T: 'static>(
17986 &mut self,
17987 position: Anchor,
17988 highlight_color: Option<Hsla>,
17989 window: &mut Window,
17990 cx: &mut Context<Self>,
17991 ) {
17992 let snapshot = self.snapshot(window, cx).display_snapshot;
17993 let position = position.to_point(&snapshot.buffer_snapshot());
17994 let start = snapshot
17995 .buffer_snapshot()
17996 .clip_point(Point::new(position.row, 0), Bias::Left);
17997 let end = start + Point::new(1, 0);
17998 let start = snapshot.buffer_snapshot().anchor_before(start);
17999 let end = snapshot.buffer_snapshot().anchor_before(end);
18000
18001 self.highlight_rows::<T>(
18002 start..end,
18003 highlight_color
18004 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
18005 Default::default(),
18006 cx,
18007 );
18008
18009 if self.buffer.read(cx).is_singleton() {
18010 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
18011 }
18012 }
18013
18014 pub fn go_to_definition(
18015 &mut self,
18016 _: &GoToDefinition,
18017 window: &mut Window,
18018 cx: &mut Context<Self>,
18019 ) -> Task<Result<Navigated>> {
18020 let definition =
18021 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
18022 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
18023 cx.spawn_in(window, async move |editor, cx| {
18024 if definition.await? == Navigated::Yes {
18025 return Ok(Navigated::Yes);
18026 }
18027 match fallback_strategy {
18028 GoToDefinitionFallback::None => Ok(Navigated::No),
18029 GoToDefinitionFallback::FindAllReferences => {
18030 match editor.update_in(cx, |editor, window, cx| {
18031 editor.find_all_references(&FindAllReferences::default(), window, cx)
18032 })? {
18033 Some(references) => references.await,
18034 None => Ok(Navigated::No),
18035 }
18036 }
18037 }
18038 })
18039 }
18040
18041 pub fn go_to_declaration(
18042 &mut self,
18043 _: &GoToDeclaration,
18044 window: &mut Window,
18045 cx: &mut Context<Self>,
18046 ) -> Task<Result<Navigated>> {
18047 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
18048 }
18049
18050 pub fn go_to_declaration_split(
18051 &mut self,
18052 _: &GoToDeclaration,
18053 window: &mut Window,
18054 cx: &mut Context<Self>,
18055 ) -> Task<Result<Navigated>> {
18056 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
18057 }
18058
18059 pub fn go_to_implementation(
18060 &mut self,
18061 _: &GoToImplementation,
18062 window: &mut Window,
18063 cx: &mut Context<Self>,
18064 ) -> Task<Result<Navigated>> {
18065 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
18066 }
18067
18068 pub fn go_to_implementation_split(
18069 &mut self,
18070 _: &GoToImplementationSplit,
18071 window: &mut Window,
18072 cx: &mut Context<Self>,
18073 ) -> Task<Result<Navigated>> {
18074 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
18075 }
18076
18077 pub fn go_to_type_definition(
18078 &mut self,
18079 _: &GoToTypeDefinition,
18080 window: &mut Window,
18081 cx: &mut Context<Self>,
18082 ) -> Task<Result<Navigated>> {
18083 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
18084 }
18085
18086 pub fn go_to_definition_split(
18087 &mut self,
18088 _: &GoToDefinitionSplit,
18089 window: &mut Window,
18090 cx: &mut Context<Self>,
18091 ) -> Task<Result<Navigated>> {
18092 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
18093 }
18094
18095 pub fn go_to_type_definition_split(
18096 &mut self,
18097 _: &GoToTypeDefinitionSplit,
18098 window: &mut Window,
18099 cx: &mut Context<Self>,
18100 ) -> Task<Result<Navigated>> {
18101 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
18102 }
18103
18104 fn go_to_definition_of_kind(
18105 &mut self,
18106 kind: GotoDefinitionKind,
18107 split: bool,
18108 window: &mut Window,
18109 cx: &mut Context<Self>,
18110 ) -> Task<Result<Navigated>> {
18111 let Some(provider) = self.semantics_provider.clone() else {
18112 return Task::ready(Ok(Navigated::No));
18113 };
18114 let head = self
18115 .selections
18116 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
18117 .head();
18118 let buffer = self.buffer.read(cx);
18119 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
18120 return Task::ready(Ok(Navigated::No));
18121 };
18122 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
18123 return Task::ready(Ok(Navigated::No));
18124 };
18125
18126 let nav_entry = self.navigation_entry(self.selections.newest_anchor().head(), cx);
18127
18128 cx.spawn_in(window, async move |editor, cx| {
18129 let Some(definitions) = definitions.await? else {
18130 return Ok(Navigated::No);
18131 };
18132 let navigated = editor
18133 .update_in(cx, |editor, window, cx| {
18134 editor.navigate_to_hover_links(
18135 Some(kind),
18136 definitions
18137 .into_iter()
18138 .filter(|location| {
18139 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
18140 })
18141 .map(HoverLink::Text)
18142 .collect::<Vec<_>>(),
18143 nav_entry,
18144 split,
18145 window,
18146 cx,
18147 )
18148 })?
18149 .await?;
18150 anyhow::Ok(navigated)
18151 })
18152 }
18153
18154 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
18155 let selection = self.selections.newest_anchor();
18156 let head = selection.head();
18157 let tail = selection.tail();
18158
18159 let Some((buffer, start_position)) =
18160 self.buffer.read(cx).text_anchor_for_position(head, cx)
18161 else {
18162 return;
18163 };
18164
18165 let end_position = if head != tail {
18166 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
18167 return;
18168 };
18169 Some(pos)
18170 } else {
18171 None
18172 };
18173
18174 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
18175 let url = if let Some(end_pos) = end_position {
18176 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
18177 } else {
18178 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
18179 };
18180
18181 if let Some(url) = url {
18182 cx.update(|window, cx| {
18183 if parse_zed_link(&url, cx).is_some() {
18184 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
18185 } else {
18186 cx.open_url(&url);
18187 }
18188 })?;
18189 }
18190
18191 anyhow::Ok(())
18192 });
18193
18194 url_finder.detach();
18195 }
18196
18197 pub fn open_selected_filename(
18198 &mut self,
18199 _: &OpenSelectedFilename,
18200 window: &mut Window,
18201 cx: &mut Context<Self>,
18202 ) {
18203 let Some(workspace) = self.workspace() else {
18204 return;
18205 };
18206
18207 let position = self.selections.newest_anchor().head();
18208
18209 let Some((buffer, buffer_position)) =
18210 self.buffer.read(cx).text_anchor_for_position(position, cx)
18211 else {
18212 return;
18213 };
18214
18215 let project = self.project.clone();
18216
18217 cx.spawn_in(window, async move |_, cx| {
18218 let result = find_file(&buffer, project, buffer_position, cx).await;
18219
18220 if let Some((_, path)) = result {
18221 workspace
18222 .update_in(cx, |workspace, window, cx| {
18223 workspace.open_resolved_path(path, window, cx)
18224 })?
18225 .await?;
18226 }
18227 anyhow::Ok(())
18228 })
18229 .detach();
18230 }
18231
18232 pub(crate) fn navigate_to_hover_links(
18233 &mut self,
18234 kind: Option<GotoDefinitionKind>,
18235 definitions: Vec<HoverLink>,
18236 origin: Option<NavigationEntry>,
18237 split: bool,
18238 window: &mut Window,
18239 cx: &mut Context<Editor>,
18240 ) -> Task<Result<Navigated>> {
18241 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
18242 let mut first_url_or_file = None;
18243 let definitions: Vec<_> = definitions
18244 .into_iter()
18245 .filter_map(|def| match def {
18246 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
18247 HoverLink::InlayHint(lsp_location, server_id) => {
18248 let computation =
18249 self.compute_target_location(lsp_location, server_id, window, cx);
18250 Some(cx.background_spawn(computation))
18251 }
18252 HoverLink::Url(url) => {
18253 first_url_or_file = Some(Either::Left(url));
18254 None
18255 }
18256 HoverLink::File(path) => {
18257 first_url_or_file = Some(Either::Right(path));
18258 None
18259 }
18260 })
18261 .collect();
18262
18263 let workspace = self.workspace();
18264
18265 let excerpt_context_lines = multi_buffer::excerpt_context_lines(cx);
18266 cx.spawn_in(window, async move |editor, cx| {
18267 let locations: Vec<Location> = future::join_all(definitions)
18268 .await
18269 .into_iter()
18270 .filter_map(|location| location.transpose())
18271 .collect::<Result<_>>()
18272 .context("location tasks")?;
18273 let mut locations = cx.update(|_, cx| {
18274 locations
18275 .into_iter()
18276 .map(|location| {
18277 let buffer = location.buffer.read(cx);
18278 (location.buffer, location.range.to_point(buffer))
18279 })
18280 .into_group_map()
18281 })?;
18282 let mut num_locations = 0;
18283 for ranges in locations.values_mut() {
18284 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18285 ranges.dedup();
18286 // Merge overlapping or contained ranges. After sorting by
18287 // (start, Reverse(end)), we can merge in a single pass:
18288 // if the next range starts before the current one ends,
18289 // extend the current range's end if needed.
18290 let mut i = 0;
18291 while i + 1 < ranges.len() {
18292 if ranges[i + 1].start <= ranges[i].end {
18293 let merged_end = ranges[i].end.max(ranges[i + 1].end);
18294 ranges[i].end = merged_end;
18295 ranges.remove(i + 1);
18296 } else {
18297 i += 1;
18298 }
18299 }
18300 let fits_in_one_excerpt = ranges
18301 .iter()
18302 .tuple_windows()
18303 .all(|(a, b)| b.start.row - a.end.row <= 2 * excerpt_context_lines);
18304 num_locations += if fits_in_one_excerpt { 1 } else { ranges.len() };
18305 }
18306
18307 if num_locations > 1 {
18308 let tab_kind = match kind {
18309 Some(GotoDefinitionKind::Implementation) => "Implementations",
18310 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
18311 Some(GotoDefinitionKind::Declaration) => "Declarations",
18312 Some(GotoDefinitionKind::Type) => "Types",
18313 };
18314 let title = editor
18315 .update_in(cx, |_, _, cx| {
18316 let target = locations
18317 .iter()
18318 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18319 .map(|(buffer, location)| {
18320 buffer
18321 .read(cx)
18322 .text_for_range(location.clone())
18323 .collect::<String>()
18324 })
18325 .filter(|text| !text.contains('\n'))
18326 .unique()
18327 .take(3)
18328 .join(", ");
18329 if target.is_empty() {
18330 tab_kind.to_owned()
18331 } else {
18332 format!("{tab_kind} for {target}")
18333 }
18334 })
18335 .context("buffer title")?;
18336
18337 let Some(workspace) = workspace else {
18338 return Ok(Navigated::No);
18339 };
18340
18341 let opened = workspace
18342 .update_in(cx, |workspace, window, cx| {
18343 let allow_preview = PreviewTabsSettings::get_global(cx)
18344 .enable_preview_multibuffer_from_code_navigation;
18345 if let Some((target_editor, target_pane)) =
18346 Self::open_locations_in_multibuffer(
18347 workspace,
18348 locations,
18349 title,
18350 split,
18351 allow_preview,
18352 MultibufferSelectionMode::First,
18353 window,
18354 cx,
18355 )
18356 {
18357 // We create our own nav history instead of using
18358 // `target_editor.nav_history` because `nav_history`
18359 // seems to be populated asynchronously when an item
18360 // is added to a pane
18361 let mut nav_history = target_pane
18362 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18363 target_editor.update(cx, |editor, cx| {
18364 let nav_data = editor
18365 .navigation_data(editor.selections.newest_anchor().head(), cx);
18366 let target =
18367 Some(nav_history.navigation_entry(Some(
18368 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18369 )));
18370 nav_history.push_tag(origin, target);
18371 })
18372 }
18373 })
18374 .is_ok();
18375
18376 anyhow::Ok(Navigated::from_bool(opened))
18377 } else if num_locations == 0 {
18378 // If there is one url or file, open it directly
18379 match first_url_or_file {
18380 Some(Either::Left(url)) => {
18381 cx.update(|window, cx| {
18382 if parse_zed_link(&url, cx).is_some() {
18383 window
18384 .dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
18385 } else {
18386 cx.open_url(&url);
18387 }
18388 })?;
18389 Ok(Navigated::Yes)
18390 }
18391 Some(Either::Right(path)) => {
18392 // TODO(andrew): respect preview tab settings
18393 // `enable_keep_preview_on_code_navigation` and
18394 // `enable_preview_file_from_code_navigation`
18395 let Some(workspace) = workspace else {
18396 return Ok(Navigated::No);
18397 };
18398 workspace
18399 .update_in(cx, |workspace, window, cx| {
18400 workspace.open_resolved_path(path, window, cx)
18401 })?
18402 .await?;
18403 Ok(Navigated::Yes)
18404 }
18405 None => Ok(Navigated::No),
18406 }
18407 } else {
18408 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18409
18410 editor.update_in(cx, |editor, window, cx| {
18411 let target_ranges = target_ranges
18412 .into_iter()
18413 .map(|r| editor.range_for_match(&r))
18414 .map(collapse_multiline_range)
18415 .collect::<Vec<_>>();
18416 if !split
18417 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
18418 {
18419 let multibuffer = editor.buffer.read(cx);
18420 let target_ranges = target_ranges
18421 .into_iter()
18422 .filter_map(|r| {
18423 let start = multibuffer.buffer_point_to_anchor(
18424 &target_buffer,
18425 r.start,
18426 cx,
18427 )?;
18428 let end = multibuffer.buffer_point_to_anchor(
18429 &target_buffer,
18430 r.end,
18431 cx,
18432 )?;
18433 Some(start..end)
18434 })
18435 .collect::<Vec<_>>();
18436 if target_ranges.is_empty() {
18437 return Navigated::No;
18438 }
18439
18440 editor.change_selections(
18441 SelectionEffects::default().nav_history(true),
18442 window,
18443 cx,
18444 |s| s.select_anchor_ranges(target_ranges),
18445 );
18446
18447 let target =
18448 editor.navigation_entry(editor.selections.newest_anchor().head(), cx);
18449 if let Some(mut nav_history) = editor.nav_history.clone() {
18450 nav_history.push_tag(origin, target);
18451 }
18452 } else {
18453 let Some(workspace) = workspace else {
18454 return Navigated::No;
18455 };
18456 let pane = workspace.read(cx).active_pane().clone();
18457 window.defer(cx, move |window, cx| {
18458 let (target_editor, target_pane): (Entity<Self>, Entity<Pane>) =
18459 workspace.update(cx, |workspace, cx| {
18460 let pane = if split {
18461 workspace.adjacent_pane(window, cx)
18462 } else {
18463 workspace.active_pane().clone()
18464 };
18465
18466 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18467 let keep_old_preview = preview_tabs_settings
18468 .enable_keep_preview_on_code_navigation;
18469 let allow_new_preview = preview_tabs_settings
18470 .enable_preview_file_from_code_navigation;
18471
18472 let editor = workspace.open_project_item(
18473 pane.clone(),
18474 target_buffer.clone(),
18475 true,
18476 true,
18477 keep_old_preview,
18478 allow_new_preview,
18479 window,
18480 cx,
18481 );
18482 (editor, pane)
18483 });
18484 // We create our own nav history instead of using
18485 // `target_editor.nav_history` because `nav_history`
18486 // seems to be populated asynchronously when an item
18487 // is added to a pane
18488 let mut nav_history = target_pane
18489 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18490 target_editor.update(cx, |target_editor, cx| {
18491 // When selecting a definition in a different buffer, disable the nav history
18492 // to avoid creating a history entry at the previous cursor location.
18493 pane.update(cx, |pane, _| pane.disable_history());
18494
18495 let multibuffer = target_editor.buffer.read(cx);
18496 let Some(target_buffer) = multibuffer.as_singleton() else {
18497 return Navigated::No;
18498 };
18499 let target_ranges = target_ranges
18500 .into_iter()
18501 .filter_map(|r| {
18502 let start = multibuffer.buffer_point_to_anchor(
18503 &target_buffer,
18504 r.start,
18505 cx,
18506 )?;
18507 let end = multibuffer.buffer_point_to_anchor(
18508 &target_buffer,
18509 r.end,
18510 cx,
18511 )?;
18512 Some(start..end)
18513 })
18514 .collect::<Vec<_>>();
18515 if target_ranges.is_empty() {
18516 return Navigated::No;
18517 }
18518
18519 target_editor.change_selections(
18520 SelectionEffects::default().nav_history(true),
18521 window,
18522 cx,
18523 |s| s.select_anchor_ranges(target_ranges),
18524 );
18525
18526 let nav_data = target_editor.navigation_data(
18527 target_editor.selections.newest_anchor().head(),
18528 cx,
18529 );
18530 let target =
18531 Some(nav_history.navigation_entry(Some(
18532 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18533 )));
18534 nav_history.push_tag(origin, target);
18535 pane.update(cx, |pane, _| pane.enable_history());
18536 Navigated::Yes
18537 });
18538 });
18539 }
18540 Navigated::Yes
18541 })
18542 }
18543 })
18544 }
18545
18546 fn compute_target_location(
18547 &self,
18548 lsp_location: lsp::Location,
18549 server_id: LanguageServerId,
18550 window: &mut Window,
18551 cx: &mut Context<Self>,
18552 ) -> Task<anyhow::Result<Option<Location>>> {
18553 let Some(project) = self.project.clone() else {
18554 return Task::ready(Ok(None));
18555 };
18556
18557 cx.spawn_in(window, async move |editor, cx| {
18558 let location_task = editor.update(cx, |_, cx| {
18559 project.update(cx, |project, cx| {
18560 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
18561 })
18562 })?;
18563 let location = Some({
18564 let target_buffer_handle = location_task.await.context("open local buffer")?;
18565 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
18566 let target_start = target_buffer
18567 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
18568 let target_end = target_buffer
18569 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
18570 target_buffer.anchor_after(target_start)
18571 ..target_buffer.anchor_before(target_end)
18572 });
18573 Location {
18574 buffer: target_buffer_handle,
18575 range,
18576 }
18577 });
18578 Ok(location)
18579 })
18580 }
18581
18582 fn go_to_next_reference(
18583 &mut self,
18584 _: &GoToNextReference,
18585 window: &mut Window,
18586 cx: &mut Context<Self>,
18587 ) {
18588 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
18589 if let Some(task) = task {
18590 task.detach();
18591 };
18592 }
18593
18594 fn go_to_prev_reference(
18595 &mut self,
18596 _: &GoToPreviousReference,
18597 window: &mut Window,
18598 cx: &mut Context<Self>,
18599 ) {
18600 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
18601 if let Some(task) = task {
18602 task.detach();
18603 };
18604 }
18605
18606 fn go_to_symbol_by_offset(
18607 &mut self,
18608 window: &mut Window,
18609 cx: &mut Context<Self>,
18610 offset: i8,
18611 ) -> Task<Result<()>> {
18612 let editor_snapshot = self.snapshot(window, cx);
18613
18614 // We don't care about multi-buffer symbols
18615 if !editor_snapshot.is_singleton() {
18616 return Task::ready(Ok(()));
18617 }
18618
18619 let cursor_offset = self
18620 .selections
18621 .newest::<MultiBufferOffset>(&editor_snapshot.display_snapshot)
18622 .head();
18623
18624 cx.spawn_in(window, async move |editor, wcx| -> Result<()> {
18625 let Ok(Some(remote_id)) = editor.update(wcx, |ed, cx| {
18626 let buffer = ed.buffer.read(cx).as_singleton()?;
18627 Some(buffer.read(cx).remote_id())
18628 }) else {
18629 return Ok(());
18630 };
18631
18632 let task = editor.update(wcx, |ed, cx| ed.buffer_outline_items(remote_id, cx))?;
18633 let outline_items: Vec<OutlineItem<text::Anchor>> = task.await;
18634
18635 let multi_snapshot = editor_snapshot.buffer();
18636 let buffer_range = |range: &Range<_>| {
18637 Some(
18638 multi_snapshot
18639 .buffer_anchor_range_to_anchor_range(range.clone())?
18640 .to_offset(multi_snapshot),
18641 )
18642 };
18643
18644 wcx.update_window(wcx.window_handle(), |_, window, acx| {
18645 let current_idx = outline_items
18646 .iter()
18647 .enumerate()
18648 .filter_map(|(idx, item)| {
18649 // Find the closest outline item by distance between outline text and cursor location
18650 let source_range = buffer_range(&item.source_range_for_text)?;
18651 let distance_to_closest_endpoint = cmp::min(
18652 (source_range.start.0 as isize - cursor_offset.0 as isize).abs(),
18653 (source_range.end.0 as isize - cursor_offset.0 as isize).abs(),
18654 );
18655
18656 let item_towards_offset =
18657 (source_range.start.0 as isize - cursor_offset.0 as isize).signum()
18658 == (offset as isize).signum();
18659
18660 let source_range_contains_cursor = source_range.contains(&cursor_offset);
18661
18662 // To pick the next outline to jump to, we should jump in the direction of the offset, and
18663 // we should not already be within the outline's source range. We then pick the closest outline
18664 // item.
18665 (item_towards_offset && !source_range_contains_cursor)
18666 .then_some((distance_to_closest_endpoint, idx))
18667 })
18668 .min()
18669 .map(|(_, idx)| idx);
18670
18671 let Some(idx) = current_idx else {
18672 return;
18673 };
18674
18675 let Some(range) = buffer_range(&outline_items[idx].source_range_for_text) else {
18676 return;
18677 };
18678 let selection = [range.start..range.start];
18679
18680 let _ = editor
18681 .update(acx, |editor, ecx| {
18682 editor.change_selections(
18683 SelectionEffects::scroll(Autoscroll::newest()),
18684 window,
18685 ecx,
18686 |s| s.select_ranges(selection),
18687 );
18688 })
18689 .ok();
18690 })?;
18691
18692 Ok(())
18693 })
18694 }
18695
18696 fn go_to_next_symbol(
18697 &mut self,
18698 _: &GoToNextSymbol,
18699 window: &mut Window,
18700 cx: &mut Context<Self>,
18701 ) {
18702 self.go_to_symbol_by_offset(window, cx, 1).detach();
18703 }
18704
18705 fn go_to_previous_symbol(
18706 &mut self,
18707 _: &GoToPreviousSymbol,
18708 window: &mut Window,
18709 cx: &mut Context<Self>,
18710 ) {
18711 self.go_to_symbol_by_offset(window, cx, -1).detach();
18712 }
18713
18714 pub fn go_to_reference_before_or_after_position(
18715 &mut self,
18716 direction: Direction,
18717 count: usize,
18718 window: &mut Window,
18719 cx: &mut Context<Self>,
18720 ) -> Option<Task<Result<()>>> {
18721 let selection = self.selections.newest_anchor();
18722 let head = selection.head();
18723
18724 let multi_buffer = self.buffer.read(cx);
18725
18726 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
18727 let workspace = self.workspace()?;
18728 let project = workspace.read(cx).project().clone();
18729 let references =
18730 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
18731 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
18732 let Some(locations) = references.await? else {
18733 return Ok(());
18734 };
18735
18736 if locations.is_empty() {
18737 // totally normal - the cursor may be on something which is not
18738 // a symbol (e.g. a keyword)
18739 log::info!("no references found under cursor");
18740 return Ok(());
18741 }
18742
18743 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
18744
18745 let (locations, current_location_index) =
18746 multi_buffer.update(cx, |multi_buffer, cx| {
18747 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18748 let mut locations = locations
18749 .into_iter()
18750 .filter_map(|loc| {
18751 let start = multi_buffer_snapshot.anchor_in_excerpt(loc.range.start)?;
18752 let end = multi_buffer_snapshot.anchor_in_excerpt(loc.range.end)?;
18753 Some(start..end)
18754 })
18755 .collect::<Vec<_>>();
18756 // There is an O(n) implementation, but given this list will be
18757 // small (usually <100 items), the extra O(log(n)) factor isn't
18758 // worth the (surprisingly large amount of) extra complexity.
18759 locations
18760 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
18761
18762 let head_offset = head.to_offset(&multi_buffer_snapshot);
18763
18764 let current_location_index = locations.iter().position(|loc| {
18765 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
18766 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
18767 });
18768
18769 (locations, current_location_index)
18770 });
18771
18772 let Some(current_location_index) = current_location_index else {
18773 // This indicates something has gone wrong, because we already
18774 // handle the "no references" case above
18775 log::error!(
18776 "failed to find current reference under cursor. Total references: {}",
18777 locations.len()
18778 );
18779 return Ok(());
18780 };
18781
18782 let destination_location_index = match direction {
18783 Direction::Next => (current_location_index + count) % locations.len(),
18784 Direction::Prev => {
18785 (current_location_index + locations.len() - count % locations.len())
18786 % locations.len()
18787 }
18788 };
18789
18790 // TODO(cameron): is this needed?
18791 // the thinking is to avoid "jumping to the current location" (avoid
18792 // polluting "jumplist" in vim terms)
18793 if current_location_index == destination_location_index {
18794 return Ok(());
18795 }
18796
18797 let Range { start, end } = locations[destination_location_index];
18798
18799 editor.update_in(cx, |editor, window, cx| {
18800 let effects = SelectionEffects::default();
18801
18802 editor.unfold_ranges(&[start..end], false, false, cx);
18803 editor.change_selections(effects, window, cx, |s| {
18804 s.select_ranges([start..start]);
18805 });
18806 })?;
18807
18808 Ok(())
18809 }))
18810 }
18811
18812 pub fn find_all_references(
18813 &mut self,
18814 action: &FindAllReferences,
18815 window: &mut Window,
18816 cx: &mut Context<Self>,
18817 ) -> Option<Task<Result<Navigated>>> {
18818 let always_open_multibuffer = action.always_open_multibuffer;
18819 let selection = self.selections.newest_anchor();
18820 let multi_buffer = self.buffer.read(cx);
18821 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18822 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
18823 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
18824 let head = selection_offset.head();
18825
18826 let head_anchor = multi_buffer_snapshot.anchor_at(
18827 head,
18828 if head < selection_offset.tail() {
18829 Bias::Right
18830 } else {
18831 Bias::Left
18832 },
18833 );
18834
18835 match self
18836 .find_all_references_task_sources
18837 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18838 {
18839 Ok(_) => {
18840 log::info!(
18841 "Ignoring repeated FindAllReferences invocation with the position of already running task"
18842 );
18843 return None;
18844 }
18845 Err(i) => {
18846 self.find_all_references_task_sources.insert(i, head_anchor);
18847 }
18848 }
18849
18850 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
18851 let workspace = self.workspace()?;
18852 let project = workspace.read(cx).project().clone();
18853 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
18854 Some(cx.spawn_in(window, async move |editor, cx| {
18855 let _cleanup = cx.on_drop(&editor, move |editor, _| {
18856 if let Ok(i) = editor
18857 .find_all_references_task_sources
18858 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18859 {
18860 editor.find_all_references_task_sources.remove(i);
18861 }
18862 });
18863
18864 let Some(locations) = references.await? else {
18865 return anyhow::Ok(Navigated::No);
18866 };
18867 let mut locations = cx.update(|_, cx| {
18868 locations
18869 .into_iter()
18870 .map(|location| {
18871 let buffer = location.buffer.read(cx);
18872 (location.buffer, location.range.to_point(buffer))
18873 })
18874 // if special-casing the single-match case, remove ranges
18875 // that intersect current selection
18876 .filter(|(location_buffer, location)| {
18877 if always_open_multibuffer || &buffer != location_buffer {
18878 return true;
18879 }
18880
18881 !location.contains_inclusive(&selection_point.range())
18882 })
18883 .into_group_map()
18884 })?;
18885 if locations.is_empty() {
18886 return anyhow::Ok(Navigated::No);
18887 }
18888 for ranges in locations.values_mut() {
18889 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18890 ranges.dedup();
18891 }
18892 let mut num_locations = 0;
18893 for ranges in locations.values_mut() {
18894 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18895 ranges.dedup();
18896 num_locations += ranges.len();
18897 }
18898
18899 if num_locations == 1 && !always_open_multibuffer {
18900 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18901 let target_range = target_ranges.first().unwrap().clone();
18902
18903 return editor.update_in(cx, |editor, window, cx| {
18904 let range = target_range.to_point(target_buffer.read(cx));
18905 let range = editor.range_for_match(&range);
18906 let range = range.start..range.start;
18907
18908 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
18909 editor.go_to_singleton_buffer_range(range, window, cx);
18910 } else {
18911 let pane = workspace.read(cx).active_pane().clone();
18912 window.defer(cx, move |window, cx| {
18913 let target_editor: Entity<Self> =
18914 workspace.update(cx, |workspace, cx| {
18915 let pane = workspace.active_pane().clone();
18916
18917 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18918 let keep_old_preview = preview_tabs_settings
18919 .enable_keep_preview_on_code_navigation;
18920 let allow_new_preview = preview_tabs_settings
18921 .enable_preview_file_from_code_navigation;
18922
18923 workspace.open_project_item(
18924 pane,
18925 target_buffer.clone(),
18926 true,
18927 true,
18928 keep_old_preview,
18929 allow_new_preview,
18930 window,
18931 cx,
18932 )
18933 });
18934 target_editor.update(cx, |target_editor, cx| {
18935 // When selecting a definition in a different buffer, disable the nav history
18936 // to avoid creating a history entry at the previous cursor location.
18937 pane.update(cx, |pane, _| pane.disable_history());
18938 target_editor.go_to_singleton_buffer_range(range, window, cx);
18939 pane.update(cx, |pane, _| pane.enable_history());
18940 });
18941 });
18942 }
18943 Navigated::No
18944 });
18945 }
18946
18947 workspace.update_in(cx, |workspace, window, cx| {
18948 let target = locations
18949 .iter()
18950 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18951 .map(|(buffer, location)| {
18952 buffer
18953 .read(cx)
18954 .text_for_range(location.clone())
18955 .collect::<String>()
18956 })
18957 .filter(|text| !text.contains('\n'))
18958 .unique()
18959 .take(3)
18960 .join(", ");
18961 let title = if target.is_empty() {
18962 "References".to_owned()
18963 } else {
18964 format!("References to {target}")
18965 };
18966 let allow_preview = PreviewTabsSettings::get_global(cx)
18967 .enable_preview_multibuffer_from_code_navigation;
18968 Self::open_locations_in_multibuffer(
18969 workspace,
18970 locations,
18971 title,
18972 false,
18973 allow_preview,
18974 MultibufferSelectionMode::First,
18975 window,
18976 cx,
18977 );
18978 Navigated::Yes
18979 })
18980 }))
18981 }
18982
18983 /// Opens a multibuffer with the given project locations in it.
18984 pub fn open_locations_in_multibuffer(
18985 workspace: &mut Workspace,
18986 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
18987 title: String,
18988 split: bool,
18989 allow_preview: bool,
18990 multibuffer_selection_mode: MultibufferSelectionMode,
18991 window: &mut Window,
18992 cx: &mut Context<Workspace>,
18993 ) -> Option<(Entity<Editor>, Entity<Pane>)> {
18994 if locations.is_empty() {
18995 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
18996 return None;
18997 }
18998
18999 let capability = workspace.project().read(cx).capability();
19000 let mut ranges = <Vec<Range<Anchor>>>::new();
19001
19002 // a key to find existing multibuffer editors with the same set of locations
19003 // to prevent us from opening more and more multibuffer tabs for searches and the like
19004 let mut key = (title.clone(), vec![]);
19005 let excerpt_buffer = cx.new(|cx| {
19006 let key = &mut key.1;
19007 let mut multibuffer = MultiBuffer::new(capability);
19008 for (buffer, mut ranges_for_buffer) in locations {
19009 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
19010 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
19011 multibuffer.set_excerpts_for_path(
19012 PathKey::for_buffer(&buffer, cx),
19013 buffer.clone(),
19014 ranges_for_buffer.clone(),
19015 multibuffer_context_lines(cx),
19016 cx,
19017 );
19018 let snapshot = multibuffer.snapshot(cx);
19019 let buffer_snapshot = buffer.read(cx).snapshot();
19020 ranges.extend(ranges_for_buffer.into_iter().filter_map(|range| {
19021 let text_range = buffer_snapshot.anchor_range_inside(range);
19022 let start = snapshot.anchor_in_buffer(text_range.start)?;
19023 let end = snapshot.anchor_in_buffer(text_range.end)?;
19024 Some(start..end)
19025 }))
19026 }
19027
19028 multibuffer.with_title(title)
19029 });
19030 let existing = workspace.active_pane().update(cx, |pane, cx| {
19031 pane.items()
19032 .filter_map(|item| item.downcast::<Editor>())
19033 .find(|editor| {
19034 editor
19035 .read(cx)
19036 .lookup_key
19037 .as_ref()
19038 .and_then(|it| {
19039 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
19040 })
19041 .is_some_and(|it| *it == key)
19042 })
19043 });
19044 let was_existing = existing.is_some();
19045 let editor = existing.unwrap_or_else(|| {
19046 cx.new(|cx| {
19047 let mut editor = Editor::for_multibuffer(
19048 excerpt_buffer,
19049 Some(workspace.project().clone()),
19050 window,
19051 cx,
19052 );
19053 editor.lookup_key = Some(Box::new(key));
19054 editor
19055 })
19056 });
19057 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
19058 MultibufferSelectionMode::First => {
19059 if let Some(first_range) = ranges.first() {
19060 editor.change_selections(
19061 SelectionEffects::no_scroll(),
19062 window,
19063 cx,
19064 |selections| {
19065 selections.clear_disjoint();
19066 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
19067 },
19068 );
19069 }
19070 editor.highlight_background(
19071 HighlightKey::Editor,
19072 &ranges,
19073 |_, theme| theme.colors().editor_highlighted_line_background,
19074 cx,
19075 );
19076 }
19077 MultibufferSelectionMode::All => {
19078 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
19079 selections.clear_disjoint();
19080 selections.select_anchor_ranges(ranges);
19081 });
19082 }
19083 });
19084
19085 let item = Box::new(editor.clone());
19086
19087 let pane = if split {
19088 workspace.adjacent_pane(window, cx)
19089 } else {
19090 workspace.active_pane().clone()
19091 };
19092 let activate_pane = split;
19093
19094 let mut destination_index = None;
19095 pane.update(cx, |pane, cx| {
19096 if allow_preview && !was_existing {
19097 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
19098 }
19099 if was_existing && !allow_preview {
19100 pane.unpreview_item_if_preview(item.item_id());
19101 }
19102 pane.add_item(item, activate_pane, true, destination_index, window, cx);
19103 });
19104
19105 Some((editor, pane))
19106 }
19107
19108 pub fn rename(
19109 &mut self,
19110 _: &Rename,
19111 window: &mut Window,
19112 cx: &mut Context<Self>,
19113 ) -> Option<Task<Result<()>>> {
19114 use language::ToOffset as _;
19115
19116 let provider = self.semantics_provider.clone()?;
19117 let selection = self.selections.newest_anchor().clone();
19118 let (cursor_buffer, cursor_buffer_position) = self
19119 .buffer
19120 .read(cx)
19121 .text_anchor_for_position(selection.head(), cx)?;
19122 let (tail_buffer, cursor_buffer_position_end) = self
19123 .buffer
19124 .read(cx)
19125 .text_anchor_for_position(selection.tail(), cx)?;
19126 if tail_buffer != cursor_buffer {
19127 return None;
19128 }
19129
19130 let snapshot = cursor_buffer.read(cx).snapshot();
19131 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
19132 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
19133 let prepare_rename = provider.range_for_rename(&cursor_buffer, cursor_buffer_position, cx);
19134 drop(snapshot);
19135
19136 Some(cx.spawn_in(window, async move |this, cx| {
19137 let rename_range = prepare_rename.await?;
19138 if let Some(rename_range) = rename_range {
19139 this.update_in(cx, |this, window, cx| {
19140 let snapshot = cursor_buffer.read(cx).snapshot();
19141 let rename_buffer_range = rename_range.to_offset(&snapshot);
19142 let cursor_offset_in_rename_range =
19143 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
19144 let cursor_offset_in_rename_range_end =
19145 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
19146
19147 this.take_rename(false, window, cx);
19148 let buffer = this.buffer.read(cx).read(cx);
19149 let cursor_offset = selection.head().to_offset(&buffer);
19150 let rename_start =
19151 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
19152 let rename_end = rename_start + rename_buffer_range.len();
19153 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
19154 let mut old_highlight_id = None;
19155 let old_name: Arc<str> = buffer
19156 .chunks(
19157 rename_start..rename_end,
19158 LanguageAwareStyling {
19159 tree_sitter: true,
19160 diagnostics: true,
19161 },
19162 )
19163 .map(|chunk| {
19164 if old_highlight_id.is_none() {
19165 old_highlight_id = chunk.syntax_highlight_id;
19166 }
19167 chunk.text
19168 })
19169 .collect::<String>()
19170 .into();
19171
19172 drop(buffer);
19173
19174 // Position the selection in the rename editor so that it matches the current selection.
19175 this.show_local_selections = false;
19176 let rename_editor = cx.new(|cx| {
19177 let mut editor = Editor::single_line(window, cx);
19178 editor.buffer.update(cx, |buffer, cx| {
19179 buffer.edit(
19180 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
19181 None,
19182 cx,
19183 )
19184 });
19185 let cursor_offset_in_rename_range =
19186 MultiBufferOffset(cursor_offset_in_rename_range);
19187 let cursor_offset_in_rename_range_end =
19188 MultiBufferOffset(cursor_offset_in_rename_range_end);
19189 let rename_selection_range = match cursor_offset_in_rename_range
19190 .cmp(&cursor_offset_in_rename_range_end)
19191 {
19192 Ordering::Equal => {
19193 editor.select_all(&SelectAll, window, cx);
19194 return editor;
19195 }
19196 Ordering::Less => {
19197 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
19198 }
19199 Ordering::Greater => {
19200 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
19201 }
19202 };
19203 if rename_selection_range.end.0 > old_name.len() {
19204 editor.select_all(&SelectAll, window, cx);
19205 } else {
19206 editor.change_selections(Default::default(), window, cx, |s| {
19207 s.select_ranges([rename_selection_range]);
19208 });
19209 }
19210 editor
19211 });
19212 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
19213 if e == &EditorEvent::Focused {
19214 cx.emit(EditorEvent::FocusedIn)
19215 }
19216 })
19217 .detach();
19218
19219 let write_highlights =
19220 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
19221 let read_highlights =
19222 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
19223 let ranges = write_highlights
19224 .iter()
19225 .flat_map(|(_, ranges)| ranges.iter())
19226 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
19227 .cloned()
19228 .collect();
19229
19230 this.highlight_text(
19231 HighlightKey::Rename,
19232 ranges,
19233 HighlightStyle {
19234 fade_out: Some(0.6),
19235 ..Default::default()
19236 },
19237 cx,
19238 );
19239 let rename_focus_handle = rename_editor.focus_handle(cx);
19240 window.focus(&rename_focus_handle, cx);
19241 let block_id = this.insert_blocks(
19242 [BlockProperties {
19243 style: BlockStyle::Flex,
19244 placement: BlockPlacement::Below(range.start),
19245 height: Some(1),
19246 render: Arc::new({
19247 let rename_editor = rename_editor.clone();
19248 move |cx: &mut BlockContext| {
19249 let mut text_style = cx.editor_style.text.clone();
19250 if let Some(highlight_style) = old_highlight_id
19251 .and_then(|h| cx.editor_style.syntax.get(h).cloned())
19252 {
19253 text_style = text_style.highlight(highlight_style);
19254 }
19255 div()
19256 .block_mouse_except_scroll()
19257 .pl(cx.anchor_x)
19258 .child(EditorElement::new(
19259 &rename_editor,
19260 EditorStyle {
19261 background: cx.theme().system().transparent,
19262 local_player: cx.editor_style.local_player,
19263 text: text_style,
19264 scrollbar_width: cx.editor_style.scrollbar_width,
19265 syntax: cx.editor_style.syntax.clone(),
19266 status: cx.editor_style.status.clone(),
19267 inlay_hints_style: HighlightStyle {
19268 font_weight: Some(FontWeight::BOLD),
19269 ..make_inlay_hints_style(cx.app)
19270 },
19271 edit_prediction_styles: make_suggestion_styles(
19272 cx.app,
19273 ),
19274 ..EditorStyle::default()
19275 },
19276 ))
19277 .into_any_element()
19278 }
19279 }),
19280 priority: 0,
19281 }],
19282 Some(Autoscroll::fit()),
19283 cx,
19284 )[0];
19285 this.pending_rename = Some(RenameState {
19286 range,
19287 old_name,
19288 editor: rename_editor,
19289 block_id,
19290 });
19291 })?;
19292 }
19293
19294 Ok(())
19295 }))
19296 }
19297
19298 pub fn confirm_rename(
19299 &mut self,
19300 _: &ConfirmRename,
19301 window: &mut Window,
19302 cx: &mut Context<Self>,
19303 ) -> Option<Task<Result<()>>> {
19304 let rename = self.take_rename(false, window, cx)?;
19305 let workspace = self.workspace()?.downgrade();
19306 let (buffer, start) = self
19307 .buffer
19308 .read(cx)
19309 .text_anchor_for_position(rename.range.start, cx)?;
19310 let (end_buffer, _) = self
19311 .buffer
19312 .read(cx)
19313 .text_anchor_for_position(rename.range.end, cx)?;
19314 if buffer != end_buffer {
19315 return None;
19316 }
19317
19318 let old_name = rename.old_name;
19319 let new_name = rename.editor.read(cx).text(cx);
19320
19321 let rename = self.semantics_provider.as_ref()?.perform_rename(
19322 &buffer,
19323 start,
19324 new_name.clone(),
19325 cx,
19326 )?;
19327
19328 Some(cx.spawn_in(window, async move |editor, cx| {
19329 let project_transaction = rename.await?;
19330 Self::open_project_transaction(
19331 &editor,
19332 workspace,
19333 project_transaction,
19334 format!("Rename: {} → {}", old_name, new_name),
19335 cx,
19336 )
19337 .await?;
19338
19339 editor.update(cx, |editor, cx| {
19340 editor.refresh_document_highlights(cx);
19341 })?;
19342 Ok(())
19343 }))
19344 }
19345
19346 fn take_rename(
19347 &mut self,
19348 moving_cursor: bool,
19349 window: &mut Window,
19350 cx: &mut Context<Self>,
19351 ) -> Option<RenameState> {
19352 let rename = self.pending_rename.take()?;
19353 if rename.editor.focus_handle(cx).is_focused(window) {
19354 window.focus(&self.focus_handle, cx);
19355 }
19356
19357 self.remove_blocks(
19358 [rename.block_id].into_iter().collect(),
19359 Some(Autoscroll::fit()),
19360 cx,
19361 );
19362 self.clear_highlights(HighlightKey::Rename, cx);
19363 self.show_local_selections = true;
19364
19365 if moving_cursor {
19366 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
19367 editor
19368 .selections
19369 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
19370 .head()
19371 });
19372
19373 // Update the selection to match the position of the selection inside
19374 // the rename editor.
19375 let snapshot = self.buffer.read(cx).read(cx);
19376 let rename_range = rename.range.to_offset(&snapshot);
19377 let cursor_in_editor = snapshot
19378 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
19379 .min(rename_range.end);
19380 drop(snapshot);
19381
19382 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19383 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
19384 });
19385 } else {
19386 self.refresh_document_highlights(cx);
19387 }
19388
19389 Some(rename)
19390 }
19391
19392 pub fn pending_rename(&self) -> Option<&RenameState> {
19393 self.pending_rename.as_ref()
19394 }
19395
19396 fn format(
19397 &mut self,
19398 _: &Format,
19399 window: &mut Window,
19400 cx: &mut Context<Self>,
19401 ) -> Option<Task<Result<()>>> {
19402 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19403
19404 let project = match &self.project {
19405 Some(project) => project.clone(),
19406 None => return None,
19407 };
19408
19409 Some(self.perform_format(
19410 project,
19411 FormatTrigger::Manual,
19412 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
19413 window,
19414 cx,
19415 ))
19416 }
19417
19418 fn format_selections(
19419 &mut self,
19420 _: &FormatSelections,
19421 window: &mut Window,
19422 cx: &mut Context<Self>,
19423 ) -> Option<Task<Result<()>>> {
19424 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19425
19426 let project = match &self.project {
19427 Some(project) => project.clone(),
19428 None => return None,
19429 };
19430
19431 let ranges = self
19432 .selections
19433 .all_adjusted(&self.display_snapshot(cx))
19434 .into_iter()
19435 .map(|selection| selection.range())
19436 .collect_vec();
19437
19438 Some(self.perform_format(
19439 project,
19440 FormatTrigger::Manual,
19441 FormatTarget::Ranges(ranges),
19442 window,
19443 cx,
19444 ))
19445 }
19446
19447 fn perform_format(
19448 &mut self,
19449 project: Entity<Project>,
19450 trigger: FormatTrigger,
19451 target: FormatTarget,
19452 window: &mut Window,
19453 cx: &mut Context<Self>,
19454 ) -> Task<Result<()>> {
19455 let buffer = self.buffer.clone();
19456 let (buffers, target) = match target {
19457 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
19458 FormatTarget::Ranges(selection_ranges) => {
19459 let multi_buffer = buffer.read(cx);
19460 let snapshot = multi_buffer.read(cx);
19461 let mut buffers = HashSet::default();
19462 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
19463 BTreeMap::new();
19464 for selection_range in selection_ranges {
19465 for (buffer_snapshot, buffer_range, _) in
19466 snapshot.range_to_buffer_ranges(selection_range.start..selection_range.end)
19467 {
19468 let buffer_id = buffer_snapshot.remote_id();
19469 let start = buffer_snapshot.anchor_before(buffer_range.start);
19470 let end = buffer_snapshot.anchor_after(buffer_range.end);
19471 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
19472 buffer_id_to_ranges
19473 .entry(buffer_id)
19474 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
19475 .or_insert_with(|| vec![start..end]);
19476 }
19477 }
19478 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
19479 }
19480 };
19481
19482 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
19483 let selections_prev = transaction_id_prev
19484 .and_then(|transaction_id_prev| {
19485 // default to selections as they were after the last edit, if we have them,
19486 // instead of how they are now.
19487 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
19488 // will take you back to where you made the last edit, instead of staying where you scrolled
19489 self.selection_history
19490 .transaction(transaction_id_prev)
19491 .map(|t| t.0.clone())
19492 })
19493 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
19494
19495 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
19496 let format = project.update(cx, |project, cx| {
19497 project.format(buffers, target, true, trigger, cx)
19498 });
19499
19500 cx.spawn_in(window, async move |editor, cx| {
19501 let transaction = futures::select_biased! {
19502 transaction = format.log_err().fuse() => transaction,
19503 () = timeout => {
19504 log::warn!("timed out waiting for formatting");
19505 None
19506 }
19507 };
19508
19509 buffer.update(cx, |buffer, cx| {
19510 if let Some(transaction) = transaction
19511 && !buffer.is_singleton()
19512 {
19513 buffer.push_transaction(&transaction.0, cx);
19514 }
19515 cx.notify();
19516 });
19517
19518 if let Some(transaction_id_now) =
19519 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))
19520 {
19521 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
19522 if has_new_transaction {
19523 editor
19524 .update(cx, |editor, _| {
19525 editor
19526 .selection_history
19527 .insert_transaction(transaction_id_now, selections_prev);
19528 })
19529 .ok();
19530 }
19531 }
19532
19533 Ok(())
19534 })
19535 }
19536
19537 fn organize_imports(
19538 &mut self,
19539 _: &OrganizeImports,
19540 window: &mut Window,
19541 cx: &mut Context<Self>,
19542 ) -> Option<Task<Result<()>>> {
19543 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19544 let project = match &self.project {
19545 Some(project) => project.clone(),
19546 None => return None,
19547 };
19548 Some(self.perform_code_action_kind(
19549 project,
19550 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
19551 window,
19552 cx,
19553 ))
19554 }
19555
19556 fn perform_code_action_kind(
19557 &mut self,
19558 project: Entity<Project>,
19559 kind: CodeActionKind,
19560 window: &mut Window,
19561 cx: &mut Context<Self>,
19562 ) -> Task<Result<()>> {
19563 let buffer = self.buffer.clone();
19564 let buffers = buffer.read(cx).all_buffers();
19565 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
19566 let apply_action = project.update(cx, |project, cx| {
19567 project.apply_code_action_kind(buffers, kind, true, cx)
19568 });
19569 cx.spawn_in(window, async move |_, cx| {
19570 let transaction = futures::select_biased! {
19571 () = timeout => {
19572 log::warn!("timed out waiting for executing code action");
19573 None
19574 }
19575 transaction = apply_action.log_err().fuse() => transaction,
19576 };
19577 buffer.update(cx, |buffer, cx| {
19578 // check if we need this
19579 if let Some(transaction) = transaction
19580 && !buffer.is_singleton()
19581 {
19582 buffer.push_transaction(&transaction.0, cx);
19583 }
19584 cx.notify();
19585 });
19586 Ok(())
19587 })
19588 }
19589
19590 pub fn restart_language_server(
19591 &mut self,
19592 _: &RestartLanguageServer,
19593 _: &mut Window,
19594 cx: &mut Context<Self>,
19595 ) {
19596 if let Some(project) = self.project.clone() {
19597 self.buffer.update(cx, |multi_buffer, cx| {
19598 project.update(cx, |project, cx| {
19599 project.restart_language_servers_for_buffers(
19600 multi_buffer.all_buffers().into_iter().collect(),
19601 HashSet::default(),
19602 cx,
19603 );
19604 });
19605 })
19606 }
19607 }
19608
19609 pub fn stop_language_server(
19610 &mut self,
19611 _: &StopLanguageServer,
19612 _: &mut Window,
19613 cx: &mut Context<Self>,
19614 ) {
19615 if let Some(project) = self.project.clone() {
19616 self.buffer.update(cx, |multi_buffer, cx| {
19617 project.update(cx, |project, cx| {
19618 project.stop_language_servers_for_buffers(
19619 multi_buffer.all_buffers().into_iter().collect(),
19620 HashSet::default(),
19621 cx,
19622 );
19623 });
19624 });
19625 }
19626 }
19627
19628 fn cancel_language_server_work(
19629 workspace: &mut Workspace,
19630 _: &actions::CancelLanguageServerWork,
19631 _: &mut Window,
19632 cx: &mut Context<Workspace>,
19633 ) {
19634 let project = workspace.project();
19635 let buffers = workspace
19636 .active_item(cx)
19637 .and_then(|item| item.act_as::<Editor>(cx))
19638 .map_or(HashSet::default(), |editor| {
19639 editor.read(cx).buffer.read(cx).all_buffers()
19640 });
19641 project.update(cx, |project, cx| {
19642 project.cancel_language_server_work_for_buffers(buffers, cx);
19643 });
19644 }
19645
19646 fn show_character_palette(
19647 &mut self,
19648 _: &ShowCharacterPalette,
19649 window: &mut Window,
19650 _: &mut Context<Self>,
19651 ) {
19652 window.show_character_palette();
19653 }
19654
19655 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
19656 if !self.diagnostics_enabled() {
19657 return;
19658 }
19659
19660 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
19661 let buffer = self.buffer.read(cx).snapshot(cx);
19662 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
19663 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
19664 let is_valid = buffer
19665 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
19666 .any(|entry| {
19667 entry.diagnostic.is_primary
19668 && !entry.range.is_empty()
19669 && entry.range.start == primary_range_start
19670 && entry.diagnostic.message == active_diagnostics.active_message
19671 });
19672
19673 if !is_valid {
19674 self.dismiss_diagnostics(cx);
19675 }
19676 }
19677 }
19678
19679 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
19680 match &self.active_diagnostics {
19681 ActiveDiagnostic::Group(group) => Some(group),
19682 _ => None,
19683 }
19684 }
19685
19686 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
19687 if !self.diagnostics_enabled() {
19688 return;
19689 }
19690 self.dismiss_diagnostics(cx);
19691 self.active_diagnostics = ActiveDiagnostic::All;
19692 }
19693
19694 fn activate_diagnostics(
19695 &mut self,
19696 buffer_id: BufferId,
19697 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
19698 window: &mut Window,
19699 cx: &mut Context<Self>,
19700 ) {
19701 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19702 return;
19703 }
19704 self.dismiss_diagnostics(cx);
19705 let snapshot = self.snapshot(window, cx);
19706 let buffer = self.buffer.read(cx).snapshot(cx);
19707 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
19708 return;
19709 };
19710
19711 let diagnostic_group = buffer
19712 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
19713 .collect::<Vec<_>>();
19714
19715 let language_registry = self
19716 .project()
19717 .map(|project| project.read(cx).languages().clone());
19718
19719 let blocks = renderer.render_group(
19720 diagnostic_group,
19721 buffer_id,
19722 snapshot,
19723 cx.weak_entity(),
19724 language_registry,
19725 cx,
19726 );
19727
19728 let blocks = self.display_map.update(cx, |display_map, cx| {
19729 display_map.insert_blocks(blocks, cx).into_iter().collect()
19730 });
19731 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
19732 active_range: buffer.anchor_before(diagnostic.range.start)
19733 ..buffer.anchor_after(diagnostic.range.end),
19734 active_message: diagnostic.diagnostic.message.clone(),
19735 group_id: diagnostic.diagnostic.group_id,
19736 blocks,
19737 });
19738 cx.notify();
19739 }
19740
19741 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
19742 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19743 return;
19744 };
19745
19746 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
19747 if let ActiveDiagnostic::Group(group) = prev {
19748 self.display_map.update(cx, |display_map, cx| {
19749 display_map.remove_blocks(group.blocks, cx);
19750 });
19751 cx.notify();
19752 }
19753 }
19754
19755 /// Disable inline diagnostics rendering for this editor.
19756 pub fn disable_inline_diagnostics(&mut self) {
19757 self.inline_diagnostics_enabled = false;
19758 self.inline_diagnostics_update = Task::ready(());
19759 self.inline_diagnostics.clear();
19760 }
19761
19762 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
19763 self.diagnostics_enabled = false;
19764 self.dismiss_diagnostics(cx);
19765 self.inline_diagnostics_update = Task::ready(());
19766 self.inline_diagnostics.clear();
19767 }
19768
19769 pub fn disable_word_completions(&mut self) {
19770 self.word_completions_enabled = false;
19771 }
19772
19773 pub fn diagnostics_enabled(&self) -> bool {
19774 self.diagnostics_enabled && self.lsp_data_enabled()
19775 }
19776
19777 pub fn inline_diagnostics_enabled(&self) -> bool {
19778 self.inline_diagnostics_enabled && self.diagnostics_enabled()
19779 }
19780
19781 pub fn show_inline_diagnostics(&self) -> bool {
19782 self.show_inline_diagnostics
19783 }
19784
19785 pub fn toggle_inline_diagnostics(
19786 &mut self,
19787 _: &ToggleInlineDiagnostics,
19788 window: &mut Window,
19789 cx: &mut Context<Editor>,
19790 ) {
19791 self.show_inline_diagnostics = !self.show_inline_diagnostics;
19792 self.refresh_inline_diagnostics(false, window, cx);
19793 }
19794
19795 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
19796 self.diagnostics_max_severity = severity;
19797 self.display_map.update(cx, |display_map, _| {
19798 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
19799 });
19800 }
19801
19802 pub fn toggle_diagnostics(
19803 &mut self,
19804 _: &ToggleDiagnostics,
19805 window: &mut Window,
19806 cx: &mut Context<Editor>,
19807 ) {
19808 if !self.diagnostics_enabled() {
19809 return;
19810 }
19811
19812 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19813 EditorSettings::get_global(cx)
19814 .diagnostics_max_severity
19815 .filter(|severity| severity != &DiagnosticSeverity::Off)
19816 .unwrap_or(DiagnosticSeverity::Hint)
19817 } else {
19818 DiagnosticSeverity::Off
19819 };
19820 self.set_max_diagnostics_severity(new_severity, cx);
19821 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19822 self.active_diagnostics = ActiveDiagnostic::None;
19823 self.inline_diagnostics_update = Task::ready(());
19824 self.inline_diagnostics.clear();
19825 } else {
19826 self.refresh_inline_diagnostics(false, window, cx);
19827 }
19828
19829 cx.notify();
19830 }
19831
19832 pub fn toggle_minimap(
19833 &mut self,
19834 _: &ToggleMinimap,
19835 window: &mut Window,
19836 cx: &mut Context<Editor>,
19837 ) {
19838 if self.supports_minimap(cx) {
19839 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
19840 }
19841 }
19842
19843 fn refresh_inline_diagnostics(
19844 &mut self,
19845 debounce: bool,
19846 window: &mut Window,
19847 cx: &mut Context<Self>,
19848 ) {
19849 let max_severity = ProjectSettings::get_global(cx)
19850 .diagnostics
19851 .inline
19852 .max_severity
19853 .unwrap_or(self.diagnostics_max_severity);
19854
19855 if !self.inline_diagnostics_enabled()
19856 || !self.diagnostics_enabled()
19857 || !self.show_inline_diagnostics
19858 || max_severity == DiagnosticSeverity::Off
19859 {
19860 self.inline_diagnostics_update = Task::ready(());
19861 self.inline_diagnostics.clear();
19862 return;
19863 }
19864
19865 let debounce_ms = ProjectSettings::get_global(cx)
19866 .diagnostics
19867 .inline
19868 .update_debounce_ms;
19869 let debounce = if debounce && debounce_ms > 0 {
19870 Some(Duration::from_millis(debounce_ms))
19871 } else {
19872 None
19873 };
19874 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
19875 if let Some(debounce) = debounce {
19876 cx.background_executor().timer(debounce).await;
19877 }
19878 let Some(snapshot) = editor.upgrade().map(|editor| {
19879 editor.update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
19880 }) else {
19881 return;
19882 };
19883
19884 let new_inline_diagnostics = cx
19885 .background_spawn(async move {
19886 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
19887 for diagnostic_entry in
19888 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
19889 {
19890 let message = diagnostic_entry
19891 .diagnostic
19892 .message
19893 .split_once('\n')
19894 .map(|(line, _)| line)
19895 .map(SharedString::new)
19896 .unwrap_or_else(|| {
19897 SharedString::new(&*diagnostic_entry.diagnostic.message)
19898 });
19899 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
19900 let (Ok(i) | Err(i)) = inline_diagnostics
19901 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
19902 inline_diagnostics.insert(
19903 i,
19904 (
19905 start_anchor,
19906 InlineDiagnostic {
19907 message,
19908 group_id: diagnostic_entry.diagnostic.group_id,
19909 start: diagnostic_entry.range.start.to_point(&snapshot),
19910 is_primary: diagnostic_entry.diagnostic.is_primary,
19911 severity: diagnostic_entry.diagnostic.severity,
19912 },
19913 ),
19914 );
19915 }
19916 inline_diagnostics
19917 })
19918 .await;
19919
19920 editor
19921 .update(cx, |editor, cx| {
19922 editor.inline_diagnostics = new_inline_diagnostics;
19923 cx.notify();
19924 })
19925 .ok();
19926 });
19927 }
19928
19929 fn pull_diagnostics(
19930 &mut self,
19931 buffer_id: BufferId,
19932 _window: &Window,
19933 cx: &mut Context<Self>,
19934 ) -> Option<()> {
19935 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
19936 // skip any LSP updates for it.
19937
19938 if self.active_diagnostics == ActiveDiagnostic::All || !self.diagnostics_enabled() {
19939 return None;
19940 }
19941 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
19942 .diagnostics
19943 .lsp_pull_diagnostics;
19944 if !pull_diagnostics_settings.enabled {
19945 return None;
19946 }
19947 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
19948 let project = self.project()?.downgrade();
19949 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
19950
19951 self.pull_diagnostics_task = cx.spawn(async move |_, cx| {
19952 cx.background_executor().timer(debounce).await;
19953 if let Ok(task) = project.update(cx, |project, cx| {
19954 project.lsp_store().update(cx, |lsp_store, cx| {
19955 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
19956 })
19957 }) {
19958 task.await.log_err();
19959 }
19960 project
19961 .update(cx, |project, cx| {
19962 project.lsp_store().update(cx, |lsp_store, cx| {
19963 lsp_store.pull_document_diagnostics_for_buffer_edit(buffer_id, cx);
19964 })
19965 })
19966 .log_err();
19967 });
19968
19969 Some(())
19970 }
19971
19972 pub fn set_selections_from_remote(
19973 &mut self,
19974 selections: Vec<Selection<Anchor>>,
19975 pending_selection: Option<Selection<Anchor>>,
19976 window: &mut Window,
19977 cx: &mut Context<Self>,
19978 ) {
19979 let old_cursor_position = self.selections.newest_anchor().head();
19980 self.selections
19981 .change_with(&self.display_snapshot(cx), |s| {
19982 s.select_anchors(selections);
19983 if let Some(pending_selection) = pending_selection {
19984 s.set_pending(pending_selection, SelectMode::Character);
19985 } else {
19986 s.clear_pending();
19987 }
19988 });
19989 self.selections_did_change(
19990 false,
19991 &old_cursor_position,
19992 SelectionEffects::default(),
19993 window,
19994 cx,
19995 );
19996 }
19997
19998 pub fn transact(
19999 &mut self,
20000 window: &mut Window,
20001 cx: &mut Context<Self>,
20002 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
20003 ) -> Option<TransactionId> {
20004 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
20005 this.start_transaction_at(Instant::now(), window, cx);
20006 update(this, window, cx);
20007 this.end_transaction_at(Instant::now(), cx)
20008 })
20009 }
20010
20011 pub fn start_transaction_at(
20012 &mut self,
20013 now: Instant,
20014 window: &mut Window,
20015 cx: &mut Context<Self>,
20016 ) -> Option<TransactionId> {
20017 self.end_selection(window, cx);
20018 if let Some(tx_id) = self
20019 .buffer
20020 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
20021 {
20022 self.selection_history
20023 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
20024 cx.emit(EditorEvent::TransactionBegun {
20025 transaction_id: tx_id,
20026 });
20027 Some(tx_id)
20028 } else {
20029 None
20030 }
20031 }
20032
20033 pub fn end_transaction_at(
20034 &mut self,
20035 now: Instant,
20036 cx: &mut Context<Self>,
20037 ) -> Option<TransactionId> {
20038 if let Some(transaction_id) = self
20039 .buffer
20040 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
20041 {
20042 if let Some((_, end_selections)) =
20043 self.selection_history.transaction_mut(transaction_id)
20044 {
20045 *end_selections = Some(self.selections.disjoint_anchors_arc());
20046 } else {
20047 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
20048 }
20049
20050 cx.emit(EditorEvent::Edited { transaction_id });
20051 Some(transaction_id)
20052 } else {
20053 None
20054 }
20055 }
20056
20057 pub fn modify_transaction_selection_history(
20058 &mut self,
20059 transaction_id: TransactionId,
20060 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
20061 ) -> bool {
20062 self.selection_history
20063 .transaction_mut(transaction_id)
20064 .map(modify)
20065 .is_some()
20066 }
20067
20068 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
20069 if self.selection_mark_mode {
20070 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20071 s.move_with(&mut |_, sel| {
20072 sel.collapse_to(sel.head(), SelectionGoal::None);
20073 });
20074 })
20075 }
20076 self.selection_mark_mode = true;
20077 cx.notify();
20078 }
20079
20080 pub fn swap_selection_ends(
20081 &mut self,
20082 _: &actions::SwapSelectionEnds,
20083 window: &mut Window,
20084 cx: &mut Context<Self>,
20085 ) {
20086 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20087 s.move_with(&mut |_, sel| {
20088 if sel.start != sel.end {
20089 sel.reversed = !sel.reversed
20090 }
20091 });
20092 });
20093 self.request_autoscroll(Autoscroll::newest(), cx);
20094 cx.notify();
20095 }
20096
20097 pub fn toggle_focus(
20098 workspace: &mut Workspace,
20099 _: &actions::ToggleFocus,
20100 window: &mut Window,
20101 cx: &mut Context<Workspace>,
20102 ) {
20103 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
20104 return;
20105 };
20106 workspace.activate_item(&item, true, true, window, cx);
20107 }
20108
20109 pub fn toggle_fold(
20110 &mut self,
20111 _: &actions::ToggleFold,
20112 window: &mut Window,
20113 cx: &mut Context<Self>,
20114 ) {
20115 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
20116 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20117 let selection = self.selections.newest::<Point>(&display_map);
20118
20119 let range = if selection.is_empty() {
20120 let point = selection.head().to_display_point(&display_map);
20121 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
20122 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
20123 .to_point(&display_map);
20124 start..end
20125 } else {
20126 selection.range()
20127 };
20128 if display_map.folds_in_range(range).next().is_some() {
20129 self.unfold_lines(&Default::default(), window, cx)
20130 } else {
20131 self.fold(&Default::default(), window, cx)
20132 }
20133 } else {
20134 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20135 let buffer_ids: HashSet<_> = self
20136 .selections
20137 .disjoint_anchor_ranges()
20138 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20139 .collect();
20140
20141 let should_unfold = buffer_ids
20142 .iter()
20143 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
20144
20145 for buffer_id in buffer_ids {
20146 if should_unfold {
20147 self.unfold_buffer(buffer_id, cx);
20148 } else {
20149 self.fold_buffer(buffer_id, cx);
20150 }
20151 }
20152 }
20153 }
20154
20155 pub fn toggle_fold_recursive(
20156 &mut self,
20157 _: &actions::ToggleFoldRecursive,
20158 window: &mut Window,
20159 cx: &mut Context<Self>,
20160 ) {
20161 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20162
20163 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20164 let range = if selection.is_empty() {
20165 let point = selection.head().to_display_point(&display_map);
20166 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
20167 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
20168 .to_point(&display_map);
20169 start..end
20170 } else {
20171 selection.range()
20172 };
20173 if display_map.folds_in_range(range).next().is_some() {
20174 self.unfold_recursive(&Default::default(), window, cx)
20175 } else {
20176 self.fold_recursive(&Default::default(), window, cx)
20177 }
20178 }
20179
20180 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
20181 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
20182 let mut to_fold = Vec::new();
20183 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20184 let selections = self.selections.all_adjusted(&display_map);
20185
20186 for selection in selections {
20187 let range = selection.range().sorted();
20188 let buffer_start_row = range.start.row;
20189
20190 if range.start.row != range.end.row {
20191 let mut found = false;
20192 let mut row = range.start.row;
20193 while row <= range.end.row {
20194 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
20195 {
20196 found = true;
20197 row = crease.range().end.row + 1;
20198 to_fold.push(crease);
20199 } else {
20200 row += 1
20201 }
20202 }
20203 if found {
20204 continue;
20205 }
20206 }
20207
20208 for row in (0..=range.start.row).rev() {
20209 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
20210 && crease.range().end.row >= buffer_start_row
20211 {
20212 to_fold.push(crease);
20213 if row <= range.start.row {
20214 break;
20215 }
20216 }
20217 }
20218 }
20219
20220 self.fold_creases(to_fold, true, window, cx);
20221 } else {
20222 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20223 let buffer_ids = self
20224 .selections
20225 .disjoint_anchor_ranges()
20226 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20227 .collect::<HashSet<_>>();
20228 for buffer_id in buffer_ids {
20229 self.fold_buffer(buffer_id, cx);
20230 }
20231 }
20232 }
20233
20234 pub fn toggle_fold_all(
20235 &mut self,
20236 _: &actions::ToggleFoldAll,
20237 window: &mut Window,
20238 cx: &mut Context<Self>,
20239 ) {
20240 let has_folds = if self.buffer.read(cx).is_singleton() {
20241 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20242 let has_folds = display_map
20243 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
20244 .next()
20245 .is_some();
20246 has_folds
20247 } else {
20248 let snapshot = self.buffer.read(cx).snapshot(cx);
20249 let has_folds = snapshot
20250 .all_buffer_ids()
20251 .any(|buffer_id| self.is_buffer_folded(buffer_id, cx));
20252 has_folds
20253 };
20254
20255 if has_folds {
20256 self.unfold_all(&actions::UnfoldAll, window, cx);
20257 } else {
20258 self.fold_all(&actions::FoldAll, window, cx);
20259 }
20260 }
20261
20262 fn fold_at_level(
20263 &mut self,
20264 fold_at: &FoldAtLevel,
20265 window: &mut Window,
20266 cx: &mut Context<Self>,
20267 ) {
20268 if !self.buffer.read(cx).is_singleton() {
20269 return;
20270 }
20271
20272 let fold_at_level = fold_at.0;
20273 let snapshot = self.buffer.read(cx).snapshot(cx);
20274 let mut to_fold = Vec::new();
20275 let mut stack = vec![(0, snapshot.max_row().0, 1)];
20276
20277 let row_ranges_to_keep: Vec<Range<u32>> = self
20278 .selections
20279 .all::<Point>(&self.display_snapshot(cx))
20280 .into_iter()
20281 .map(|sel| sel.start.row..sel.end.row)
20282 .collect();
20283
20284 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
20285 while start_row < end_row {
20286 match self
20287 .snapshot(window, cx)
20288 .crease_for_buffer_row(MultiBufferRow(start_row))
20289 {
20290 Some(crease) => {
20291 let nested_start_row = crease.range().start.row + 1;
20292 let nested_end_row = crease.range().end.row;
20293
20294 if current_level < fold_at_level {
20295 stack.push((nested_start_row, nested_end_row, current_level + 1));
20296 } else if current_level == fold_at_level {
20297 // Fold iff there is no selection completely contained within the fold region
20298 if !row_ranges_to_keep.iter().any(|selection| {
20299 selection.end >= nested_start_row
20300 && selection.start <= nested_end_row
20301 }) {
20302 to_fold.push(crease);
20303 }
20304 }
20305
20306 start_row = nested_end_row + 1;
20307 }
20308 None => start_row += 1,
20309 }
20310 }
20311 }
20312
20313 self.fold_creases(to_fold, true, window, cx);
20314 }
20315
20316 pub fn fold_at_level_1(
20317 &mut self,
20318 _: &actions::FoldAtLevel1,
20319 window: &mut Window,
20320 cx: &mut Context<Self>,
20321 ) {
20322 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
20323 }
20324
20325 pub fn fold_at_level_2(
20326 &mut self,
20327 _: &actions::FoldAtLevel2,
20328 window: &mut Window,
20329 cx: &mut Context<Self>,
20330 ) {
20331 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
20332 }
20333
20334 pub fn fold_at_level_3(
20335 &mut self,
20336 _: &actions::FoldAtLevel3,
20337 window: &mut Window,
20338 cx: &mut Context<Self>,
20339 ) {
20340 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
20341 }
20342
20343 pub fn fold_at_level_4(
20344 &mut self,
20345 _: &actions::FoldAtLevel4,
20346 window: &mut Window,
20347 cx: &mut Context<Self>,
20348 ) {
20349 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
20350 }
20351
20352 pub fn fold_at_level_5(
20353 &mut self,
20354 _: &actions::FoldAtLevel5,
20355 window: &mut Window,
20356 cx: &mut Context<Self>,
20357 ) {
20358 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
20359 }
20360
20361 pub fn fold_at_level_6(
20362 &mut self,
20363 _: &actions::FoldAtLevel6,
20364 window: &mut Window,
20365 cx: &mut Context<Self>,
20366 ) {
20367 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
20368 }
20369
20370 pub fn fold_at_level_7(
20371 &mut self,
20372 _: &actions::FoldAtLevel7,
20373 window: &mut Window,
20374 cx: &mut Context<Self>,
20375 ) {
20376 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
20377 }
20378
20379 pub fn fold_at_level_8(
20380 &mut self,
20381 _: &actions::FoldAtLevel8,
20382 window: &mut Window,
20383 cx: &mut Context<Self>,
20384 ) {
20385 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
20386 }
20387
20388 pub fn fold_at_level_9(
20389 &mut self,
20390 _: &actions::FoldAtLevel9,
20391 window: &mut Window,
20392 cx: &mut Context<Self>,
20393 ) {
20394 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
20395 }
20396
20397 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
20398 if self.buffer.read(cx).is_singleton() {
20399 let mut fold_ranges = Vec::new();
20400 let snapshot = self.buffer.read(cx).snapshot(cx);
20401
20402 for row in 0..snapshot.max_row().0 {
20403 if let Some(foldable_range) = self
20404 .snapshot(window, cx)
20405 .crease_for_buffer_row(MultiBufferRow(row))
20406 {
20407 fold_ranges.push(foldable_range);
20408 }
20409 }
20410
20411 self.fold_creases(fold_ranges, true, window, cx);
20412 } else {
20413 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
20414 editor
20415 .update_in(cx, |editor, _, cx| {
20416 let snapshot = editor.buffer.read(cx).snapshot(cx);
20417 for buffer_id in snapshot.all_buffer_ids() {
20418 editor.fold_buffer(buffer_id, cx);
20419 }
20420 })
20421 .ok();
20422 });
20423 }
20424 }
20425
20426 pub fn fold_function_bodies(
20427 &mut self,
20428 _: &actions::FoldFunctionBodies,
20429 window: &mut Window,
20430 cx: &mut Context<Self>,
20431 ) {
20432 let snapshot = self.buffer.read(cx).snapshot(cx);
20433
20434 let ranges = snapshot
20435 .text_object_ranges(
20436 MultiBufferOffset(0)..snapshot.len(),
20437 TreeSitterOptions::default(),
20438 )
20439 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
20440 .collect::<Vec<_>>();
20441
20442 let creases = ranges
20443 .into_iter()
20444 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
20445 .collect();
20446
20447 self.fold_creases(creases, true, window, cx);
20448 }
20449
20450 pub fn fold_recursive(
20451 &mut self,
20452 _: &actions::FoldRecursive,
20453 window: &mut Window,
20454 cx: &mut Context<Self>,
20455 ) {
20456 let mut to_fold = Vec::new();
20457 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20458 let selections = self.selections.all_adjusted(&display_map);
20459
20460 for selection in selections {
20461 let range = selection.range().sorted();
20462 let buffer_start_row = range.start.row;
20463
20464 if range.start.row != range.end.row {
20465 let mut found = false;
20466 for row in range.start.row..=range.end.row {
20467 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
20468 found = true;
20469 to_fold.push(crease);
20470 }
20471 }
20472 if found {
20473 continue;
20474 }
20475 }
20476
20477 for row in (0..=range.start.row).rev() {
20478 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
20479 if crease.range().end.row >= buffer_start_row {
20480 to_fold.push(crease);
20481 } else {
20482 break;
20483 }
20484 }
20485 }
20486 }
20487
20488 self.fold_creases(to_fold, true, window, cx);
20489 }
20490
20491 pub fn fold_at(
20492 &mut self,
20493 buffer_row: MultiBufferRow,
20494 window: &mut Window,
20495 cx: &mut Context<Self>,
20496 ) {
20497 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20498
20499 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
20500 let autoscroll = self
20501 .selections
20502 .all::<Point>(&display_map)
20503 .iter()
20504 .any(|selection| crease.range().overlaps(&selection.range()));
20505
20506 self.fold_creases(vec![crease], autoscroll, window, cx);
20507 }
20508 }
20509
20510 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
20511 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
20512 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20513 let buffer = display_map.buffer_snapshot();
20514 let selections = self.selections.all::<Point>(&display_map);
20515 let ranges = selections
20516 .iter()
20517 .map(|s| {
20518 let range = s.display_range(&display_map).sorted();
20519 let mut start = range.start.to_point(&display_map);
20520 let mut end = range.end.to_point(&display_map);
20521 start.column = 0;
20522 end.column = buffer.line_len(MultiBufferRow(end.row));
20523 start..end
20524 })
20525 .collect::<Vec<_>>();
20526
20527 self.unfold_ranges(&ranges, true, true, cx);
20528 } else {
20529 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20530 let buffer_ids = self
20531 .selections
20532 .disjoint_anchor_ranges()
20533 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20534 .collect::<HashSet<_>>();
20535 for buffer_id in buffer_ids {
20536 self.unfold_buffer(buffer_id, cx);
20537 }
20538 }
20539 }
20540
20541 pub fn unfold_recursive(
20542 &mut self,
20543 _: &UnfoldRecursive,
20544 _window: &mut Window,
20545 cx: &mut Context<Self>,
20546 ) {
20547 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20548 let selections = self.selections.all::<Point>(&display_map);
20549 let ranges = selections
20550 .iter()
20551 .map(|s| {
20552 let mut range = s.display_range(&display_map).sorted();
20553 *range.start.column_mut() = 0;
20554 *range.end.column_mut() = display_map.line_len(range.end.row());
20555 let start = range.start.to_point(&display_map);
20556 let end = range.end.to_point(&display_map);
20557 start..end
20558 })
20559 .collect::<Vec<_>>();
20560
20561 self.unfold_ranges(&ranges, true, true, cx);
20562 }
20563
20564 pub fn unfold_at(
20565 &mut self,
20566 buffer_row: MultiBufferRow,
20567 _window: &mut Window,
20568 cx: &mut Context<Self>,
20569 ) {
20570 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20571
20572 let intersection_range = Point::new(buffer_row.0, 0)
20573 ..Point::new(
20574 buffer_row.0,
20575 display_map.buffer_snapshot().line_len(buffer_row),
20576 );
20577
20578 let autoscroll = self
20579 .selections
20580 .all::<Point>(&display_map)
20581 .iter()
20582 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
20583
20584 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
20585 }
20586
20587 pub fn unfold_all(
20588 &mut self,
20589 _: &actions::UnfoldAll,
20590 _window: &mut Window,
20591 cx: &mut Context<Self>,
20592 ) {
20593 if self.buffer.read(cx).is_singleton() {
20594 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20595 self.unfold_ranges(
20596 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
20597 true,
20598 true,
20599 cx,
20600 );
20601 } else {
20602 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
20603 editor
20604 .update(cx, |editor, cx| {
20605 let snapshot = editor.buffer.read(cx).snapshot(cx);
20606 for buffer_id in snapshot.all_buffer_ids() {
20607 editor.unfold_buffer(buffer_id, cx);
20608 }
20609 })
20610 .ok();
20611 });
20612 }
20613 }
20614
20615 pub fn fold_selected_ranges(
20616 &mut self,
20617 _: &FoldSelectedRanges,
20618 window: &mut Window,
20619 cx: &mut Context<Self>,
20620 ) {
20621 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20622 let selections = self.selections.all_adjusted(&display_map);
20623 let ranges = selections
20624 .into_iter()
20625 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
20626 .collect::<Vec<_>>();
20627 self.fold_creases(ranges, true, window, cx);
20628 }
20629
20630 pub fn fold_ranges<T: ToOffset + Clone>(
20631 &mut self,
20632 ranges: Vec<Range<T>>,
20633 auto_scroll: bool,
20634 window: &mut Window,
20635 cx: &mut Context<Self>,
20636 ) {
20637 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20638 let ranges = ranges
20639 .into_iter()
20640 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
20641 .collect::<Vec<_>>();
20642 self.fold_creases(ranges, auto_scroll, window, cx);
20643 }
20644
20645 pub fn fold_creases<T: ToOffset + Clone>(
20646 &mut self,
20647 creases: Vec<Crease<T>>,
20648 auto_scroll: bool,
20649 window: &mut Window,
20650 cx: &mut Context<Self>,
20651 ) {
20652 if creases.is_empty() {
20653 return;
20654 }
20655
20656 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
20657
20658 if auto_scroll {
20659 self.request_autoscroll(Autoscroll::fit(), cx);
20660 }
20661
20662 cx.notify();
20663
20664 self.scrollbar_marker_state.dirty = true;
20665 self.update_data_on_scroll(window, cx);
20666 self.folds_did_change(cx);
20667 }
20668
20669 /// Removes any folds whose ranges intersect any of the given ranges.
20670 pub fn unfold_ranges<T: ToOffset + Clone>(
20671 &mut self,
20672 ranges: &[Range<T>],
20673 inclusive: bool,
20674 auto_scroll: bool,
20675 cx: &mut Context<Self>,
20676 ) {
20677 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20678 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx);
20679 });
20680 self.folds_did_change(cx);
20681 }
20682
20683 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20684 self.fold_buffers([buffer_id], cx);
20685 }
20686
20687 pub fn fold_buffers(
20688 &mut self,
20689 buffer_ids: impl IntoIterator<Item = BufferId>,
20690 cx: &mut Context<Self>,
20691 ) {
20692 if self.buffer().read(cx).is_singleton() {
20693 return;
20694 }
20695
20696 let ids_to_fold: Vec<BufferId> = buffer_ids
20697 .into_iter()
20698 .filter(|id| !self.is_buffer_folded(*id, cx))
20699 .collect();
20700
20701 if ids_to_fold.is_empty() {
20702 return;
20703 }
20704
20705 self.display_map.update(cx, |display_map, cx| {
20706 display_map.fold_buffers(ids_to_fold.clone(), cx)
20707 });
20708
20709 let snapshot = self.display_snapshot(cx);
20710 self.selections.change_with(&snapshot, |selections| {
20711 for buffer_id in ids_to_fold.iter().copied() {
20712 selections.remove_selections_from_buffer(buffer_id);
20713 }
20714 });
20715
20716 cx.emit(EditorEvent::BufferFoldToggled {
20717 ids: ids_to_fold,
20718 folded: true,
20719 });
20720 cx.notify();
20721 }
20722
20723 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20724 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
20725 return;
20726 }
20727 self.display_map.update(cx, |display_map, cx| {
20728 display_map.unfold_buffers([buffer_id], cx);
20729 });
20730 cx.emit(EditorEvent::BufferFoldToggled {
20731 ids: vec![buffer_id],
20732 folded: false,
20733 });
20734 cx.notify();
20735 }
20736
20737 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
20738 self.display_map.read(cx).is_buffer_folded(buffer)
20739 }
20740
20741 pub fn has_any_buffer_folded(&self, cx: &App) -> bool {
20742 if self.buffer().read(cx).is_singleton() {
20743 return false;
20744 }
20745 !self.folded_buffers(cx).is_empty()
20746 }
20747
20748 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
20749 self.display_map.read(cx).folded_buffers()
20750 }
20751
20752 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20753 self.display_map.update(cx, |display_map, cx| {
20754 display_map.disable_header_for_buffer(buffer_id, cx);
20755 });
20756 cx.notify();
20757 }
20758
20759 /// Removes any folds with the given ranges.
20760 pub fn remove_folds_with_type<T: ToOffset + Clone>(
20761 &mut self,
20762 ranges: &[Range<T>],
20763 type_id: TypeId,
20764 auto_scroll: bool,
20765 cx: &mut Context<Self>,
20766 ) {
20767 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20768 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
20769 });
20770 self.folds_did_change(cx);
20771 }
20772
20773 fn remove_folds_with<T: ToOffset + Clone>(
20774 &mut self,
20775 ranges: &[Range<T>],
20776 auto_scroll: bool,
20777 cx: &mut Context<Self>,
20778 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
20779 ) {
20780 if ranges.is_empty() {
20781 return;
20782 }
20783
20784 self.display_map.update(cx, update);
20785
20786 if auto_scroll {
20787 self.request_autoscroll(Autoscroll::fit(), cx);
20788 }
20789
20790 cx.notify();
20791 self.scrollbar_marker_state.dirty = true;
20792 self.active_indent_guides_state.dirty = true;
20793 }
20794
20795 pub fn update_renderer_widths(
20796 &mut self,
20797 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
20798 cx: &mut Context<Self>,
20799 ) -> bool {
20800 self.display_map
20801 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
20802 }
20803
20804 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
20805 self.display_map.read(cx).fold_placeholder.clone()
20806 }
20807
20808 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
20809 self.buffer.update(cx, |buffer, cx| {
20810 buffer.set_all_diff_hunks_expanded(cx);
20811 });
20812 }
20813
20814 pub fn expand_all_diff_hunks(
20815 &mut self,
20816 _: &ExpandAllDiffHunks,
20817 _window: &mut Window,
20818 cx: &mut Context<Self>,
20819 ) {
20820 self.buffer.update(cx, |buffer, cx| {
20821 buffer.expand_diff_hunks(vec![Anchor::Min..Anchor::Max], cx)
20822 });
20823 }
20824
20825 pub fn collapse_all_diff_hunks(
20826 &mut self,
20827 _: &CollapseAllDiffHunks,
20828 _window: &mut Window,
20829 cx: &mut Context<Self>,
20830 ) {
20831 self.buffer.update(cx, |buffer, cx| {
20832 buffer.collapse_diff_hunks(vec![Anchor::Min..Anchor::Max], cx)
20833 });
20834 }
20835
20836 pub fn toggle_selected_diff_hunks(
20837 &mut self,
20838 _: &ToggleSelectedDiffHunks,
20839 _window: &mut Window,
20840 cx: &mut Context<Self>,
20841 ) {
20842 let ranges: Vec<_> = self
20843 .selections
20844 .disjoint_anchors()
20845 .iter()
20846 .map(|s| s.range())
20847 .collect();
20848 self.toggle_diff_hunks_in_ranges(ranges, cx);
20849 }
20850
20851 pub fn diff_hunks_in_ranges<'a>(
20852 &'a self,
20853 ranges: &'a [Range<Anchor>],
20854 buffer: &'a MultiBufferSnapshot,
20855 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
20856 ranges.iter().flat_map(move |range| {
20857 let end_excerpt = buffer.excerpt_containing(range.end..range.end);
20858 let range = range.to_point(buffer);
20859 let mut peek_end = range.end;
20860 if range.end.row < buffer.max_row().0 {
20861 peek_end = Point::new(range.end.row + 1, 0);
20862 }
20863 buffer
20864 .diff_hunks_in_range(range.start..peek_end)
20865 .filter(move |hunk| {
20866 if let Some((_, excerpt_range)) = &end_excerpt
20867 && let Some(end_anchor) =
20868 buffer.anchor_in_excerpt(excerpt_range.context.end)
20869 && let Some(hunk_end_anchor) =
20870 buffer.anchor_in_excerpt(hunk.excerpt_range.context.end)
20871 && hunk_end_anchor.cmp(&end_anchor, buffer).is_gt()
20872 {
20873 false
20874 } else {
20875 true
20876 }
20877 })
20878 })
20879 }
20880
20881 pub fn has_stageable_diff_hunks_in_ranges(
20882 &self,
20883 ranges: &[Range<Anchor>],
20884 snapshot: &MultiBufferSnapshot,
20885 ) -> bool {
20886 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
20887 hunks.any(|hunk| hunk.status().has_secondary_hunk())
20888 }
20889
20890 pub fn toggle_staged_selected_diff_hunks(
20891 &mut self,
20892 _: &::git::ToggleStaged,
20893 _: &mut Window,
20894 cx: &mut Context<Self>,
20895 ) {
20896 let snapshot = self.buffer.read(cx).snapshot(cx);
20897 let ranges: Vec<_> = self
20898 .selections
20899 .disjoint_anchors()
20900 .iter()
20901 .map(|s| s.range())
20902 .collect();
20903 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
20904 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20905 }
20906
20907 pub fn set_render_diff_hunk_controls(
20908 &mut self,
20909 render_diff_hunk_controls: RenderDiffHunkControlsFn,
20910 cx: &mut Context<Self>,
20911 ) {
20912 self.render_diff_hunk_controls = render_diff_hunk_controls;
20913 cx.notify();
20914 }
20915
20916 pub fn stage_and_next(
20917 &mut self,
20918 _: &::git::StageAndNext,
20919 window: &mut Window,
20920 cx: &mut Context<Self>,
20921 ) {
20922 self.do_stage_or_unstage_and_next(true, window, cx);
20923 }
20924
20925 pub fn unstage_and_next(
20926 &mut self,
20927 _: &::git::UnstageAndNext,
20928 window: &mut Window,
20929 cx: &mut Context<Self>,
20930 ) {
20931 self.do_stage_or_unstage_and_next(false, window, cx);
20932 }
20933
20934 pub fn stage_or_unstage_diff_hunks(
20935 &mut self,
20936 stage: bool,
20937 ranges: Vec<Range<Anchor>>,
20938 cx: &mut Context<Self>,
20939 ) {
20940 if self.delegate_stage_and_restore {
20941 let snapshot = self.buffer.read(cx).snapshot(cx);
20942 let hunks: Vec<_> = self.diff_hunks_in_ranges(&ranges, &snapshot).collect();
20943 if !hunks.is_empty() {
20944 cx.emit(EditorEvent::StageOrUnstageRequested { stage, hunks });
20945 }
20946 return;
20947 }
20948 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
20949 cx.spawn(async move |this, cx| {
20950 task.await?;
20951 this.update(cx, |this, cx| {
20952 let snapshot = this.buffer.read(cx).snapshot(cx);
20953 let chunk_by = this
20954 .diff_hunks_in_ranges(&ranges, &snapshot)
20955 .chunk_by(|hunk| hunk.buffer_id);
20956 for (buffer_id, hunks) in &chunk_by {
20957 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
20958 }
20959 })
20960 })
20961 .detach_and_log_err(cx);
20962 }
20963
20964 fn save_buffers_for_ranges_if_needed(
20965 &mut self,
20966 ranges: &[Range<Anchor>],
20967 cx: &mut Context<Editor>,
20968 ) -> Task<Result<()>> {
20969 let multibuffer = self.buffer.read(cx);
20970 let snapshot = multibuffer.read(cx);
20971 let buffer_ids: HashSet<_> = ranges
20972 .iter()
20973 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
20974 .collect();
20975 drop(snapshot);
20976
20977 let mut buffers = HashSet::default();
20978 for buffer_id in buffer_ids {
20979 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
20980 let buffer = buffer_entity.read(cx);
20981 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
20982 {
20983 buffers.insert(buffer_entity);
20984 }
20985 }
20986 }
20987
20988 if let Some(project) = &self.project {
20989 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
20990 } else {
20991 Task::ready(Ok(()))
20992 }
20993 }
20994
20995 fn do_stage_or_unstage_and_next(
20996 &mut self,
20997 stage: bool,
20998 window: &mut Window,
20999 cx: &mut Context<Self>,
21000 ) {
21001 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
21002
21003 if ranges.iter().any(|range| range.start != range.end) {
21004 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
21005 return;
21006 }
21007
21008 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
21009
21010 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
21011 let wrap_around = !all_diff_hunks_expanded;
21012 let snapshot = self.snapshot(window, cx);
21013 let position = self
21014 .selections
21015 .newest::<Point>(&snapshot.display_snapshot)
21016 .head();
21017
21018 self.go_to_hunk_before_or_after_position(
21019 &snapshot,
21020 position,
21021 Direction::Next,
21022 wrap_around,
21023 window,
21024 cx,
21025 );
21026 }
21027
21028 pub(crate) fn do_stage_or_unstage(
21029 &self,
21030 stage: bool,
21031 buffer_id: BufferId,
21032 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
21033 cx: &mut App,
21034 ) -> Option<()> {
21035 let project = self.project()?;
21036 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
21037 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
21038 let buffer_snapshot = buffer.read(cx).snapshot();
21039 let file_exists = buffer_snapshot
21040 .file()
21041 .is_some_and(|file| file.disk_state().exists());
21042 diff.update(cx, |diff, cx| {
21043 diff.stage_or_unstage_hunks(
21044 stage,
21045 &hunks
21046 .map(|hunk| buffer_diff::DiffHunk {
21047 buffer_range: hunk.buffer_range,
21048 // We don't need to pass in word diffs here because they're only used for rendering and
21049 // this function changes internal state
21050 base_word_diffs: Vec::default(),
21051 buffer_word_diffs: Vec::default(),
21052 diff_base_byte_range: hunk.diff_base_byte_range.start.0
21053 ..hunk.diff_base_byte_range.end.0,
21054 secondary_status: hunk.status.secondary,
21055 range: Point::zero()..Point::zero(), // unused
21056 })
21057 .collect::<Vec<_>>(),
21058 &buffer_snapshot,
21059 file_exists,
21060 cx,
21061 )
21062 });
21063 None
21064 }
21065
21066 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
21067 let ranges: Vec<_> = self
21068 .selections
21069 .disjoint_anchors()
21070 .iter()
21071 .map(|s| s.range())
21072 .collect();
21073 self.buffer
21074 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
21075 }
21076
21077 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
21078 self.buffer.update(cx, |buffer, cx| {
21079 let ranges = vec![Anchor::Min..Anchor::Max];
21080 if !buffer.all_diff_hunks_expanded()
21081 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
21082 {
21083 buffer.collapse_diff_hunks(ranges, cx);
21084 true
21085 } else {
21086 false
21087 }
21088 })
21089 }
21090
21091 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
21092 if self.buffer.read(cx).all_diff_hunks_expanded() {
21093 return true;
21094 }
21095 let ranges = vec![Anchor::Min..Anchor::Max];
21096 self.buffer
21097 .read(cx)
21098 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
21099 }
21100
21101 fn toggle_diff_hunks_in_ranges(
21102 &mut self,
21103 ranges: Vec<Range<Anchor>>,
21104 cx: &mut Context<Editor>,
21105 ) {
21106 self.buffer.update(cx, |buffer, cx| {
21107 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
21108 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
21109 })
21110 }
21111
21112 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
21113 self.buffer.update(cx, |buffer, cx| {
21114 buffer.toggle_single_diff_hunk(range, cx);
21115 })
21116 }
21117
21118 pub(crate) fn apply_all_diff_hunks(
21119 &mut self,
21120 _: &ApplyAllDiffHunks,
21121 window: &mut Window,
21122 cx: &mut Context<Self>,
21123 ) {
21124 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
21125
21126 let buffers = self.buffer.read(cx).all_buffers();
21127 for branch_buffer in buffers {
21128 branch_buffer.update(cx, |branch_buffer, cx| {
21129 branch_buffer.merge_into_base(Vec::new(), cx);
21130 });
21131 }
21132
21133 if let Some(project) = self.project.clone() {
21134 self.save(
21135 SaveOptions {
21136 format: true,
21137 autosave: false,
21138 },
21139 project,
21140 window,
21141 cx,
21142 )
21143 .detach_and_log_err(cx);
21144 }
21145 }
21146
21147 pub(crate) fn apply_selected_diff_hunks(
21148 &mut self,
21149 _: &ApplyDiffHunk,
21150 window: &mut Window,
21151 cx: &mut Context<Self>,
21152 ) {
21153 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
21154 let snapshot = self.snapshot(window, cx);
21155 let hunks = snapshot.hunks_for_ranges(
21156 self.selections
21157 .all(&snapshot.display_snapshot)
21158 .into_iter()
21159 .map(|selection| selection.range()),
21160 );
21161 let mut ranges_by_buffer = HashMap::default();
21162 self.transact(window, cx, |editor, _window, cx| {
21163 for hunk in hunks {
21164 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
21165 ranges_by_buffer
21166 .entry(buffer.clone())
21167 .or_insert_with(Vec::new)
21168 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
21169 }
21170 }
21171
21172 for (buffer, ranges) in ranges_by_buffer {
21173 buffer.update(cx, |buffer, cx| {
21174 buffer.merge_into_base(ranges, cx);
21175 });
21176 }
21177 });
21178
21179 if let Some(project) = self.project.clone() {
21180 self.save(
21181 SaveOptions {
21182 format: true,
21183 autosave: false,
21184 },
21185 project,
21186 window,
21187 cx,
21188 )
21189 .detach_and_log_err(cx);
21190 }
21191 }
21192
21193 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
21194 if hovered != self.gutter_hovered {
21195 self.gutter_hovered = hovered;
21196 cx.notify();
21197 }
21198 }
21199
21200 pub fn insert_blocks(
21201 &mut self,
21202 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
21203 autoscroll: Option<Autoscroll>,
21204 cx: &mut Context<Self>,
21205 ) -> Vec<CustomBlockId> {
21206 let blocks = self
21207 .display_map
21208 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
21209 if let Some(autoscroll) = autoscroll {
21210 self.request_autoscroll(autoscroll, cx);
21211 }
21212 cx.notify();
21213 blocks
21214 }
21215
21216 pub fn resize_blocks(
21217 &mut self,
21218 heights: HashMap<CustomBlockId, u32>,
21219 autoscroll: Option<Autoscroll>,
21220 cx: &mut Context<Self>,
21221 ) {
21222 self.display_map
21223 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
21224 if let Some(autoscroll) = autoscroll {
21225 self.request_autoscroll(autoscroll, cx);
21226 }
21227 cx.notify();
21228 }
21229
21230 pub fn replace_blocks(
21231 &mut self,
21232 renderers: HashMap<CustomBlockId, RenderBlock>,
21233 autoscroll: Option<Autoscroll>,
21234 cx: &mut Context<Self>,
21235 ) {
21236 self.display_map
21237 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
21238 if let Some(autoscroll) = autoscroll {
21239 self.request_autoscroll(autoscroll, cx);
21240 }
21241 cx.notify();
21242 }
21243
21244 pub fn remove_blocks(
21245 &mut self,
21246 block_ids: HashSet<CustomBlockId>,
21247 autoscroll: Option<Autoscroll>,
21248 cx: &mut Context<Self>,
21249 ) {
21250 self.display_map.update(cx, |display_map, cx| {
21251 display_map.remove_blocks(block_ids, cx)
21252 });
21253 if let Some(autoscroll) = autoscroll {
21254 self.request_autoscroll(autoscroll, cx);
21255 }
21256 cx.notify();
21257 }
21258
21259 pub fn row_for_block(
21260 &self,
21261 block_id: CustomBlockId,
21262 cx: &mut Context<Self>,
21263 ) -> Option<DisplayRow> {
21264 self.display_map
21265 .update(cx, |map, cx| map.row_for_block(block_id, cx))
21266 }
21267
21268 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
21269 self.focused_block = Some(focused_block);
21270 }
21271
21272 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
21273 self.focused_block.take()
21274 }
21275
21276 pub fn insert_creases(
21277 &mut self,
21278 creases: impl IntoIterator<Item = Crease<Anchor>>,
21279 cx: &mut Context<Self>,
21280 ) -> Vec<CreaseId> {
21281 self.display_map
21282 .update(cx, |map, cx| map.insert_creases(creases, cx))
21283 }
21284
21285 pub fn remove_creases(
21286 &mut self,
21287 ids: impl IntoIterator<Item = CreaseId>,
21288 cx: &mut Context<Self>,
21289 ) -> Vec<(CreaseId, Range<Anchor>)> {
21290 self.display_map
21291 .update(cx, |map, cx| map.remove_creases(ids, cx))
21292 }
21293
21294 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
21295 self.display_map
21296 .update(cx, |map, cx| map.snapshot(cx))
21297 .longest_row()
21298 }
21299
21300 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
21301 self.display_map
21302 .update(cx, |map, cx| map.snapshot(cx))
21303 .max_point()
21304 }
21305
21306 pub fn text(&self, cx: &App) -> String {
21307 self.buffer.read(cx).read(cx).text()
21308 }
21309
21310 pub fn is_empty(&self, cx: &App) -> bool {
21311 self.buffer.read(cx).read(cx).is_empty()
21312 }
21313
21314 pub fn text_option(&self, cx: &App) -> Option<String> {
21315 let text = self.text(cx);
21316 let text = text.trim();
21317
21318 if text.is_empty() {
21319 return None;
21320 }
21321
21322 Some(text.to_string())
21323 }
21324
21325 pub fn set_text(
21326 &mut self,
21327 text: impl Into<Arc<str>>,
21328 window: &mut Window,
21329 cx: &mut Context<Self>,
21330 ) {
21331 self.transact(window, cx, |this, _, cx| {
21332 this.buffer
21333 .read(cx)
21334 .as_singleton()
21335 .expect("you can only call set_text on editors for singleton buffers")
21336 .update(cx, |buffer, cx| buffer.set_text(text, cx));
21337 });
21338 }
21339
21340 pub fn display_text(&self, cx: &mut App) -> String {
21341 self.display_map
21342 .update(cx, |map, cx| map.snapshot(cx))
21343 .text()
21344 }
21345
21346 fn create_minimap(
21347 &self,
21348 minimap_settings: MinimapSettings,
21349 window: &mut Window,
21350 cx: &mut Context<Self>,
21351 ) -> Option<Entity<Self>> {
21352 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
21353 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
21354 }
21355
21356 fn initialize_new_minimap(
21357 &self,
21358 minimap_settings: MinimapSettings,
21359 window: &mut Window,
21360 cx: &mut Context<Self>,
21361 ) -> Entity<Self> {
21362 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
21363 const MINIMAP_FONT_FAMILY: SharedString = SharedString::new_static(".ZedMono");
21364
21365 let mut minimap = Editor::new_internal(
21366 EditorMode::Minimap {
21367 parent: cx.weak_entity(),
21368 },
21369 self.buffer.clone(),
21370 None,
21371 Some(self.display_map.clone()),
21372 window,
21373 cx,
21374 );
21375 let my_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
21376 let minimap_snapshot = minimap.display_map.update(cx, |map, cx| map.snapshot(cx));
21377 minimap.scroll_manager.clone_state(
21378 &self.scroll_manager,
21379 &my_snapshot,
21380 &minimap_snapshot,
21381 cx,
21382 );
21383 minimap.set_text_style_refinement(TextStyleRefinement {
21384 font_size: Some(MINIMAP_FONT_SIZE),
21385 font_weight: Some(MINIMAP_FONT_WEIGHT),
21386 font_family: Some(MINIMAP_FONT_FAMILY),
21387 ..Default::default()
21388 });
21389 minimap.update_minimap_configuration(minimap_settings, cx);
21390 cx.new(|_| minimap)
21391 }
21392
21393 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
21394 let current_line_highlight = minimap_settings
21395 .current_line_highlight
21396 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
21397 self.set_current_line_highlight(Some(current_line_highlight));
21398 }
21399
21400 pub fn minimap(&self) -> Option<&Entity<Self>> {
21401 self.minimap
21402 .as_ref()
21403 .filter(|_| self.minimap_visibility.visible())
21404 }
21405
21406 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
21407 let mut wrap_guides = smallvec![];
21408
21409 if self.show_wrap_guides == Some(false) {
21410 return wrap_guides;
21411 }
21412
21413 let settings = self.buffer.read(cx).language_settings(cx);
21414 if settings.show_wrap_guides {
21415 match self.soft_wrap_mode(cx) {
21416 SoftWrap::Column(soft_wrap) => {
21417 wrap_guides.push((soft_wrap as usize, true));
21418 }
21419 SoftWrap::Bounded(soft_wrap) => {
21420 wrap_guides.push((soft_wrap as usize, true));
21421 }
21422 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
21423 }
21424 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
21425 }
21426
21427 wrap_guides
21428 }
21429
21430 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
21431 let settings = self.buffer.read(cx).language_settings(cx);
21432 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
21433 match mode {
21434 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
21435 SoftWrap::None
21436 }
21437 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
21438 language_settings::SoftWrap::PreferredLineLength => {
21439 SoftWrap::Column(settings.preferred_line_length)
21440 }
21441 language_settings::SoftWrap::Bounded => {
21442 SoftWrap::Bounded(settings.preferred_line_length)
21443 }
21444 }
21445 }
21446
21447 pub fn set_soft_wrap_mode(
21448 &mut self,
21449 mode: language_settings::SoftWrap,
21450 cx: &mut Context<Self>,
21451 ) {
21452 self.soft_wrap_mode_override = Some(mode);
21453 cx.notify();
21454 }
21455
21456 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
21457 self.hard_wrap = hard_wrap;
21458 cx.notify();
21459 }
21460
21461 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
21462 self.text_style_refinement = Some(style);
21463 }
21464
21465 /// called by the Element so we know what style we were most recently rendered with.
21466 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
21467 // We intentionally do not inform the display map about the minimap style
21468 // so that wrapping is not recalculated and stays consistent for the editor
21469 // and its linked minimap.
21470 if !self.mode.is_minimap() {
21471 let font = style.text.font();
21472 let font_size = style.text.font_size.to_pixels(window.rem_size());
21473 let display_map = self
21474 .placeholder_display_map
21475 .as_ref()
21476 .filter(|_| self.is_empty(cx))
21477 .unwrap_or(&self.display_map);
21478
21479 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
21480 }
21481 self.style = Some(style);
21482 }
21483
21484 pub fn style(&mut self, cx: &App) -> &EditorStyle {
21485 if self.style.is_none() {
21486 self.style = Some(self.create_style(cx));
21487 }
21488 self.style.as_ref().unwrap()
21489 }
21490
21491 // Called by the element. This method is not designed to be called outside of the editor
21492 // element's layout code because it does not notify when rewrapping is computed synchronously.
21493 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
21494 if self.is_empty(cx) {
21495 self.placeholder_display_map
21496 .as_ref()
21497 .map_or(false, |display_map| {
21498 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
21499 })
21500 } else {
21501 self.display_map
21502 .update(cx, |map, cx| map.set_wrap_width(width, cx))
21503 }
21504 }
21505
21506 pub fn set_soft_wrap(&mut self) {
21507 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
21508 }
21509
21510 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
21511 if self.soft_wrap_mode_override.is_some() {
21512 self.soft_wrap_mode_override.take();
21513 } else {
21514 let soft_wrap = match self.soft_wrap_mode(cx) {
21515 SoftWrap::GitDiff => return,
21516 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
21517 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
21518 language_settings::SoftWrap::None
21519 }
21520 };
21521 self.soft_wrap_mode_override = Some(soft_wrap);
21522 }
21523 cx.notify();
21524 }
21525
21526 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
21527 let Some(workspace) = self.workspace() else {
21528 return;
21529 };
21530 let fs = workspace.read(cx).app_state().fs.clone();
21531 let current_show = TabBarSettings::get_global(cx).show;
21532 update_settings_file(fs, cx, move |setting, _| {
21533 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
21534 });
21535 }
21536
21537 pub fn toggle_indent_guides(
21538 &mut self,
21539 _: &ToggleIndentGuides,
21540 _: &mut Window,
21541 cx: &mut Context<Self>,
21542 ) {
21543 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
21544 self.buffer
21545 .read(cx)
21546 .language_settings(cx)
21547 .indent_guides
21548 .enabled
21549 });
21550 self.show_indent_guides = Some(!currently_enabled);
21551 cx.notify();
21552 }
21553
21554 fn should_show_indent_guides(&self) -> Option<bool> {
21555 self.show_indent_guides
21556 }
21557
21558 pub fn disable_indent_guides_for_buffer(
21559 &mut self,
21560 buffer_id: BufferId,
21561 cx: &mut Context<Self>,
21562 ) {
21563 self.buffers_with_disabled_indent_guides.insert(buffer_id);
21564 cx.notify();
21565 }
21566
21567 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
21568 self.buffers_with_disabled_indent_guides
21569 .contains(&buffer_id)
21570 }
21571
21572 pub fn toggle_line_numbers(
21573 &mut self,
21574 _: &ToggleLineNumbers,
21575 _: &mut Window,
21576 cx: &mut Context<Self>,
21577 ) {
21578 let mut editor_settings = EditorSettings::get_global(cx).clone();
21579 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
21580 EditorSettings::override_global(editor_settings, cx);
21581 }
21582
21583 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
21584 if let Some(show_line_numbers) = self.show_line_numbers {
21585 return show_line_numbers;
21586 }
21587 EditorSettings::get_global(cx).gutter.line_numbers
21588 }
21589
21590 pub fn relative_line_numbers(&self, cx: &App) -> RelativeLineNumbers {
21591 match (
21592 self.use_relative_line_numbers,
21593 EditorSettings::get_global(cx).relative_line_numbers,
21594 ) {
21595 (None, setting) => setting,
21596 (Some(false), _) => RelativeLineNumbers::Disabled,
21597 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
21598 (Some(true), _) => RelativeLineNumbers::Enabled,
21599 }
21600 }
21601
21602 pub fn toggle_relative_line_numbers(
21603 &mut self,
21604 _: &ToggleRelativeLineNumbers,
21605 _: &mut Window,
21606 cx: &mut Context<Self>,
21607 ) {
21608 let is_relative = self.relative_line_numbers(cx);
21609 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
21610 }
21611
21612 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
21613 self.use_relative_line_numbers = is_relative;
21614 cx.notify();
21615 }
21616
21617 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
21618 self.show_gutter = show_gutter;
21619 cx.notify();
21620 }
21621
21622 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
21623 self.show_scrollbars = ScrollbarAxes {
21624 horizontal: show,
21625 vertical: show,
21626 };
21627 cx.notify();
21628 }
21629
21630 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21631 self.show_scrollbars.vertical = show;
21632 cx.notify();
21633 }
21634
21635 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21636 self.show_scrollbars.horizontal = show;
21637 cx.notify();
21638 }
21639
21640 pub fn set_minimap_visibility(
21641 &mut self,
21642 minimap_visibility: MinimapVisibility,
21643 window: &mut Window,
21644 cx: &mut Context<Self>,
21645 ) {
21646 if self.minimap_visibility != minimap_visibility {
21647 if minimap_visibility.visible() && self.minimap.is_none() {
21648 let minimap_settings = EditorSettings::get_global(cx).minimap;
21649 self.minimap =
21650 self.create_minimap(minimap_settings.with_show_override(), window, cx);
21651 }
21652 self.minimap_visibility = minimap_visibility;
21653 cx.notify();
21654 }
21655 }
21656
21657 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21658 self.set_show_scrollbars(false, cx);
21659 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
21660 }
21661
21662 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21663 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
21664 }
21665
21666 /// Normally the text in full mode and auto height editors is padded on the
21667 /// left side by roughly half a character width for improved hit testing.
21668 ///
21669 /// Use this method to disable this for cases where this is not wanted (e.g.
21670 /// if you want to align the editor text with some other text above or below)
21671 /// or if you want to add this padding to single-line editors.
21672 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
21673 self.offset_content = offset_content;
21674 cx.notify();
21675 }
21676
21677 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
21678 self.show_line_numbers = Some(show_line_numbers);
21679 cx.notify();
21680 }
21681
21682 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
21683 self.disable_expand_excerpt_buttons = true;
21684 cx.notify();
21685 }
21686
21687 pub fn set_number_deleted_lines(&mut self, number: bool, cx: &mut Context<Self>) {
21688 self.number_deleted_lines = number;
21689 cx.notify();
21690 }
21691
21692 pub fn set_delegate_expand_excerpts(&mut self, delegate: bool) {
21693 self.delegate_expand_excerpts = delegate;
21694 }
21695
21696 pub fn set_delegate_stage_and_restore(&mut self, delegate: bool) {
21697 self.delegate_stage_and_restore = delegate;
21698 }
21699
21700 pub fn set_delegate_open_excerpts(&mut self, delegate: bool) {
21701 self.delegate_open_excerpts = delegate;
21702 }
21703
21704 pub fn set_on_local_selections_changed(
21705 &mut self,
21706 callback: Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
21707 ) {
21708 self.on_local_selections_changed = callback;
21709 }
21710
21711 pub fn set_suppress_selection_callback(&mut self, suppress: bool) {
21712 self.suppress_selection_callback = suppress;
21713 }
21714
21715 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
21716 self.show_git_diff_gutter = Some(show_git_diff_gutter);
21717 cx.notify();
21718 }
21719
21720 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
21721 self.show_code_actions = Some(show_code_actions);
21722 cx.notify();
21723 }
21724
21725 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
21726 self.show_runnables = Some(show_runnables);
21727 cx.notify();
21728 }
21729
21730 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
21731 self.show_breakpoints = Some(show_breakpoints);
21732 cx.notify();
21733 }
21734
21735 pub fn set_show_diff_review_button(&mut self, show: bool, cx: &mut Context<Self>) {
21736 self.show_diff_review_button = show;
21737 cx.notify();
21738 }
21739
21740 pub fn show_diff_review_button(&self) -> bool {
21741 self.show_diff_review_button
21742 }
21743
21744 pub fn render_diff_review_button(
21745 &self,
21746 display_row: DisplayRow,
21747 width: Pixels,
21748 cx: &mut Context<Self>,
21749 ) -> impl IntoElement {
21750 let text_color = cx.theme().colors().text;
21751 let icon_color = cx.theme().colors().icon_accent;
21752
21753 h_flex()
21754 .id("diff_review_button")
21755 .cursor_pointer()
21756 .w(width - px(1.))
21757 .h(relative(0.9))
21758 .justify_center()
21759 .rounded_sm()
21760 .border_1()
21761 .border_color(text_color.opacity(0.1))
21762 .bg(text_color.opacity(0.15))
21763 .hover(|s| {
21764 s.bg(icon_color.opacity(0.4))
21765 .border_color(icon_color.opacity(0.5))
21766 })
21767 .child(Icon::new(IconName::Plus).size(IconSize::Small))
21768 .tooltip(Tooltip::text("Add Review (drag to select multiple lines)"))
21769 .on_mouse_down(
21770 gpui::MouseButton::Left,
21771 cx.listener(move |editor, _event: &gpui::MouseDownEvent, window, cx| {
21772 editor.start_diff_review_drag(display_row, window, cx);
21773 }),
21774 )
21775 }
21776
21777 pub fn start_diff_review_drag(
21778 &mut self,
21779 display_row: DisplayRow,
21780 window: &mut Window,
21781 cx: &mut Context<Self>,
21782 ) {
21783 let snapshot = self.snapshot(window, cx);
21784 let point = snapshot
21785 .display_snapshot
21786 .display_point_to_point(DisplayPoint::new(display_row, 0), Bias::Left);
21787 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21788 self.diff_review_drag_state = Some(DiffReviewDragState {
21789 start_anchor: anchor,
21790 current_anchor: anchor,
21791 });
21792 cx.notify();
21793 }
21794
21795 pub fn update_diff_review_drag(
21796 &mut self,
21797 display_row: DisplayRow,
21798 window: &mut Window,
21799 cx: &mut Context<Self>,
21800 ) {
21801 if self.diff_review_drag_state.is_none() {
21802 return;
21803 }
21804 let snapshot = self.snapshot(window, cx);
21805 let point = snapshot
21806 .display_snapshot
21807 .display_point_to_point(display_row.as_display_point(), Bias::Left);
21808 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21809 if let Some(drag_state) = &mut self.diff_review_drag_state {
21810 drag_state.current_anchor = anchor;
21811 cx.notify();
21812 }
21813 }
21814
21815 pub fn end_diff_review_drag(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21816 if let Some(drag_state) = self.diff_review_drag_state.take() {
21817 let snapshot = self.snapshot(window, cx);
21818 let range = drag_state.row_range(&snapshot.display_snapshot);
21819 self.show_diff_review_overlay(*range.start()..*range.end(), window, cx);
21820 }
21821 cx.notify();
21822 }
21823
21824 pub fn cancel_diff_review_drag(&mut self, cx: &mut Context<Self>) {
21825 self.diff_review_drag_state = None;
21826 cx.notify();
21827 }
21828
21829 /// Calculates the appropriate block height for the diff review overlay.
21830 /// Height is in lines: 2 for input row, 1 for header when comments exist,
21831 /// and 2 lines per comment when expanded.
21832 fn calculate_overlay_height(
21833 &self,
21834 hunk_key: &DiffHunkKey,
21835 comments_expanded: bool,
21836 snapshot: &MultiBufferSnapshot,
21837 ) -> u32 {
21838 let comment_count = self.hunk_comment_count(hunk_key, snapshot);
21839 let base_height: u32 = 2; // Input row with avatar and buttons
21840
21841 if comment_count == 0 {
21842 base_height
21843 } else if comments_expanded {
21844 // Header (1 line) + 2 lines per comment
21845 base_height + 1 + (comment_count as u32 * 2)
21846 } else {
21847 // Just header when collapsed
21848 base_height + 1
21849 }
21850 }
21851
21852 pub fn show_diff_review_overlay(
21853 &mut self,
21854 display_range: Range<DisplayRow>,
21855 window: &mut Window,
21856 cx: &mut Context<Self>,
21857 ) {
21858 let Range { start, end } = display_range.sorted();
21859
21860 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21861 let editor_snapshot = self.snapshot(window, cx);
21862
21863 // Convert display rows to multibuffer points
21864 let start_point = editor_snapshot
21865 .display_snapshot
21866 .display_point_to_point(start.as_display_point(), Bias::Left);
21867 let end_point = editor_snapshot
21868 .display_snapshot
21869 .display_point_to_point(end.as_display_point(), Bias::Left);
21870 let end_multi_buffer_row = MultiBufferRow(end_point.row);
21871
21872 // Create anchor range for the selected lines (start of first line to end of last line)
21873 let line_end = Point::new(
21874 end_point.row,
21875 buffer_snapshot.line_len(end_multi_buffer_row),
21876 );
21877 let anchor_range =
21878 buffer_snapshot.anchor_after(start_point)..buffer_snapshot.anchor_before(line_end);
21879
21880 // Compute the hunk key for this display row
21881 let file_path = buffer_snapshot
21882 .file_at(start_point)
21883 .map(|file: &Arc<dyn language::File>| file.path().clone())
21884 .unwrap_or_else(|| Arc::from(util::rel_path::RelPath::empty()));
21885 let hunk_start_anchor = buffer_snapshot.anchor_before(start_point);
21886 let new_hunk_key = DiffHunkKey {
21887 file_path,
21888 hunk_start_anchor,
21889 };
21890
21891 // Check if we already have an overlay for this hunk
21892 if let Some(existing_overlay) = self.diff_review_overlays.iter().find(|overlay| {
21893 Self::hunk_keys_match(&overlay.hunk_key, &new_hunk_key, &buffer_snapshot)
21894 }) {
21895 // Just focus the existing overlay's prompt editor
21896 let focus_handle = existing_overlay.prompt_editor.focus_handle(cx);
21897 window.focus(&focus_handle, cx);
21898 return;
21899 }
21900
21901 // Dismiss overlays that have no comments for their hunks
21902 self.dismiss_overlays_without_comments(cx);
21903
21904 // Get the current user's avatar URI from the project's user_store
21905 let user_avatar_uri = self.project.as_ref().and_then(|project| {
21906 let user_store = project.read(cx).user_store();
21907 user_store
21908 .read(cx)
21909 .current_user()
21910 .map(|user| user.avatar_uri.clone())
21911 });
21912
21913 // Create anchor at the end of the last row so the block appears immediately below it
21914 // Use multibuffer coordinates for anchor creation
21915 let line_len = buffer_snapshot.line_len(end_multi_buffer_row);
21916 let anchor = buffer_snapshot.anchor_after(Point::new(end_multi_buffer_row.0, line_len));
21917
21918 // Use the hunk key we already computed
21919 let hunk_key = new_hunk_key;
21920
21921 // Create the prompt editor for the review input
21922 let prompt_editor = cx.new(|cx| {
21923 let mut editor = Editor::single_line(window, cx);
21924 editor.set_placeholder_text("Add a review comment...", window, cx);
21925 editor
21926 });
21927
21928 // Register the Newline action on the prompt editor to submit the review
21929 let parent_editor = cx.entity().downgrade();
21930 let subscription = prompt_editor.update(cx, |prompt_editor, _cx| {
21931 prompt_editor.register_action({
21932 let parent_editor = parent_editor.clone();
21933 move |_: &crate::actions::Newline, window, cx| {
21934 if let Some(editor) = parent_editor.upgrade() {
21935 editor.update(cx, |editor, cx| {
21936 editor.submit_diff_review_comment(window, cx);
21937 });
21938 }
21939 }
21940 })
21941 });
21942
21943 // Calculate initial height based on existing comments for this hunk
21944 let initial_height = self.calculate_overlay_height(&hunk_key, true, &buffer_snapshot);
21945
21946 // Create the overlay block
21947 let prompt_editor_for_render = prompt_editor.clone();
21948 let hunk_key_for_render = hunk_key.clone();
21949 let editor_handle = cx.entity().downgrade();
21950 let block = BlockProperties {
21951 style: BlockStyle::Sticky,
21952 placement: BlockPlacement::Below(anchor),
21953 height: Some(initial_height),
21954 render: Arc::new(move |cx| {
21955 Self::render_diff_review_overlay(
21956 &prompt_editor_for_render,
21957 &hunk_key_for_render,
21958 &editor_handle,
21959 cx,
21960 )
21961 }),
21962 priority: 0,
21963 };
21964
21965 let block_ids = self.insert_blocks([block], None, cx);
21966 let Some(block_id) = block_ids.into_iter().next() else {
21967 log::error!("Failed to insert diff review overlay block");
21968 return;
21969 };
21970
21971 self.diff_review_overlays.push(DiffReviewOverlay {
21972 anchor_range,
21973 block_id,
21974 prompt_editor: prompt_editor.clone(),
21975 hunk_key,
21976 comments_expanded: true,
21977 inline_edit_editors: HashMap::default(),
21978 inline_edit_subscriptions: HashMap::default(),
21979 user_avatar_uri,
21980 _subscription: subscription,
21981 });
21982
21983 // Focus the prompt editor
21984 let focus_handle = prompt_editor.focus_handle(cx);
21985 window.focus(&focus_handle, cx);
21986
21987 cx.notify();
21988 }
21989
21990 /// Dismisses all diff review overlays.
21991 pub fn dismiss_all_diff_review_overlays(&mut self, cx: &mut Context<Self>) {
21992 if self.diff_review_overlays.is_empty() {
21993 return;
21994 }
21995 let block_ids: HashSet<_> = self
21996 .diff_review_overlays
21997 .drain(..)
21998 .map(|overlay| overlay.block_id)
21999 .collect();
22000 self.remove_blocks(block_ids, None, cx);
22001 cx.notify();
22002 }
22003
22004 /// Dismisses overlays that have no comments stored for their hunks.
22005 /// Keeps overlays that have at least one comment.
22006 fn dismiss_overlays_without_comments(&mut self, cx: &mut Context<Self>) {
22007 let snapshot = self.buffer.read(cx).snapshot(cx);
22008
22009 // First, compute which overlays have comments (to avoid borrow issues with retain)
22010 let overlays_with_comments: Vec<bool> = self
22011 .diff_review_overlays
22012 .iter()
22013 .map(|overlay| self.hunk_comment_count(&overlay.hunk_key, &snapshot) > 0)
22014 .collect();
22015
22016 // Now collect block IDs to remove and retain overlays
22017 let mut block_ids_to_remove = HashSet::default();
22018 let mut index = 0;
22019 self.diff_review_overlays.retain(|overlay| {
22020 let has_comments = overlays_with_comments[index];
22021 index += 1;
22022 if !has_comments {
22023 block_ids_to_remove.insert(overlay.block_id);
22024 }
22025 has_comments
22026 });
22027
22028 if !block_ids_to_remove.is_empty() {
22029 self.remove_blocks(block_ids_to_remove, None, cx);
22030 cx.notify();
22031 }
22032 }
22033
22034 /// Refreshes the diff review overlay block to update its height and render function.
22035 /// Uses resize_blocks and replace_blocks to avoid visual flicker from remove+insert.
22036 fn refresh_diff_review_overlay_height(
22037 &mut self,
22038 hunk_key: &DiffHunkKey,
22039 _window: &mut Window,
22040 cx: &mut Context<Self>,
22041 ) {
22042 // Extract all needed data from overlay first to avoid borrow conflicts
22043 let snapshot = self.buffer.read(cx).snapshot(cx);
22044 let (comments_expanded, block_id, prompt_editor) = {
22045 let Some(overlay) = self
22046 .diff_review_overlays
22047 .iter()
22048 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
22049 else {
22050 return;
22051 };
22052
22053 (
22054 overlay.comments_expanded,
22055 overlay.block_id,
22056 overlay.prompt_editor.clone(),
22057 )
22058 };
22059
22060 // Calculate new height
22061 let snapshot = self.buffer.read(cx).snapshot(cx);
22062 let new_height = self.calculate_overlay_height(hunk_key, comments_expanded, &snapshot);
22063
22064 // Update the block height using resize_blocks (avoids flicker)
22065 let mut heights = HashMap::default();
22066 heights.insert(block_id, new_height);
22067 self.resize_blocks(heights, None, cx);
22068
22069 // Update the render function using replace_blocks (avoids flicker)
22070 let hunk_key_for_render = hunk_key.clone();
22071 let editor_handle = cx.entity().downgrade();
22072 let render: Arc<dyn Fn(&mut BlockContext) -> AnyElement + Send + Sync> =
22073 Arc::new(move |cx| {
22074 Self::render_diff_review_overlay(
22075 &prompt_editor,
22076 &hunk_key_for_render,
22077 &editor_handle,
22078 cx,
22079 )
22080 });
22081
22082 let mut renderers = HashMap::default();
22083 renderers.insert(block_id, render);
22084 self.replace_blocks(renderers, None, cx);
22085 }
22086
22087 /// Action handler for SubmitDiffReviewComment.
22088 pub fn submit_diff_review_comment_action(
22089 &mut self,
22090 _: &SubmitDiffReviewComment,
22091 window: &mut Window,
22092 cx: &mut Context<Self>,
22093 ) {
22094 self.submit_diff_review_comment(window, cx);
22095 }
22096
22097 /// Stores the diff review comment locally.
22098 /// Comments are stored per-hunk and can later be batch-submitted to the Agent panel.
22099 pub fn submit_diff_review_comment(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22100 // Find the overlay that currently has focus
22101 let overlay_index = self
22102 .diff_review_overlays
22103 .iter()
22104 .position(|overlay| overlay.prompt_editor.focus_handle(cx).is_focused(window));
22105 let Some(overlay_index) = overlay_index else {
22106 return;
22107 };
22108 let overlay = &self.diff_review_overlays[overlay_index];
22109
22110 let comment_text = overlay.prompt_editor.read(cx).text(cx).trim().to_string();
22111 if comment_text.is_empty() {
22112 return;
22113 }
22114
22115 let anchor_range = overlay.anchor_range.clone();
22116 let hunk_key = overlay.hunk_key.clone();
22117
22118 self.add_review_comment(hunk_key.clone(), comment_text, anchor_range, cx);
22119
22120 // Clear the prompt editor but keep the overlay open
22121 if let Some(overlay) = self.diff_review_overlays.get(overlay_index) {
22122 overlay.prompt_editor.update(cx, |editor, cx| {
22123 editor.clear(window, cx);
22124 });
22125 }
22126
22127 // Refresh the overlay to update the block height for the new comment
22128 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22129
22130 cx.notify();
22131 }
22132
22133 /// Returns the prompt editor for the diff review overlay, if one is active.
22134 /// This is primarily used for testing.
22135 pub fn diff_review_prompt_editor(&self) -> Option<&Entity<Editor>> {
22136 self.diff_review_overlays
22137 .first()
22138 .map(|overlay| &overlay.prompt_editor)
22139 }
22140
22141 /// Returns the line range for the first diff review overlay, if one is active.
22142 /// Returns (start_row, end_row) as physical line numbers in the underlying file.
22143 pub fn diff_review_line_range(&self, cx: &App) -> Option<(u32, u32)> {
22144 let overlay = self.diff_review_overlays.first()?;
22145 let snapshot = self.buffer.read(cx).snapshot(cx);
22146 let start_point = overlay.anchor_range.start.to_point(&snapshot);
22147 let end_point = overlay.anchor_range.end.to_point(&snapshot);
22148 let start_row = snapshot
22149 .point_to_buffer_point(start_point)
22150 .map(|(_, p)| p.row)
22151 .unwrap_or(start_point.row);
22152 let end_row = snapshot
22153 .point_to_buffer_point(end_point)
22154 .map(|(_, p)| p.row)
22155 .unwrap_or(end_point.row);
22156 Some((start_row, end_row))
22157 }
22158
22159 /// Sets whether the comments section is expanded in the diff review overlay.
22160 /// This is primarily used for testing.
22161 pub fn set_diff_review_comments_expanded(&mut self, expanded: bool, cx: &mut Context<Self>) {
22162 for overlay in &mut self.diff_review_overlays {
22163 overlay.comments_expanded = expanded;
22164 }
22165 cx.notify();
22166 }
22167
22168 /// Compares two DiffHunkKeys for equality by resolving their anchors.
22169 fn hunk_keys_match(a: &DiffHunkKey, b: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> bool {
22170 a.file_path == b.file_path
22171 && a.hunk_start_anchor.to_point(snapshot) == b.hunk_start_anchor.to_point(snapshot)
22172 }
22173
22174 /// Returns comments for a specific hunk, ordered by creation time.
22175 pub fn comments_for_hunk<'a>(
22176 &'a self,
22177 key: &DiffHunkKey,
22178 snapshot: &MultiBufferSnapshot,
22179 ) -> &'a [StoredReviewComment] {
22180 let key_point = key.hunk_start_anchor.to_point(snapshot);
22181 self.stored_review_comments
22182 .iter()
22183 .find(|(k, _)| {
22184 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
22185 })
22186 .map(|(_, comments)| comments.as_slice())
22187 .unwrap_or(&[])
22188 }
22189
22190 /// Returns the total count of stored review comments across all hunks.
22191 pub fn total_review_comment_count(&self) -> usize {
22192 self.stored_review_comments
22193 .iter()
22194 .map(|(_, v)| v.len())
22195 .sum()
22196 }
22197
22198 /// Returns the count of comments for a specific hunk.
22199 pub fn hunk_comment_count(&self, key: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> usize {
22200 let key_point = key.hunk_start_anchor.to_point(snapshot);
22201 self.stored_review_comments
22202 .iter()
22203 .find(|(k, _)| {
22204 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
22205 })
22206 .map(|(_, v)| v.len())
22207 .unwrap_or(0)
22208 }
22209
22210 /// Adds a new review comment to a specific hunk.
22211 pub fn add_review_comment(
22212 &mut self,
22213 hunk_key: DiffHunkKey,
22214 comment: String,
22215 anchor_range: Range<Anchor>,
22216 cx: &mut Context<Self>,
22217 ) -> usize {
22218 let id = self.next_review_comment_id;
22219 self.next_review_comment_id += 1;
22220
22221 let stored_comment = StoredReviewComment::new(id, comment, anchor_range);
22222
22223 let snapshot = self.buffer.read(cx).snapshot(cx);
22224 let key_point = hunk_key.hunk_start_anchor.to_point(&snapshot);
22225
22226 // Find existing entry for this hunk or add a new one
22227 if let Some((_, comments)) = self.stored_review_comments.iter_mut().find(|(k, _)| {
22228 k.file_path == hunk_key.file_path
22229 && k.hunk_start_anchor.to_point(&snapshot) == key_point
22230 }) {
22231 comments.push(stored_comment);
22232 } else {
22233 self.stored_review_comments
22234 .push((hunk_key, vec![stored_comment]));
22235 }
22236
22237 cx.emit(EditorEvent::ReviewCommentsChanged {
22238 total_count: self.total_review_comment_count(),
22239 });
22240 cx.notify();
22241 id
22242 }
22243
22244 /// Removes a review comment by ID from any hunk.
22245 pub fn remove_review_comment(&mut self, id: usize, cx: &mut Context<Self>) -> bool {
22246 for (_, comments) in self.stored_review_comments.iter_mut() {
22247 if let Some(index) = comments.iter().position(|c| c.id == id) {
22248 comments.remove(index);
22249 cx.emit(EditorEvent::ReviewCommentsChanged {
22250 total_count: self.total_review_comment_count(),
22251 });
22252 cx.notify();
22253 return true;
22254 }
22255 }
22256 false
22257 }
22258
22259 /// Updates a review comment's text by ID.
22260 pub fn update_review_comment(
22261 &mut self,
22262 id: usize,
22263 new_comment: String,
22264 cx: &mut Context<Self>,
22265 ) -> bool {
22266 for (_, comments) in self.stored_review_comments.iter_mut() {
22267 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
22268 comment.comment = new_comment;
22269 comment.is_editing = false;
22270 cx.emit(EditorEvent::ReviewCommentsChanged {
22271 total_count: self.total_review_comment_count(),
22272 });
22273 cx.notify();
22274 return true;
22275 }
22276 }
22277 false
22278 }
22279
22280 /// Sets a comment's editing state.
22281 pub fn set_comment_editing(&mut self, id: usize, is_editing: bool, cx: &mut Context<Self>) {
22282 for (_, comments) in self.stored_review_comments.iter_mut() {
22283 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
22284 comment.is_editing = is_editing;
22285 cx.notify();
22286 return;
22287 }
22288 }
22289 }
22290
22291 /// Takes all stored comments from all hunks, clearing the storage.
22292 /// Returns a Vec of (hunk_key, comments) pairs.
22293 pub fn take_all_review_comments(
22294 &mut self,
22295 cx: &mut Context<Self>,
22296 ) -> Vec<(DiffHunkKey, Vec<StoredReviewComment>)> {
22297 // Dismiss all overlays when taking comments (e.g., when sending to agent)
22298 self.dismiss_all_diff_review_overlays(cx);
22299 let comments = std::mem::take(&mut self.stored_review_comments);
22300 // Reset the ID counter since all comments have been taken
22301 self.next_review_comment_id = 0;
22302 cx.emit(EditorEvent::ReviewCommentsChanged { total_count: 0 });
22303 cx.notify();
22304 comments
22305 }
22306
22307 /// Removes review comments whose anchors are no longer valid or whose
22308 /// associated diff hunks no longer exist.
22309 ///
22310 /// This should be called when the buffer changes to prevent orphaned comments
22311 /// from accumulating.
22312 pub fn cleanup_orphaned_review_comments(&mut self, cx: &mut Context<Self>) {
22313 let snapshot = self.buffer.read(cx).snapshot(cx);
22314 let original_count = self.total_review_comment_count();
22315
22316 // Remove comments with invalid hunk anchors
22317 self.stored_review_comments
22318 .retain(|(hunk_key, _)| hunk_key.hunk_start_anchor.is_valid(&snapshot));
22319
22320 // Also clean up individual comments with invalid anchor ranges
22321 for (_, comments) in &mut self.stored_review_comments {
22322 comments.retain(|comment| {
22323 comment.range.start.is_valid(&snapshot) && comment.range.end.is_valid(&snapshot)
22324 });
22325 }
22326
22327 // Remove empty hunk entries
22328 self.stored_review_comments
22329 .retain(|(_, comments)| !comments.is_empty());
22330
22331 let new_count = self.total_review_comment_count();
22332 if new_count != original_count {
22333 cx.emit(EditorEvent::ReviewCommentsChanged {
22334 total_count: new_count,
22335 });
22336 cx.notify();
22337 }
22338 }
22339
22340 /// Toggles the expanded state of the comments section in the overlay.
22341 pub fn toggle_review_comments_expanded(
22342 &mut self,
22343 _: &ToggleReviewCommentsExpanded,
22344 window: &mut Window,
22345 cx: &mut Context<Self>,
22346 ) {
22347 // Find the overlay that currently has focus, or use the first one
22348 let overlay_info = self.diff_review_overlays.iter_mut().find_map(|overlay| {
22349 if overlay.prompt_editor.focus_handle(cx).is_focused(window) {
22350 overlay.comments_expanded = !overlay.comments_expanded;
22351 Some(overlay.hunk_key.clone())
22352 } else {
22353 None
22354 }
22355 });
22356
22357 // If no focused overlay found, toggle the first one
22358 let hunk_key = overlay_info.or_else(|| {
22359 self.diff_review_overlays.first_mut().map(|overlay| {
22360 overlay.comments_expanded = !overlay.comments_expanded;
22361 overlay.hunk_key.clone()
22362 })
22363 });
22364
22365 if let Some(hunk_key) = hunk_key {
22366 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22367 cx.notify();
22368 }
22369 }
22370
22371 /// Handles the EditReviewComment action - sets a comment into editing mode.
22372 pub fn edit_review_comment(
22373 &mut self,
22374 action: &EditReviewComment,
22375 window: &mut Window,
22376 cx: &mut Context<Self>,
22377 ) {
22378 let comment_id = action.id;
22379
22380 // Set the comment to editing mode
22381 self.set_comment_editing(comment_id, true, cx);
22382
22383 // Find the overlay that contains this comment and create an inline editor if needed
22384 // First, find which hunk this comment belongs to
22385 let hunk_key = self
22386 .stored_review_comments
22387 .iter()
22388 .find_map(|(key, comments)| {
22389 if comments.iter().any(|c| c.id == comment_id) {
22390 Some(key.clone())
22391 } else {
22392 None
22393 }
22394 });
22395
22396 let snapshot = self.buffer.read(cx).snapshot(cx);
22397 if let Some(hunk_key) = hunk_key {
22398 if let Some(overlay) = self
22399 .diff_review_overlays
22400 .iter_mut()
22401 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22402 {
22403 if let std::collections::hash_map::Entry::Vacant(entry) =
22404 overlay.inline_edit_editors.entry(comment_id)
22405 {
22406 // Find the comment text
22407 let comment_text = self
22408 .stored_review_comments
22409 .iter()
22410 .flat_map(|(_, comments)| comments)
22411 .find(|c| c.id == comment_id)
22412 .map(|c| c.comment.clone())
22413 .unwrap_or_default();
22414
22415 // Create inline editor
22416 let parent_editor = cx.entity().downgrade();
22417 let inline_editor = cx.new(|cx| {
22418 let mut editor = Editor::single_line(window, cx);
22419 editor.set_text(&*comment_text, window, cx);
22420 // Select all text for easy replacement
22421 editor.select_all(&crate::actions::SelectAll, window, cx);
22422 editor
22423 });
22424
22425 // Register the Newline action to confirm the edit
22426 let subscription = inline_editor.update(cx, |inline_editor, _cx| {
22427 inline_editor.register_action({
22428 let parent_editor = parent_editor.clone();
22429 move |_: &crate::actions::Newline, window, cx| {
22430 if let Some(editor) = parent_editor.upgrade() {
22431 editor.update(cx, |editor, cx| {
22432 editor.confirm_edit_review_comment(comment_id, window, cx);
22433 });
22434 }
22435 }
22436 })
22437 });
22438
22439 // Store the subscription to keep the action handler alive
22440 overlay
22441 .inline_edit_subscriptions
22442 .insert(comment_id, subscription);
22443
22444 // Focus the inline editor
22445 let focus_handle = inline_editor.focus_handle(cx);
22446 window.focus(&focus_handle, cx);
22447
22448 entry.insert(inline_editor);
22449 }
22450 }
22451 }
22452
22453 cx.notify();
22454 }
22455
22456 /// Confirms an inline edit of a review comment.
22457 pub fn confirm_edit_review_comment(
22458 &mut self,
22459 comment_id: usize,
22460 _window: &mut Window,
22461 cx: &mut Context<Self>,
22462 ) {
22463 // Get the new text from the inline editor
22464 // Find the overlay containing this comment's inline editor
22465 let snapshot = self.buffer.read(cx).snapshot(cx);
22466 let hunk_key = self
22467 .stored_review_comments
22468 .iter()
22469 .find_map(|(key, comments)| {
22470 if comments.iter().any(|c| c.id == comment_id) {
22471 Some(key.clone())
22472 } else {
22473 None
22474 }
22475 });
22476
22477 let new_text = hunk_key
22478 .as_ref()
22479 .and_then(|hunk_key| {
22480 self.diff_review_overlays
22481 .iter()
22482 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
22483 })
22484 .as_ref()
22485 .and_then(|overlay| overlay.inline_edit_editors.get(&comment_id))
22486 .map(|editor| editor.read(cx).text(cx).trim().to_string());
22487
22488 if let Some(new_text) = new_text {
22489 if !new_text.is_empty() {
22490 self.update_review_comment(comment_id, new_text, cx);
22491 }
22492 }
22493
22494 // Remove the inline editor and its subscription
22495 if let Some(hunk_key) = hunk_key {
22496 if let Some(overlay) = self
22497 .diff_review_overlays
22498 .iter_mut()
22499 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22500 {
22501 overlay.inline_edit_editors.remove(&comment_id);
22502 overlay.inline_edit_subscriptions.remove(&comment_id);
22503 }
22504 }
22505
22506 // Clear editing state
22507 self.set_comment_editing(comment_id, false, cx);
22508 }
22509
22510 /// Cancels an inline edit of a review comment.
22511 pub fn cancel_edit_review_comment(
22512 &mut self,
22513 comment_id: usize,
22514 _window: &mut Window,
22515 cx: &mut Context<Self>,
22516 ) {
22517 // Find which hunk this comment belongs to
22518 let hunk_key = self
22519 .stored_review_comments
22520 .iter()
22521 .find_map(|(key, comments)| {
22522 if comments.iter().any(|c| c.id == comment_id) {
22523 Some(key.clone())
22524 } else {
22525 None
22526 }
22527 });
22528
22529 // Remove the inline editor and its subscription
22530 if let Some(hunk_key) = hunk_key {
22531 let snapshot = self.buffer.read(cx).snapshot(cx);
22532 if let Some(overlay) = self
22533 .diff_review_overlays
22534 .iter_mut()
22535 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22536 {
22537 overlay.inline_edit_editors.remove(&comment_id);
22538 overlay.inline_edit_subscriptions.remove(&comment_id);
22539 }
22540 }
22541
22542 // Clear editing state
22543 self.set_comment_editing(comment_id, false, cx);
22544 }
22545
22546 /// Action handler for ConfirmEditReviewComment.
22547 pub fn confirm_edit_review_comment_action(
22548 &mut self,
22549 action: &ConfirmEditReviewComment,
22550 window: &mut Window,
22551 cx: &mut Context<Self>,
22552 ) {
22553 self.confirm_edit_review_comment(action.id, window, cx);
22554 }
22555
22556 /// Action handler for CancelEditReviewComment.
22557 pub fn cancel_edit_review_comment_action(
22558 &mut self,
22559 action: &CancelEditReviewComment,
22560 window: &mut Window,
22561 cx: &mut Context<Self>,
22562 ) {
22563 self.cancel_edit_review_comment(action.id, window, cx);
22564 }
22565
22566 /// Handles the DeleteReviewComment action - removes a comment.
22567 pub fn delete_review_comment(
22568 &mut self,
22569 action: &DeleteReviewComment,
22570 window: &mut Window,
22571 cx: &mut Context<Self>,
22572 ) {
22573 // Get the hunk key before removing the comment
22574 // Find the hunk key from the comment itself
22575 let comment_id = action.id;
22576 let hunk_key = self
22577 .stored_review_comments
22578 .iter()
22579 .find_map(|(key, comments)| {
22580 if comments.iter().any(|c| c.id == comment_id) {
22581 Some(key.clone())
22582 } else {
22583 None
22584 }
22585 });
22586
22587 // Also get it from the overlay for refresh purposes
22588 let overlay_hunk_key = self
22589 .diff_review_overlays
22590 .first()
22591 .map(|o| o.hunk_key.clone());
22592
22593 self.remove_review_comment(action.id, cx);
22594
22595 // Refresh the overlay height after removing a comment
22596 if let Some(hunk_key) = hunk_key.or(overlay_hunk_key) {
22597 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22598 }
22599 }
22600
22601 fn render_diff_review_overlay(
22602 prompt_editor: &Entity<Editor>,
22603 hunk_key: &DiffHunkKey,
22604 editor_handle: &WeakEntity<Editor>,
22605 cx: &mut BlockContext,
22606 ) -> AnyElement {
22607 fn format_line_ranges(ranges: &[(u32, u32)]) -> Option<String> {
22608 if ranges.is_empty() {
22609 return None;
22610 }
22611 let formatted: Vec<String> = ranges
22612 .iter()
22613 .map(|(start, end)| {
22614 let start_line = start + 1;
22615 let end_line = end + 1;
22616 if start_line == end_line {
22617 format!("Line {start_line}")
22618 } else {
22619 format!("Lines {start_line}-{end_line}")
22620 }
22621 })
22622 .collect();
22623 // Don't show label for single line in single excerpt
22624 if ranges.len() == 1 && ranges[0].0 == ranges[0].1 {
22625 return None;
22626 }
22627 Some(formatted.join(" ⋯ "))
22628 }
22629
22630 let theme = cx.theme();
22631 let colors = theme.colors();
22632
22633 let (comments, comments_expanded, inline_editors, user_avatar_uri, line_ranges) =
22634 editor_handle
22635 .upgrade()
22636 .map(|editor| {
22637 let editor = editor.read(cx);
22638 let snapshot = editor.buffer().read(cx).snapshot(cx);
22639 let comments = editor.comments_for_hunk(hunk_key, &snapshot).to_vec();
22640 let (expanded, editors, avatar_uri, line_ranges) = editor
22641 .diff_review_overlays
22642 .iter()
22643 .find(|overlay| {
22644 Editor::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot)
22645 })
22646 .map(|o| {
22647 let start_point = o.anchor_range.start.to_point(&snapshot);
22648 let end_point = o.anchor_range.end.to_point(&snapshot);
22649 // Get line ranges per excerpt to detect discontinuities
22650 let buffer_ranges =
22651 snapshot.range_to_buffer_ranges(start_point..end_point);
22652 let ranges: Vec<(u32, u32)> = buffer_ranges
22653 .iter()
22654 .map(|(buffer_snapshot, range, _)| {
22655 let start = buffer_snapshot.offset_to_point(range.start.0).row;
22656 let end = buffer_snapshot.offset_to_point(range.end.0).row;
22657 (start, end)
22658 })
22659 .collect();
22660 (
22661 o.comments_expanded,
22662 o.inline_edit_editors.clone(),
22663 o.user_avatar_uri.clone(),
22664 if ranges.is_empty() {
22665 None
22666 } else {
22667 Some(ranges)
22668 },
22669 )
22670 })
22671 .unwrap_or((true, HashMap::default(), None, None));
22672 (comments, expanded, editors, avatar_uri, line_ranges)
22673 })
22674 .unwrap_or((Vec::new(), true, HashMap::default(), None, None));
22675
22676 let comment_count = comments.len();
22677 let avatar_size = px(20.);
22678 let action_icon_size = IconSize::XSmall;
22679
22680 v_flex()
22681 .w_full()
22682 .bg(colors.editor_background)
22683 .border_b_1()
22684 .border_color(colors.border)
22685 .px_2()
22686 .pb_2()
22687 .gap_2()
22688 // Line range indicator (only shown for multi-line selections or multiple excerpts)
22689 .when_some(line_ranges, |el, ranges| {
22690 let label = format_line_ranges(&ranges);
22691 if let Some(label) = label {
22692 el.child(
22693 h_flex()
22694 .w_full()
22695 .px_2()
22696 .child(Label::new(label).size(LabelSize::Small).color(Color::Muted)),
22697 )
22698 } else {
22699 el
22700 }
22701 })
22702 // Top row: editable input with user's avatar
22703 .child(
22704 h_flex()
22705 .w_full()
22706 .items_center()
22707 .gap_2()
22708 .px_2()
22709 .py_1p5()
22710 .rounded_md()
22711 .bg(colors.surface_background)
22712 .child(
22713 div()
22714 .size(avatar_size)
22715 .flex_shrink_0()
22716 .rounded_full()
22717 .overflow_hidden()
22718 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22719 Avatar::new(avatar_uri.clone())
22720 .size(avatar_size)
22721 .into_any_element()
22722 } else {
22723 Icon::new(IconName::Person)
22724 .size(IconSize::Small)
22725 .color(ui::Color::Muted)
22726 .into_any_element()
22727 }),
22728 )
22729 .child(
22730 div()
22731 .flex_1()
22732 .border_1()
22733 .border_color(colors.border)
22734 .rounded_md()
22735 .bg(colors.editor_background)
22736 .px_2()
22737 .py_1()
22738 .child(prompt_editor.clone()),
22739 )
22740 .child(
22741 h_flex()
22742 .flex_shrink_0()
22743 .gap_1()
22744 .child(
22745 IconButton::new("diff-review-close", IconName::Close)
22746 .icon_color(ui::Color::Muted)
22747 .icon_size(action_icon_size)
22748 .tooltip(Tooltip::text("Close"))
22749 .on_click(|_, window, cx| {
22750 window
22751 .dispatch_action(Box::new(crate::actions::Cancel), cx);
22752 }),
22753 )
22754 .child(
22755 IconButton::new("diff-review-add", IconName::Return)
22756 .icon_color(ui::Color::Muted)
22757 .icon_size(action_icon_size)
22758 .tooltip(Tooltip::text("Add comment"))
22759 .on_click(|_, window, cx| {
22760 window.dispatch_action(
22761 Box::new(crate::actions::SubmitDiffReviewComment),
22762 cx,
22763 );
22764 }),
22765 ),
22766 ),
22767 )
22768 // Expandable comments section (only shown when there are comments)
22769 .when(comment_count > 0, |el| {
22770 el.child(Self::render_comments_section(
22771 comments,
22772 comments_expanded,
22773 inline_editors,
22774 user_avatar_uri,
22775 avatar_size,
22776 action_icon_size,
22777 colors,
22778 ))
22779 })
22780 .into_any_element()
22781 }
22782
22783 fn render_comments_section(
22784 comments: Vec<StoredReviewComment>,
22785 expanded: bool,
22786 inline_editors: HashMap<usize, Entity<Editor>>,
22787 user_avatar_uri: Option<SharedUri>,
22788 avatar_size: Pixels,
22789 action_icon_size: IconSize,
22790 colors: &theme::ThemeColors,
22791 ) -> impl IntoElement {
22792 let comment_count = comments.len();
22793
22794 v_flex()
22795 .w_full()
22796 .gap_1()
22797 // Header with expand/collapse toggle
22798 .child(
22799 h_flex()
22800 .id("review-comments-header")
22801 .w_full()
22802 .items_center()
22803 .gap_1()
22804 .px_2()
22805 .py_1()
22806 .cursor_pointer()
22807 .rounded_md()
22808 .hover(|style| style.bg(colors.ghost_element_hover))
22809 .on_click(|_, window: &mut Window, cx| {
22810 window.dispatch_action(
22811 Box::new(crate::actions::ToggleReviewCommentsExpanded),
22812 cx,
22813 );
22814 })
22815 .child(
22816 Icon::new(if expanded {
22817 IconName::ChevronDown
22818 } else {
22819 IconName::ChevronRight
22820 })
22821 .size(IconSize::Small)
22822 .color(ui::Color::Muted),
22823 )
22824 .child(
22825 Label::new(format!(
22826 "{} Comment{}",
22827 comment_count,
22828 if comment_count == 1 { "" } else { "s" }
22829 ))
22830 .size(LabelSize::Small)
22831 .color(Color::Muted),
22832 ),
22833 )
22834 // Comments list (when expanded)
22835 .when(expanded, |el| {
22836 el.children(comments.into_iter().map(|comment| {
22837 let inline_editor = inline_editors.get(&comment.id).cloned();
22838 Self::render_comment_row(
22839 comment,
22840 inline_editor,
22841 user_avatar_uri.clone(),
22842 avatar_size,
22843 action_icon_size,
22844 colors,
22845 )
22846 }))
22847 })
22848 }
22849
22850 fn render_comment_row(
22851 comment: StoredReviewComment,
22852 inline_editor: Option<Entity<Editor>>,
22853 user_avatar_uri: Option<SharedUri>,
22854 avatar_size: Pixels,
22855 action_icon_size: IconSize,
22856 colors: &theme::ThemeColors,
22857 ) -> impl IntoElement {
22858 let comment_id = comment.id;
22859 let is_editing = inline_editor.is_some();
22860
22861 h_flex()
22862 .w_full()
22863 .items_center()
22864 .gap_2()
22865 .px_2()
22866 .py_1p5()
22867 .rounded_md()
22868 .bg(colors.surface_background)
22869 .child(
22870 div()
22871 .size(avatar_size)
22872 .flex_shrink_0()
22873 .rounded_full()
22874 .overflow_hidden()
22875 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22876 Avatar::new(avatar_uri.clone())
22877 .size(avatar_size)
22878 .into_any_element()
22879 } else {
22880 Icon::new(IconName::Person)
22881 .size(IconSize::Small)
22882 .color(ui::Color::Muted)
22883 .into_any_element()
22884 }),
22885 )
22886 .child(if let Some(editor) = inline_editor {
22887 // Inline edit mode: show an editable text field
22888 div()
22889 .flex_1()
22890 .border_1()
22891 .border_color(colors.border)
22892 .rounded_md()
22893 .bg(colors.editor_background)
22894 .px_2()
22895 .py_1()
22896 .child(editor)
22897 .into_any_element()
22898 } else {
22899 // Display mode: show the comment text
22900 div()
22901 .flex_1()
22902 .text_sm()
22903 .text_color(colors.text)
22904 .child(comment.comment)
22905 .into_any_element()
22906 })
22907 .child(if is_editing {
22908 // Editing mode: show close and confirm buttons
22909 h_flex()
22910 .gap_1()
22911 .child(
22912 IconButton::new(
22913 format!("diff-review-cancel-edit-{comment_id}"),
22914 IconName::Close,
22915 )
22916 .icon_color(ui::Color::Muted)
22917 .icon_size(action_icon_size)
22918 .tooltip(Tooltip::text("Cancel"))
22919 .on_click(move |_, window, cx| {
22920 window.dispatch_action(
22921 Box::new(crate::actions::CancelEditReviewComment {
22922 id: comment_id,
22923 }),
22924 cx,
22925 );
22926 }),
22927 )
22928 .child(
22929 IconButton::new(
22930 format!("diff-review-confirm-edit-{comment_id}"),
22931 IconName::Return,
22932 )
22933 .icon_color(ui::Color::Muted)
22934 .icon_size(action_icon_size)
22935 .tooltip(Tooltip::text("Confirm"))
22936 .on_click(move |_, window, cx| {
22937 window.dispatch_action(
22938 Box::new(crate::actions::ConfirmEditReviewComment {
22939 id: comment_id,
22940 }),
22941 cx,
22942 );
22943 }),
22944 )
22945 .into_any_element()
22946 } else {
22947 // Display mode: no action buttons for now (edit/delete not yet implemented)
22948 gpui::Empty.into_any_element()
22949 })
22950 }
22951
22952 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
22953 if self.display_map.read(cx).masked != masked {
22954 self.display_map.update(cx, |map, _| map.masked = masked);
22955 }
22956 cx.notify()
22957 }
22958
22959 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
22960 self.show_wrap_guides = Some(show_wrap_guides);
22961 cx.notify();
22962 }
22963
22964 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
22965 self.show_indent_guides = Some(show_indent_guides);
22966 cx.notify();
22967 }
22968
22969 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
22970 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
22971 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
22972 && let Some(dir) = file.abs_path(cx).parent()
22973 {
22974 return Some(dir.to_owned());
22975 }
22976 }
22977
22978 None
22979 }
22980
22981 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
22982 self.active_buffer(cx)?
22983 .read(cx)
22984 .file()
22985 .and_then(|f| f.as_local())
22986 }
22987
22988 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
22989 self.active_buffer(cx).and_then(|buffer| {
22990 let buffer = buffer.read(cx);
22991 if let Some(project_path) = buffer.project_path(cx) {
22992 let project = self.project()?.read(cx);
22993 project.absolute_path(&project_path, cx)
22994 } else {
22995 buffer
22996 .file()
22997 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
22998 }
22999 })
23000 }
23001
23002 pub fn reveal_in_finder(
23003 &mut self,
23004 _: &RevealInFileManager,
23005 _window: &mut Window,
23006 cx: &mut Context<Self>,
23007 ) {
23008 if let Some(path) = self.target_file_abs_path(cx) {
23009 if let Some(project) = self.project() {
23010 project.update(cx, |project, cx| project.reveal_path(&path, cx));
23011 } else {
23012 cx.reveal_path(&path);
23013 }
23014 }
23015 }
23016
23017 pub fn copy_path(
23018 &mut self,
23019 _: &zed_actions::workspace::CopyPath,
23020 _window: &mut Window,
23021 cx: &mut Context<Self>,
23022 ) {
23023 if let Some(path) = self.target_file_abs_path(cx)
23024 && let Some(path) = path.to_str()
23025 {
23026 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
23027 } else {
23028 cx.propagate();
23029 }
23030 }
23031
23032 pub fn copy_relative_path(
23033 &mut self,
23034 _: &zed_actions::workspace::CopyRelativePath,
23035 _window: &mut Window,
23036 cx: &mut Context<Self>,
23037 ) {
23038 if let Some(path) = self.active_buffer(cx).and_then(|buffer| {
23039 let project = self.project()?.read(cx);
23040 let path = buffer.read(cx).file()?.path();
23041 let path = path.display(project.path_style(cx));
23042 Some(path)
23043 }) {
23044 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
23045 } else {
23046 cx.propagate();
23047 }
23048 }
23049
23050 /// Returns the project path for the editor's buffer, if any buffer is
23051 /// opened in the editor.
23052 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
23053 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
23054 buffer.read(cx).project_path(cx)
23055 } else {
23056 None
23057 }
23058 }
23059
23060 // Returns true if the editor handled a go-to-line request
23061 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
23062 maybe!({
23063 let breakpoint_store = self.breakpoint_store.as_ref()?;
23064
23065 let (active_stack_frame, debug_line_pane_id) = {
23066 let store = breakpoint_store.read(cx);
23067 let active_stack_frame = store.active_position().cloned();
23068 let debug_line_pane_id = store.active_debug_line_pane_id();
23069 (active_stack_frame, debug_line_pane_id)
23070 };
23071
23072 let Some(active_stack_frame) = active_stack_frame else {
23073 self.clear_row_highlights::<ActiveDebugLine>();
23074 return None;
23075 };
23076
23077 if let Some(debug_line_pane_id) = debug_line_pane_id {
23078 if let Some(workspace) = self
23079 .workspace
23080 .as_ref()
23081 .and_then(|(workspace, _)| workspace.upgrade())
23082 {
23083 let editor_pane_id = workspace
23084 .read(cx)
23085 .pane_for_item_id(cx.entity_id())
23086 .map(|pane| pane.entity_id());
23087
23088 if editor_pane_id.is_some_and(|id| id != debug_line_pane_id) {
23089 self.clear_row_highlights::<ActiveDebugLine>();
23090 return None;
23091 }
23092 }
23093 }
23094
23095 let position = active_stack_frame.position;
23096
23097 let snapshot = self.buffer.read(cx).snapshot(cx);
23098 let multibuffer_anchor = snapshot.anchor_in_excerpt(position)?;
23099
23100 self.clear_row_highlights::<ActiveDebugLine>();
23101
23102 self.go_to_line::<ActiveDebugLine>(
23103 multibuffer_anchor,
23104 Some(cx.theme().colors().editor_debugger_active_line_background),
23105 window,
23106 cx,
23107 );
23108
23109 cx.notify();
23110
23111 Some(())
23112 })
23113 .is_some()
23114 }
23115
23116 pub fn copy_file_name_without_extension(
23117 &mut self,
23118 _: &CopyFileNameWithoutExtension,
23119 _: &mut Window,
23120 cx: &mut Context<Self>,
23121 ) {
23122 if let Some(file_stem) = self.active_buffer(cx).and_then(|buffer| {
23123 let file = buffer.read(cx).file()?;
23124 file.path().file_stem()
23125 }) {
23126 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
23127 }
23128 }
23129
23130 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
23131 if let Some(file_name) = self.active_buffer(cx).and_then(|buffer| {
23132 let file = buffer.read(cx).file()?;
23133 Some(file.file_name(cx))
23134 }) {
23135 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
23136 }
23137 }
23138
23139 pub fn toggle_git_blame(
23140 &mut self,
23141 _: &::git::Blame,
23142 window: &mut Window,
23143 cx: &mut Context<Self>,
23144 ) {
23145 self.show_git_blame_gutter = !self.show_git_blame_gutter;
23146
23147 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
23148 self.start_git_blame(true, window, cx);
23149 }
23150
23151 cx.notify();
23152 }
23153
23154 pub fn toggle_git_blame_inline(
23155 &mut self,
23156 _: &ToggleGitBlameInline,
23157 window: &mut Window,
23158 cx: &mut Context<Self>,
23159 ) {
23160 self.toggle_git_blame_inline_internal(true, window, cx);
23161 cx.notify();
23162 }
23163
23164 pub fn open_git_blame_commit(
23165 &mut self,
23166 _: &OpenGitBlameCommit,
23167 window: &mut Window,
23168 cx: &mut Context<Self>,
23169 ) {
23170 self.open_git_blame_commit_internal(window, cx);
23171 }
23172
23173 fn open_git_blame_commit_internal(
23174 &mut self,
23175 window: &mut Window,
23176 cx: &mut Context<Self>,
23177 ) -> Option<()> {
23178 let blame = self.blame.as_ref()?;
23179 let snapshot = self.snapshot(window, cx);
23180 let cursor = self
23181 .selections
23182 .newest::<Point>(&snapshot.display_snapshot)
23183 .head();
23184 let (buffer, point) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
23185 let (_, blame_entry) = blame
23186 .update(cx, |blame, cx| {
23187 blame
23188 .blame_for_rows(
23189 &[RowInfo {
23190 buffer_id: Some(buffer.remote_id()),
23191 buffer_row: Some(point.row),
23192 ..Default::default()
23193 }],
23194 cx,
23195 )
23196 .next()
23197 })
23198 .flatten()?;
23199 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23200 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
23201 let workspace = self.workspace()?.downgrade();
23202 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
23203 None
23204 }
23205
23206 pub fn git_blame_inline_enabled(&self) -> bool {
23207 self.git_blame_inline_enabled
23208 }
23209
23210 pub fn toggle_selection_menu(
23211 &mut self,
23212 _: &ToggleSelectionMenu,
23213 _: &mut Window,
23214 cx: &mut Context<Self>,
23215 ) {
23216 self.show_selection_menu = self
23217 .show_selection_menu
23218 .map(|show_selections_menu| !show_selections_menu)
23219 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
23220
23221 cx.notify();
23222 }
23223
23224 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
23225 self.show_selection_menu
23226 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
23227 }
23228
23229 fn start_git_blame(
23230 &mut self,
23231 user_triggered: bool,
23232 window: &mut Window,
23233 cx: &mut Context<Self>,
23234 ) {
23235 if let Some(project) = self.project() {
23236 if let Some(buffer) = self.buffer().read(cx).as_singleton()
23237 && buffer.read(cx).file().is_none()
23238 {
23239 return;
23240 }
23241
23242 let focused = self.focus_handle(cx).contains_focused(window, cx);
23243
23244 let project = project.clone();
23245 let blame = cx
23246 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
23247 self.blame_subscription =
23248 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
23249 self.blame = Some(blame);
23250 }
23251 }
23252
23253 fn toggle_git_blame_inline_internal(
23254 &mut self,
23255 user_triggered: bool,
23256 window: &mut Window,
23257 cx: &mut Context<Self>,
23258 ) {
23259 if self.git_blame_inline_enabled {
23260 self.git_blame_inline_enabled = false;
23261 self.show_git_blame_inline = false;
23262 self.show_git_blame_inline_delay_task.take();
23263 } else {
23264 self.git_blame_inline_enabled = true;
23265 self.start_git_blame_inline(user_triggered, window, cx);
23266 }
23267
23268 cx.notify();
23269 }
23270
23271 fn start_git_blame_inline(
23272 &mut self,
23273 user_triggered: bool,
23274 window: &mut Window,
23275 cx: &mut Context<Self>,
23276 ) {
23277 self.start_git_blame(user_triggered, window, cx);
23278
23279 if ProjectSettings::get_global(cx)
23280 .git
23281 .inline_blame_delay()
23282 .is_some()
23283 {
23284 self.start_inline_blame_timer(window, cx);
23285 } else {
23286 self.show_git_blame_inline = true
23287 }
23288 }
23289
23290 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
23291 self.blame.as_ref()
23292 }
23293
23294 pub fn show_git_blame_gutter(&self) -> bool {
23295 self.show_git_blame_gutter
23296 }
23297
23298 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
23299 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
23300 }
23301
23302 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
23303 self.show_git_blame_inline
23304 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
23305 && !self.newest_selection_head_on_empty_line(cx)
23306 && self.has_blame_entries(cx)
23307 }
23308
23309 fn has_blame_entries(&self, cx: &App) -> bool {
23310 self.blame()
23311 .is_some_and(|blame| blame.read(cx).has_generated_entries())
23312 }
23313
23314 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
23315 let cursor_anchor = self.selections.newest_anchor().head();
23316
23317 let snapshot = self.buffer.read(cx).snapshot(cx);
23318 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
23319
23320 snapshot.line_len(buffer_row) == 0
23321 }
23322
23323 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
23324 let buffer_and_selection = maybe!({
23325 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
23326 let selection_range = selection.range();
23327
23328 let multi_buffer = self.buffer().read(cx);
23329 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
23330 let buffer_ranges = multi_buffer_snapshot
23331 .range_to_buffer_ranges(selection_range.start..selection_range.end);
23332
23333 let (buffer_snapshot, range, _) = if selection.reversed {
23334 buffer_ranges.first()
23335 } else {
23336 buffer_ranges.last()
23337 }?;
23338
23339 let buffer_range = range.to_point(buffer_snapshot);
23340 let buffer = multi_buffer.buffer(buffer_snapshot.remote_id()).unwrap();
23341
23342 let Some(buffer_diff) = multi_buffer.diff_for(buffer_snapshot.remote_id()) else {
23343 return Some((buffer, buffer_range.start.row..buffer_range.end.row));
23344 };
23345
23346 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
23347 let start = buffer_diff_snapshot
23348 .buffer_point_to_base_text_point(buffer_range.start, &buffer_snapshot);
23349 let end = buffer_diff_snapshot
23350 .buffer_point_to_base_text_point(buffer_range.end, &buffer_snapshot);
23351
23352 Some((buffer, start.row..end.row))
23353 });
23354
23355 let Some((buffer, selection)) = buffer_and_selection else {
23356 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
23357 };
23358
23359 let Some(project) = self.project() else {
23360 return Task::ready(Err(anyhow!("editor does not have project")));
23361 };
23362
23363 project.update(cx, |project, cx| {
23364 project.get_permalink_to_line(&buffer, selection, cx)
23365 })
23366 }
23367
23368 pub fn copy_permalink_to_line(
23369 &mut self,
23370 _: &CopyPermalinkToLine,
23371 window: &mut Window,
23372 cx: &mut Context<Self>,
23373 ) {
23374 let permalink_task = self.get_permalink_to_line(cx);
23375 let workspace = self.workspace();
23376
23377 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
23378 Ok(permalink) => {
23379 cx.update(|_, cx| {
23380 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
23381 })
23382 .ok();
23383 }
23384 Err(err) => {
23385 let message = format!("Failed to copy permalink: {err}");
23386
23387 anyhow::Result::<()>::Err(err).log_err();
23388
23389 if let Some(workspace) = workspace {
23390 workspace
23391 .update_in(cx, |workspace, _, cx| {
23392 struct CopyPermalinkToLine;
23393
23394 workspace.show_toast(
23395 Toast::new(
23396 NotificationId::unique::<CopyPermalinkToLine>(),
23397 message,
23398 ),
23399 cx,
23400 )
23401 })
23402 .ok();
23403 }
23404 }
23405 })
23406 .detach();
23407 }
23408
23409 pub fn copy_file_location(
23410 &mut self,
23411 _: &CopyFileLocation,
23412 _: &mut Window,
23413 cx: &mut Context<Self>,
23414 ) {
23415 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
23416
23417 let start_line = selection.start.row + 1;
23418 let end_line = selection.end.row + 1;
23419
23420 let end_line = if selection.end.column == 0 && end_line > start_line {
23421 end_line - 1
23422 } else {
23423 end_line
23424 };
23425
23426 if let Some(file_location) = self.active_buffer(cx).and_then(|buffer| {
23427 let project = self.project()?.read(cx);
23428 let file = buffer.read(cx).file()?;
23429 let path = file.path().display(project.path_style(cx));
23430
23431 let location = if start_line == end_line {
23432 format!("{path}:{start_line}")
23433 } else {
23434 format!("{path}:{start_line}-{end_line}")
23435 };
23436 Some(location)
23437 }) {
23438 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
23439 }
23440 }
23441
23442 pub fn open_permalink_to_line(
23443 &mut self,
23444 _: &OpenPermalinkToLine,
23445 window: &mut Window,
23446 cx: &mut Context<Self>,
23447 ) {
23448 let permalink_task = self.get_permalink_to_line(cx);
23449 let workspace = self.workspace();
23450
23451 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
23452 Ok(permalink) => {
23453 cx.update(|_, cx| {
23454 cx.open_url(permalink.as_ref());
23455 })
23456 .ok();
23457 }
23458 Err(err) => {
23459 let message = format!("Failed to open permalink: {err}");
23460
23461 anyhow::Result::<()>::Err(err).log_err();
23462
23463 if let Some(workspace) = workspace {
23464 workspace.update(cx, |workspace, cx| {
23465 struct OpenPermalinkToLine;
23466
23467 workspace.show_toast(
23468 Toast::new(NotificationId::unique::<OpenPermalinkToLine>(), message),
23469 cx,
23470 )
23471 });
23472 }
23473 }
23474 })
23475 .detach();
23476 }
23477
23478 pub fn insert_uuid_v4(
23479 &mut self,
23480 _: &InsertUuidV4,
23481 window: &mut Window,
23482 cx: &mut Context<Self>,
23483 ) {
23484 self.insert_uuid(UuidVersion::V4, window, cx);
23485 }
23486
23487 pub fn insert_uuid_v7(
23488 &mut self,
23489 _: &InsertUuidV7,
23490 window: &mut Window,
23491 cx: &mut Context<Self>,
23492 ) {
23493 self.insert_uuid(UuidVersion::V7, window, cx);
23494 }
23495
23496 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
23497 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
23498 self.transact(window, cx, |this, window, cx| {
23499 let edits = this
23500 .selections
23501 .all::<Point>(&this.display_snapshot(cx))
23502 .into_iter()
23503 .map(|selection| {
23504 let uuid = match version {
23505 UuidVersion::V4 => uuid::Uuid::new_v4(),
23506 UuidVersion::V7 => uuid::Uuid::now_v7(),
23507 };
23508
23509 (selection.range(), uuid.to_string())
23510 });
23511 this.edit(edits, cx);
23512 this.refresh_edit_prediction(true, false, window, cx);
23513 });
23514 }
23515
23516 pub fn open_selections_in_multibuffer(
23517 &mut self,
23518 _: &OpenSelectionsInMultibuffer,
23519 window: &mut Window,
23520 cx: &mut Context<Self>,
23521 ) {
23522 let multibuffer = self.buffer.read(cx);
23523
23524 let Some(buffer) = multibuffer.as_singleton() else {
23525 return;
23526 };
23527 let buffer_snapshot = buffer.read(cx).snapshot();
23528
23529 let Some(workspace) = self.workspace() else {
23530 return;
23531 };
23532
23533 let title = multibuffer.title(cx).to_string();
23534
23535 let locations = self
23536 .selections
23537 .all_anchors(&self.display_snapshot(cx))
23538 .iter()
23539 .map(|selection| {
23540 (
23541 buffer.clone(),
23542 (selection.start.text_anchor_in(&buffer_snapshot)
23543 ..selection.end.text_anchor_in(&buffer_snapshot))
23544 .to_point(buffer.read(cx)),
23545 )
23546 })
23547 .into_group_map();
23548
23549 cx.spawn_in(window, async move |_, cx| {
23550 workspace.update_in(cx, |workspace, window, cx| {
23551 Self::open_locations_in_multibuffer(
23552 workspace,
23553 locations,
23554 format!("Selections for '{title}'"),
23555 false,
23556 false,
23557 MultibufferSelectionMode::All,
23558 window,
23559 cx,
23560 );
23561 })
23562 })
23563 .detach();
23564 }
23565
23566 /// Adds a row highlight for the given range. If a row has multiple highlights, the
23567 /// last highlight added will be used.
23568 ///
23569 /// If the range ends at the beginning of a line, then that line will not be highlighted.
23570 pub fn highlight_rows<T: 'static>(
23571 &mut self,
23572 range: Range<Anchor>,
23573 color: Hsla,
23574 options: RowHighlightOptions,
23575 cx: &mut Context<Self>,
23576 ) {
23577 let snapshot = self.buffer().read(cx).snapshot(cx);
23578 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23579 let ix = row_highlights.binary_search_by(|highlight| {
23580 Ordering::Equal
23581 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
23582 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
23583 });
23584
23585 if let Err(mut ix) = ix {
23586 let index = post_inc(&mut self.highlight_order);
23587
23588 // If this range intersects with the preceding highlight, then merge it with
23589 // the preceding highlight. Otherwise insert a new highlight.
23590 let mut merged = false;
23591 if ix > 0 {
23592 let prev_highlight = &mut row_highlights[ix - 1];
23593 if prev_highlight
23594 .range
23595 .end
23596 .cmp(&range.start, &snapshot)
23597 .is_ge()
23598 {
23599 ix -= 1;
23600 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
23601 prev_highlight.range.end = range.end;
23602 }
23603 merged = true;
23604 prev_highlight.index = index;
23605 prev_highlight.color = color;
23606 prev_highlight.options = options;
23607 }
23608 }
23609
23610 if !merged {
23611 row_highlights.insert(
23612 ix,
23613 RowHighlight {
23614 range,
23615 index,
23616 color,
23617 options,
23618 type_id: TypeId::of::<T>(),
23619 },
23620 );
23621 }
23622
23623 // If any of the following highlights intersect with this one, merge them.
23624 while let Some(next_highlight) = row_highlights.get(ix + 1) {
23625 let highlight = &row_highlights[ix];
23626 if next_highlight
23627 .range
23628 .start
23629 .cmp(&highlight.range.end, &snapshot)
23630 .is_le()
23631 {
23632 if next_highlight
23633 .range
23634 .end
23635 .cmp(&highlight.range.end, &snapshot)
23636 .is_gt()
23637 {
23638 row_highlights[ix].range.end = next_highlight.range.end;
23639 }
23640 row_highlights.remove(ix + 1);
23641 } else {
23642 break;
23643 }
23644 }
23645 }
23646 }
23647
23648 /// Remove any highlighted row ranges of the given type that intersect the
23649 /// given ranges.
23650 pub fn remove_highlighted_rows<T: 'static>(
23651 &mut self,
23652 ranges_to_remove: Vec<Range<Anchor>>,
23653 cx: &mut Context<Self>,
23654 ) {
23655 let snapshot = self.buffer().read(cx).snapshot(cx);
23656 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23657 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23658 row_highlights.retain(|highlight| {
23659 while let Some(range_to_remove) = ranges_to_remove.peek() {
23660 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
23661 Ordering::Less | Ordering::Equal => {
23662 ranges_to_remove.next();
23663 }
23664 Ordering::Greater => {
23665 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
23666 Ordering::Less | Ordering::Equal => {
23667 return false;
23668 }
23669 Ordering::Greater => break,
23670 }
23671 }
23672 }
23673 }
23674
23675 true
23676 })
23677 }
23678
23679 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
23680 pub fn clear_row_highlights<T: 'static>(&mut self) {
23681 self.highlighted_rows.remove(&TypeId::of::<T>());
23682 }
23683
23684 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
23685 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
23686 self.highlighted_rows
23687 .get(&TypeId::of::<T>())
23688 .map_or(&[] as &[_], |vec| vec.as_slice())
23689 .iter()
23690 .map(|highlight| (highlight.range.clone(), highlight.color))
23691 }
23692
23693 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
23694 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
23695 /// Allows to ignore certain kinds of highlights.
23696 pub fn highlighted_display_rows(
23697 &self,
23698 window: &mut Window,
23699 cx: &mut App,
23700 ) -> BTreeMap<DisplayRow, LineHighlight> {
23701 let snapshot = self.snapshot(window, cx);
23702 let mut used_highlight_orders = HashMap::default();
23703 self.highlighted_rows
23704 .iter()
23705 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
23706 .fold(
23707 BTreeMap::<DisplayRow, LineHighlight>::new(),
23708 |mut unique_rows, highlight| {
23709 let start = highlight.range.start.to_display_point(&snapshot);
23710 let end = highlight.range.end.to_display_point(&snapshot);
23711 let start_row = start.row().0;
23712 let end_row = if !highlight.range.end.is_max() && end.column() == 0 {
23713 end.row().0.saturating_sub(1)
23714 } else {
23715 end.row().0
23716 };
23717 for row in start_row..=end_row {
23718 let used_index =
23719 used_highlight_orders.entry(row).or_insert(highlight.index);
23720 if highlight.index >= *used_index {
23721 *used_index = highlight.index;
23722 unique_rows.insert(
23723 DisplayRow(row),
23724 LineHighlight {
23725 include_gutter: highlight.options.include_gutter,
23726 border: None,
23727 background: highlight.color.into(),
23728 type_id: Some(highlight.type_id),
23729 },
23730 );
23731 }
23732 }
23733 unique_rows
23734 },
23735 )
23736 }
23737
23738 pub fn highlighted_display_row_for_autoscroll(
23739 &self,
23740 snapshot: &DisplaySnapshot,
23741 ) -> Option<DisplayRow> {
23742 self.highlighted_rows
23743 .values()
23744 .flat_map(|highlighted_rows| highlighted_rows.iter())
23745 .filter_map(|highlight| {
23746 if highlight.options.autoscroll {
23747 Some(highlight.range.start.to_display_point(snapshot).row())
23748 } else {
23749 None
23750 }
23751 })
23752 .min()
23753 }
23754
23755 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
23756 self.highlight_background(
23757 HighlightKey::SearchWithinRange,
23758 ranges,
23759 |_, colors| colors.colors().editor_document_highlight_read_background,
23760 cx,
23761 )
23762 }
23763
23764 pub fn set_breadcrumb_header(&mut self, new_header: String) {
23765 self.breadcrumb_header = Some(new_header);
23766 }
23767
23768 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
23769 self.clear_background_highlights(HighlightKey::SearchWithinRange, cx);
23770 }
23771
23772 pub fn highlight_background(
23773 &mut self,
23774 key: HighlightKey,
23775 ranges: &[Range<Anchor>],
23776 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
23777 cx: &mut Context<Self>,
23778 ) {
23779 self.background_highlights
23780 .insert(key, (Arc::new(color_fetcher), Arc::from(ranges)));
23781 self.scrollbar_marker_state.dirty = true;
23782 cx.notify();
23783 }
23784
23785 pub fn clear_background_highlights(
23786 &mut self,
23787 key: HighlightKey,
23788 cx: &mut Context<Self>,
23789 ) -> Option<BackgroundHighlight> {
23790 let text_highlights = self.background_highlights.remove(&key)?;
23791 if !text_highlights.1.is_empty() {
23792 self.scrollbar_marker_state.dirty = true;
23793 cx.notify();
23794 }
23795 Some(text_highlights)
23796 }
23797
23798 pub fn highlight_gutter<T: 'static>(
23799 &mut self,
23800 ranges: impl Into<Vec<Range<Anchor>>>,
23801 color_fetcher: fn(&App) -> Hsla,
23802 cx: &mut Context<Self>,
23803 ) {
23804 self.gutter_highlights
23805 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
23806 cx.notify();
23807 }
23808
23809 pub fn clear_gutter_highlights<T: 'static>(
23810 &mut self,
23811 cx: &mut Context<Self>,
23812 ) -> Option<GutterHighlight> {
23813 cx.notify();
23814 self.gutter_highlights.remove(&TypeId::of::<T>())
23815 }
23816
23817 pub fn insert_gutter_highlight<T: 'static>(
23818 &mut self,
23819 range: Range<Anchor>,
23820 color_fetcher: fn(&App) -> Hsla,
23821 cx: &mut Context<Self>,
23822 ) {
23823 let snapshot = self.buffer().read(cx).snapshot(cx);
23824 let mut highlights = self
23825 .gutter_highlights
23826 .remove(&TypeId::of::<T>())
23827 .map(|(_, highlights)| highlights)
23828 .unwrap_or_default();
23829 let ix = highlights.binary_search_by(|highlight| {
23830 Ordering::Equal
23831 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
23832 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
23833 });
23834 if let Err(ix) = ix {
23835 highlights.insert(ix, range);
23836 }
23837 self.gutter_highlights
23838 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
23839 }
23840
23841 pub fn remove_gutter_highlights<T: 'static>(
23842 &mut self,
23843 ranges_to_remove: Vec<Range<Anchor>>,
23844 cx: &mut Context<Self>,
23845 ) {
23846 let snapshot = self.buffer().read(cx).snapshot(cx);
23847 let Some((color_fetcher, mut gutter_highlights)) =
23848 self.gutter_highlights.remove(&TypeId::of::<T>())
23849 else {
23850 return;
23851 };
23852 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23853 gutter_highlights.retain(|highlight| {
23854 while let Some(range_to_remove) = ranges_to_remove.peek() {
23855 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
23856 Ordering::Less | Ordering::Equal => {
23857 ranges_to_remove.next();
23858 }
23859 Ordering::Greater => {
23860 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
23861 Ordering::Less | Ordering::Equal => {
23862 return false;
23863 }
23864 Ordering::Greater => break,
23865 }
23866 }
23867 }
23868 }
23869
23870 true
23871 });
23872 self.gutter_highlights
23873 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
23874 }
23875
23876 #[cfg(any(test, feature = "test-support"))]
23877 pub fn all_text_highlights(
23878 &self,
23879 window: &mut Window,
23880 cx: &mut Context<Self>,
23881 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
23882 let snapshot = self.snapshot(window, cx);
23883 self.display_map.update(cx, |display_map, _| {
23884 display_map
23885 .all_text_highlights()
23886 .map(|(_, highlight)| {
23887 let (style, ranges) = highlight.as_ref();
23888 (
23889 *style,
23890 ranges
23891 .iter()
23892 .map(|range| range.clone().to_display_points(&snapshot))
23893 .collect(),
23894 )
23895 })
23896 .collect()
23897 })
23898 }
23899
23900 #[cfg(any(test, feature = "test-support"))]
23901 pub fn all_text_background_highlights(
23902 &self,
23903 window: &mut Window,
23904 cx: &mut Context<Self>,
23905 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23906 let snapshot = self.snapshot(window, cx);
23907 let buffer = &snapshot.buffer_snapshot();
23908 let start = buffer.anchor_before(MultiBufferOffset(0));
23909 let end = buffer.anchor_after(buffer.len());
23910 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
23911 }
23912
23913 #[cfg(any(test, feature = "test-support"))]
23914 pub fn sorted_background_highlights_in_range(
23915 &self,
23916 search_range: Range<Anchor>,
23917 display_snapshot: &DisplaySnapshot,
23918 theme: &Theme,
23919 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23920 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
23921 res.sort_by(|a, b| {
23922 a.0.start
23923 .cmp(&b.0.start)
23924 .then_with(|| a.0.end.cmp(&b.0.end))
23925 .then_with(|| a.1.cmp(&b.1))
23926 });
23927 res
23928 }
23929
23930 #[cfg(any(test, feature = "test-support"))]
23931 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
23932 let snapshot = self.buffer().read(cx).snapshot(cx);
23933
23934 let highlights = self
23935 .background_highlights
23936 .get(&HighlightKey::BufferSearchHighlights);
23937
23938 if let Some((_color, ranges)) = highlights {
23939 ranges
23940 .iter()
23941 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
23942 .collect_vec()
23943 } else {
23944 vec![]
23945 }
23946 }
23947
23948 pub fn has_background_highlights(&self, key: HighlightKey) -> bool {
23949 self.background_highlights
23950 .get(&key)
23951 .is_some_and(|(_, highlights)| !highlights.is_empty())
23952 }
23953
23954 /// Returns all background highlights for a given range.
23955 ///
23956 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
23957 pub fn background_highlights_in_range(
23958 &self,
23959 search_range: Range<Anchor>,
23960 display_snapshot: &DisplaySnapshot,
23961 theme: &Theme,
23962 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23963 let mut results = Vec::new();
23964 for (color_fetcher, ranges) in self.background_highlights.values() {
23965 let start_ix = match ranges.binary_search_by(|probe| {
23966 let cmp = probe
23967 .end
23968 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23969 if cmp.is_gt() {
23970 Ordering::Greater
23971 } else {
23972 Ordering::Less
23973 }
23974 }) {
23975 Ok(i) | Err(i) => i,
23976 };
23977 for (index, range) in ranges[start_ix..].iter().enumerate() {
23978 if range
23979 .start
23980 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23981 .is_ge()
23982 {
23983 break;
23984 }
23985
23986 let color = color_fetcher(&(start_ix + index), theme);
23987 let start = range.start.to_display_point(display_snapshot);
23988 let end = range.end.to_display_point(display_snapshot);
23989 results.push((start..end, color))
23990 }
23991 }
23992 results
23993 }
23994
23995 pub fn gutter_highlights_in_range(
23996 &self,
23997 search_range: Range<Anchor>,
23998 display_snapshot: &DisplaySnapshot,
23999 cx: &App,
24000 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
24001 let mut results = Vec::new();
24002 for (color_fetcher, ranges) in self.gutter_highlights.values() {
24003 let color = color_fetcher(cx);
24004 let start_ix = match ranges.binary_search_by(|probe| {
24005 let cmp = probe
24006 .end
24007 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
24008 if cmp.is_gt() {
24009 Ordering::Greater
24010 } else {
24011 Ordering::Less
24012 }
24013 }) {
24014 Ok(i) | Err(i) => i,
24015 };
24016 for range in &ranges[start_ix..] {
24017 if range
24018 .start
24019 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
24020 .is_ge()
24021 {
24022 break;
24023 }
24024
24025 let start = range.start.to_display_point(display_snapshot);
24026 let end = range.end.to_display_point(display_snapshot);
24027 results.push((start..end, color))
24028 }
24029 }
24030 results
24031 }
24032
24033 /// Get the text ranges corresponding to the redaction query
24034 pub fn redacted_ranges(
24035 &self,
24036 search_range: Range<Anchor>,
24037 display_snapshot: &DisplaySnapshot,
24038 cx: &App,
24039 ) -> Vec<Range<DisplayPoint>> {
24040 display_snapshot
24041 .buffer_snapshot()
24042 .redacted_ranges(search_range, |file| {
24043 if let Some(file) = file {
24044 file.is_private()
24045 && EditorSettings::get(
24046 Some(SettingsLocation {
24047 worktree_id: file.worktree_id(cx),
24048 path: file.path().as_ref(),
24049 }),
24050 cx,
24051 )
24052 .redact_private_values
24053 } else {
24054 false
24055 }
24056 })
24057 .map(|range| {
24058 range.start.to_display_point(display_snapshot)
24059 ..range.end.to_display_point(display_snapshot)
24060 })
24061 .collect()
24062 }
24063
24064 pub fn highlight_text_key(
24065 &mut self,
24066 key: HighlightKey,
24067 ranges: Vec<Range<Anchor>>,
24068 style: HighlightStyle,
24069 merge: bool,
24070 cx: &mut Context<Self>,
24071 ) {
24072 self.display_map.update(cx, |map, cx| {
24073 map.highlight_text(key, ranges, style, merge, cx);
24074 });
24075 cx.notify();
24076 }
24077
24078 pub fn highlight_text(
24079 &mut self,
24080 key: HighlightKey,
24081 ranges: Vec<Range<Anchor>>,
24082 style: HighlightStyle,
24083 cx: &mut Context<Self>,
24084 ) {
24085 self.display_map.update(cx, |map, cx| {
24086 map.highlight_text(key, ranges, style, false, cx)
24087 });
24088 cx.notify();
24089 }
24090
24091 pub fn text_highlights<'a>(
24092 &'a self,
24093 key: HighlightKey,
24094 cx: &'a App,
24095 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
24096 self.display_map.read(cx).text_highlights(key)
24097 }
24098
24099 pub fn clear_highlights(&mut self, key: HighlightKey, cx: &mut Context<Self>) {
24100 let cleared = self
24101 .display_map
24102 .update(cx, |map, _| map.clear_highlights(key));
24103 if cleared {
24104 cx.notify();
24105 }
24106 }
24107
24108 pub fn clear_highlights_with(
24109 &mut self,
24110 f: &mut dyn FnMut(&HighlightKey) -> bool,
24111 cx: &mut Context<Self>,
24112 ) {
24113 let cleared = self
24114 .display_map
24115 .update(cx, |map, _| map.clear_highlights_with(f));
24116 if cleared {
24117 cx.notify();
24118 }
24119 }
24120
24121 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
24122 (self.read_only(cx) || self.blink_manager.read(cx).visible())
24123 && self.focus_handle.is_focused(window)
24124 }
24125
24126 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
24127 self.show_cursor_when_unfocused = is_enabled;
24128 cx.notify();
24129 }
24130
24131 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
24132 cx.notify();
24133 }
24134
24135 fn on_debug_session_event(
24136 &mut self,
24137 _session: Entity<Session>,
24138 event: &SessionEvent,
24139 cx: &mut Context<Self>,
24140 ) {
24141 if let SessionEvent::InvalidateInlineValue = event {
24142 self.refresh_inline_values(cx);
24143 }
24144 }
24145
24146 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
24147 let Some(semantics) = self.semantics_provider.clone() else {
24148 return;
24149 };
24150
24151 if !self.inline_value_cache.enabled {
24152 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
24153 self.splice_inlays(&inlays, Vec::new(), cx);
24154 return;
24155 }
24156
24157 let current_execution_position = self
24158 .highlighted_rows
24159 .get(&TypeId::of::<ActiveDebugLine>())
24160 .and_then(|lines| lines.last().map(|line| line.range.end));
24161
24162 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
24163 let inline_values = editor
24164 .update(cx, |editor, cx| {
24165 let Some(current_execution_position) = current_execution_position else {
24166 return Some(Task::ready(Ok(Vec::new())));
24167 };
24168
24169 let (buffer, buffer_anchor) =
24170 editor.buffer.read_with(cx, |multibuffer, cx| {
24171 let multibuffer_snapshot = multibuffer.snapshot(cx);
24172 let (buffer_anchor, _) = multibuffer_snapshot
24173 .anchor_to_buffer_anchor(current_execution_position)?;
24174 let buffer = multibuffer.buffer(buffer_anchor.buffer_id)?;
24175 Some((buffer, buffer_anchor))
24176 })?;
24177
24178 let range = buffer.read(cx).anchor_before(0)..buffer_anchor;
24179
24180 semantics.inline_values(buffer, range, cx)
24181 })
24182 .ok()
24183 .flatten()?
24184 .await
24185 .context("refreshing debugger inlays")
24186 .log_err()?;
24187
24188 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
24189
24190 for (buffer_id, inline_value) in inline_values
24191 .into_iter()
24192 .map(|hint| (hint.position.buffer_id, hint))
24193 {
24194 buffer_inline_values
24195 .entry(buffer_id)
24196 .or_default()
24197 .push(inline_value);
24198 }
24199
24200 editor
24201 .update(cx, |editor, cx| {
24202 let snapshot = editor.buffer.read(cx).snapshot(cx);
24203 let mut new_inlays = Vec::default();
24204
24205 for (_buffer_id, inline_values) in buffer_inline_values {
24206 for hint in inline_values {
24207 let Some(anchor) = snapshot.anchor_in_excerpt(hint.position) else {
24208 continue;
24209 };
24210 let inlay = Inlay::debugger(
24211 post_inc(&mut editor.next_inlay_id),
24212 anchor,
24213 hint.text(),
24214 );
24215 if !inlay.text().chars().contains(&'\n') {
24216 new_inlays.push(inlay);
24217 }
24218 }
24219 }
24220
24221 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
24222 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
24223
24224 editor.splice_inlays(&inlay_ids, new_inlays, cx);
24225 })
24226 .ok()?;
24227 Some(())
24228 });
24229 }
24230
24231 fn on_buffer_event(
24232 &mut self,
24233 multibuffer: &Entity<MultiBuffer>,
24234 event: &multi_buffer::Event,
24235 window: &mut Window,
24236 cx: &mut Context<Self>,
24237 ) {
24238 match event {
24239 multi_buffer::Event::Edited {
24240 edited_buffer,
24241 is_local,
24242 } => {
24243 self.scrollbar_marker_state.dirty = true;
24244 self.active_indent_guides_state.dirty = true;
24245 self.refresh_active_diagnostics(cx);
24246 self.refresh_code_actions(window, cx);
24247 self.refresh_single_line_folds(window, cx);
24248 let snapshot = self.snapshot(window, cx);
24249 self.refresh_matching_bracket_highlights(&snapshot, cx);
24250 self.refresh_outline_symbols_at_cursor(cx);
24251 self.refresh_sticky_headers(&snapshot, cx);
24252 if *is_local && self.has_active_edit_prediction() {
24253 self.update_visible_edit_prediction(window, cx);
24254 }
24255
24256 // Clean up orphaned review comments after edits
24257 self.cleanup_orphaned_review_comments(cx);
24258
24259 if let Some(buffer) = edited_buffer {
24260 if buffer.read(cx).file().is_none() {
24261 cx.emit(EditorEvent::TitleChanged);
24262 }
24263
24264 if self.project.is_some() {
24265 let buffer_id = buffer.read(cx).remote_id();
24266 self.register_buffer(buffer_id, cx);
24267 self.update_lsp_data(Some(buffer_id), window, cx);
24268 self.refresh_inlay_hints(
24269 InlayHintRefreshReason::BufferEdited(buffer_id),
24270 cx,
24271 );
24272 }
24273 }
24274
24275 cx.emit(EditorEvent::BufferEdited);
24276 cx.emit(SearchEvent::MatchesInvalidated);
24277
24278 let Some(project) = &self.project else { return };
24279 let (telemetry, is_via_ssh) = {
24280 let project = project.read(cx);
24281 let telemetry = project.client().telemetry().clone();
24282 let is_via_ssh = project.is_via_remote_server();
24283 (telemetry, is_via_ssh)
24284 };
24285 telemetry.log_edit_event("editor", is_via_ssh);
24286 }
24287 multi_buffer::Event::BufferRangesUpdated {
24288 buffer,
24289 ranges,
24290 path_key,
24291 } => {
24292 self.refresh_document_highlights(cx);
24293 let buffer_id = buffer.read(cx).remote_id();
24294 if self.buffer.read(cx).diff_for(buffer_id).is_none()
24295 && let Some(project) = &self.project
24296 {
24297 update_uncommitted_diff_for_buffer(
24298 cx.entity(),
24299 project,
24300 [buffer.clone()],
24301 self.buffer.clone(),
24302 cx,
24303 )
24304 .detach();
24305 }
24306 self.register_visible_buffers(cx);
24307 self.update_lsp_data(Some(buffer_id), window, cx);
24308 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
24309 self.refresh_runnables(None, window, cx);
24310 self.bracket_fetched_tree_sitter_chunks
24311 .retain(|range, _| range.start.buffer_id != buffer_id);
24312 self.colorize_brackets(false, cx);
24313 self.refresh_selected_text_highlights(&self.display_snapshot(cx), true, window, cx);
24314 self.semantic_token_state.invalidate_buffer(&buffer_id);
24315 cx.emit(EditorEvent::BufferRangesUpdated {
24316 buffer: buffer.clone(),
24317 ranges: ranges.clone(),
24318 path_key: path_key.clone(),
24319 });
24320 }
24321 multi_buffer::Event::BuffersRemoved { removed_buffer_ids } => {
24322 if let Some(inlay_hints) = &mut self.inlay_hints {
24323 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
24324 }
24325 self.refresh_inlay_hints(
24326 InlayHintRefreshReason::BuffersRemoved(removed_buffer_ids.clone()),
24327 cx,
24328 );
24329 for buffer_id in removed_buffer_ids {
24330 self.registered_buffers.remove(buffer_id);
24331 self.clear_runnables(Some(*buffer_id));
24332 self.semantic_token_state.invalidate_buffer(buffer_id);
24333 self.display_map.update(cx, |display_map, cx| {
24334 display_map.invalidate_semantic_highlights(*buffer_id);
24335 display_map.clear_lsp_folding_ranges(*buffer_id, cx);
24336 });
24337 }
24338
24339 self.display_map.update(cx, |display_map, cx| {
24340 display_map.unfold_buffers(removed_buffer_ids.iter().copied(), cx);
24341 });
24342
24343 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24344 cx.emit(EditorEvent::BuffersRemoved {
24345 removed_buffer_ids: removed_buffer_ids.clone(),
24346 });
24347 }
24348 multi_buffer::Event::BuffersEdited { buffer_ids } => {
24349 self.display_map.update(cx, |map, cx| {
24350 map.unfold_buffers(buffer_ids.iter().copied(), cx)
24351 });
24352 cx.emit(EditorEvent::BuffersEdited {
24353 buffer_ids: buffer_ids.clone(),
24354 });
24355 }
24356 multi_buffer::Event::Reparsed(buffer_id) => {
24357 self.refresh_runnables(Some(*buffer_id), window, cx);
24358 self.refresh_selected_text_highlights(&self.display_snapshot(cx), true, window, cx);
24359 self.colorize_brackets(true, cx);
24360 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24361
24362 cx.emit(EditorEvent::Reparsed(*buffer_id));
24363 }
24364 multi_buffer::Event::DiffHunksToggled => {
24365 self.refresh_runnables(None, window, cx);
24366 }
24367 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
24368 if !is_fresh_language {
24369 self.registered_buffers.remove(&buffer_id);
24370 }
24371 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24372 cx.emit(EditorEvent::Reparsed(*buffer_id));
24373 self.update_edit_prediction_settings(cx);
24374 cx.notify();
24375 }
24376 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
24377 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
24378 multi_buffer::Event::FileHandleChanged
24379 | multi_buffer::Event::Reloaded
24380 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
24381 multi_buffer::Event::DiagnosticsUpdated => {
24382 self.update_diagnostics_state(window, cx);
24383 }
24384 _ => {}
24385 };
24386 }
24387
24388 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
24389 if !self.diagnostics_enabled() {
24390 return;
24391 }
24392 self.refresh_active_diagnostics(cx);
24393 self.refresh_inline_diagnostics(true, window, cx);
24394 self.scrollbar_marker_state.dirty = true;
24395 cx.notify();
24396 }
24397
24398 pub fn start_temporary_diff_override(&mut self) {
24399 self.load_diff_task.take();
24400 self.temporary_diff_override = true;
24401 }
24402
24403 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
24404 self.temporary_diff_override = false;
24405 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
24406 self.buffer.update(cx, |buffer, cx| {
24407 buffer.set_all_diff_hunks_collapsed(cx);
24408 });
24409
24410 if let Some(project) = self.project.clone() {
24411 self.load_diff_task = Some(
24412 update_uncommitted_diff_for_buffer(
24413 cx.entity(),
24414 &project,
24415 self.buffer.read(cx).all_buffers(),
24416 self.buffer.clone(),
24417 cx,
24418 )
24419 .shared(),
24420 );
24421 }
24422 }
24423
24424 fn on_display_map_changed(
24425 &mut self,
24426 _: Entity<DisplayMap>,
24427 _: &mut Window,
24428 cx: &mut Context<Self>,
24429 ) {
24430 cx.notify();
24431 }
24432
24433 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
24434 if !self.mode.is_full() {
24435 return None;
24436 }
24437
24438 let theme_settings = theme_settings::ThemeSettings::get_global(cx);
24439 let theme = cx.theme();
24440 let accent_colors = theme.accents().clone();
24441
24442 let accent_overrides = theme_settings
24443 .theme_overrides
24444 .get(theme.name.as_ref())
24445 .map(|theme_style| &theme_style.accents)
24446 .into_iter()
24447 .flatten()
24448 .chain(
24449 theme_settings
24450 .experimental_theme_overrides
24451 .as_ref()
24452 .map(|overrides| &overrides.accents)
24453 .into_iter()
24454 .flatten(),
24455 )
24456 .flat_map(|accent| accent.0.clone().map(SharedString::from))
24457 .collect();
24458
24459 Some(AccentData {
24460 colors: accent_colors,
24461 overrides: accent_overrides,
24462 })
24463 }
24464
24465 fn fetch_applicable_language_settings(
24466 &self,
24467 cx: &App,
24468 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
24469 if !self.mode.is_full() {
24470 return HashMap::default();
24471 }
24472
24473 self.buffer().read(cx).all_buffers().into_iter().fold(
24474 HashMap::default(),
24475 |mut acc, buffer| {
24476 let buffer = buffer.read(cx);
24477 let language = buffer.language().map(|language| language.name());
24478 if let hash_map::Entry::Vacant(v) = acc.entry(language) {
24479 v.insert(LanguageSettings::for_buffer(&buffer, cx).into_owned());
24480 }
24481 acc
24482 },
24483 )
24484 }
24485
24486 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24487 let new_language_settings = self.fetch_applicable_language_settings(cx);
24488 let language_settings_changed = new_language_settings != self.applicable_language_settings;
24489 self.applicable_language_settings = new_language_settings;
24490
24491 let new_accents = self.fetch_accent_data(cx);
24492 let accents_changed = new_accents != self.accent_data;
24493 self.accent_data = new_accents;
24494
24495 if self.diagnostics_enabled() {
24496 let new_severity = EditorSettings::get_global(cx)
24497 .diagnostics_max_severity
24498 .unwrap_or(DiagnosticSeverity::Hint);
24499 self.set_max_diagnostics_severity(new_severity, cx);
24500 }
24501 self.refresh_runnables(None, window, cx);
24502 self.update_edit_prediction_settings(cx);
24503 self.refresh_edit_prediction(true, false, window, cx);
24504 self.refresh_inline_values(cx);
24505
24506 let old_cursor_shape = self.cursor_shape;
24507 let old_show_breadcrumbs = self.show_breadcrumbs;
24508
24509 {
24510 let editor_settings = EditorSettings::get_global(cx);
24511 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
24512 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
24513 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
24514 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
24515 }
24516
24517 if old_cursor_shape != self.cursor_shape {
24518 cx.emit(EditorEvent::CursorShapeChanged);
24519 }
24520
24521 if old_show_breadcrumbs != self.show_breadcrumbs {
24522 cx.emit(EditorEvent::BreadcrumbsChanged);
24523 }
24524
24525 let (restore_unsaved_buffers, show_inline_diagnostics, inline_blame_enabled) = {
24526 let project_settings = ProjectSettings::get_global(cx);
24527 (
24528 project_settings.session.restore_unsaved_buffers,
24529 project_settings.diagnostics.inline.enabled,
24530 project_settings.git.inline_blame.enabled,
24531 )
24532 };
24533 self.buffer_serialization = self
24534 .should_serialize_buffer()
24535 .then(|| BufferSerialization::new(restore_unsaved_buffers));
24536
24537 if self.mode.is_full() {
24538 if self.show_inline_diagnostics != show_inline_diagnostics {
24539 self.show_inline_diagnostics = show_inline_diagnostics;
24540 self.refresh_inline_diagnostics(false, window, cx);
24541 }
24542
24543 if self.git_blame_inline_enabled != inline_blame_enabled {
24544 self.toggle_git_blame_inline_internal(false, window, cx);
24545 }
24546
24547 let minimap_settings = EditorSettings::get_global(cx).minimap;
24548 if self.minimap_visibility != MinimapVisibility::Disabled {
24549 if self.minimap_visibility.settings_visibility()
24550 != minimap_settings.minimap_enabled()
24551 {
24552 self.set_minimap_visibility(
24553 MinimapVisibility::for_mode(self.mode(), cx),
24554 window,
24555 cx,
24556 );
24557 } else if let Some(minimap_entity) = self.minimap.as_ref() {
24558 minimap_entity.update(cx, |minimap_editor, cx| {
24559 minimap_editor.update_minimap_configuration(minimap_settings, cx)
24560 })
24561 }
24562 }
24563
24564 if language_settings_changed || accents_changed {
24565 self.colorize_brackets(true, cx);
24566 }
24567
24568 if language_settings_changed {
24569 self.clear_disabled_lsp_folding_ranges(window, cx);
24570 self.refresh_document_symbols(None, cx);
24571 }
24572
24573 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
24574 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
24575 }) {
24576 if !inlay_splice.is_empty() {
24577 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
24578 }
24579 self.refresh_document_colors(None, window, cx);
24580 }
24581
24582 self.refresh_inlay_hints(
24583 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
24584 self.selections.newest_anchor().head(),
24585 &self.buffer.read(cx).snapshot(cx),
24586 cx,
24587 )),
24588 cx,
24589 );
24590
24591 let new_semantic_token_rules = ProjectSettings::get_global(cx)
24592 .global_lsp_settings
24593 .semantic_token_rules
24594 .clone();
24595 let semantic_token_rules_changed = self
24596 .semantic_token_state
24597 .update_rules(new_semantic_token_rules);
24598 if language_settings_changed || semantic_token_rules_changed {
24599 self.invalidate_semantic_tokens(None);
24600 self.refresh_semantic_tokens(None, None, cx);
24601 }
24602 }
24603
24604 cx.notify();
24605 }
24606
24607 fn theme_changed(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24608 if !self.mode.is_full() {
24609 return;
24610 }
24611
24612 let new_accents = self.fetch_accent_data(cx);
24613 if new_accents != self.accent_data {
24614 self.accent_data = new_accents;
24615 self.colorize_brackets(true, cx);
24616 }
24617
24618 self.invalidate_semantic_tokens(None);
24619 self.refresh_semantic_tokens(None, None, cx);
24620 }
24621
24622 pub fn set_searchable(&mut self, searchable: bool) {
24623 self.searchable = searchable;
24624 }
24625
24626 pub fn searchable(&self) -> bool {
24627 self.searchable
24628 }
24629
24630 pub fn open_excerpts_in_split(
24631 &mut self,
24632 _: &OpenExcerptsSplit,
24633 window: &mut Window,
24634 cx: &mut Context<Self>,
24635 ) {
24636 self.open_excerpts_common(None, true, window, cx)
24637 }
24638
24639 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
24640 self.open_excerpts_common(None, false, window, cx)
24641 }
24642
24643 pub(crate) fn open_excerpts_common(
24644 &mut self,
24645 jump_data: Option<JumpData>,
24646 split: bool,
24647 window: &mut Window,
24648 cx: &mut Context<Self>,
24649 ) {
24650 if self.buffer.read(cx).is_singleton() {
24651 cx.propagate();
24652 return;
24653 }
24654
24655 let mut new_selections_by_buffer = HashMap::default();
24656 match &jump_data {
24657 Some(JumpData::MultiBufferPoint {
24658 anchor,
24659 position,
24660 line_offset_from_top,
24661 }) => {
24662 if let Some(buffer) = self.buffer.read(cx).buffer(anchor.buffer_id) {
24663 let buffer_snapshot = buffer.read(cx).snapshot();
24664 let jump_to_point = if buffer_snapshot.can_resolve(&anchor) {
24665 language::ToPoint::to_point(anchor, &buffer_snapshot)
24666 } else {
24667 buffer_snapshot.clip_point(*position, Bias::Left)
24668 };
24669 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
24670 new_selections_by_buffer.insert(
24671 buffer,
24672 (
24673 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
24674 Some(*line_offset_from_top),
24675 ),
24676 );
24677 }
24678 }
24679 Some(JumpData::MultiBufferRow {
24680 row,
24681 line_offset_from_top,
24682 }) => {
24683 let point = MultiBufferPoint::new(row.0, 0);
24684 if let Some((buffer, buffer_point)) =
24685 self.buffer.read(cx).point_to_buffer_point(point, cx)
24686 {
24687 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
24688 new_selections_by_buffer
24689 .entry(buffer)
24690 .or_insert((Vec::new(), Some(*line_offset_from_top)))
24691 .0
24692 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
24693 }
24694 }
24695 None => {
24696 let selections = self
24697 .selections
24698 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
24699 let multi_buffer = self.buffer.read(cx);
24700 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
24701 for selection in selections {
24702 for (snapshot, range, anchor) in multi_buffer_snapshot
24703 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
24704 {
24705 if let Some((text_anchor, _)) = anchor.and_then(|anchor| {
24706 multi_buffer_snapshot.anchor_to_buffer_anchor(anchor)
24707 }) {
24708 let Some(buffer_handle) = multi_buffer.buffer(text_anchor.buffer_id)
24709 else {
24710 continue;
24711 };
24712 let offset = text::ToOffset::to_offset(
24713 &text_anchor,
24714 &buffer_handle.read(cx).snapshot(),
24715 );
24716 let range = BufferOffset(offset)..BufferOffset(offset);
24717 new_selections_by_buffer
24718 .entry(buffer_handle)
24719 .or_insert((Vec::new(), None))
24720 .0
24721 .push(range)
24722 } else {
24723 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
24724 else {
24725 continue;
24726 };
24727 new_selections_by_buffer
24728 .entry(buffer_handle)
24729 .or_insert((Vec::new(), None))
24730 .0
24731 .push(range)
24732 }
24733 }
24734 }
24735 }
24736 }
24737
24738 if self.delegate_open_excerpts {
24739 let selections_by_buffer: HashMap<_, _> = new_selections_by_buffer
24740 .into_iter()
24741 .map(|(buffer, value)| (buffer.read(cx).remote_id(), value))
24742 .collect();
24743 if !selections_by_buffer.is_empty() {
24744 cx.emit(EditorEvent::OpenExcerptsRequested {
24745 selections_by_buffer,
24746 split,
24747 });
24748 }
24749 return;
24750 }
24751
24752 let Some(workspace) = self.workspace() else {
24753 cx.propagate();
24754 return;
24755 };
24756
24757 new_selections_by_buffer
24758 .retain(|buffer, _| buffer.read(cx).file().is_none_or(|file| file.can_open()));
24759
24760 if new_selections_by_buffer.is_empty() {
24761 return;
24762 }
24763
24764 Self::open_buffers_in_workspace(
24765 workspace.downgrade(),
24766 new_selections_by_buffer,
24767 split,
24768 window,
24769 cx,
24770 );
24771 }
24772
24773 pub(crate) fn open_buffers_in_workspace(
24774 workspace: WeakEntity<Workspace>,
24775 new_selections_by_buffer: HashMap<
24776 Entity<language::Buffer>,
24777 (Vec<Range<BufferOffset>>, Option<u32>),
24778 >,
24779 split: bool,
24780 window: &mut Window,
24781 cx: &mut App,
24782 ) {
24783 // We defer the pane interaction because we ourselves are a workspace item
24784 // and activating a new item causes the pane to call a method on us reentrantly,
24785 // which panics if we're on the stack.
24786 window.defer(cx, move |window, cx| {
24787 workspace
24788 .update(cx, |workspace, cx| {
24789 let pane = if split {
24790 workspace.adjacent_pane(window, cx)
24791 } else {
24792 workspace.active_pane().clone()
24793 };
24794
24795 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
24796 let buffer_read = buffer.read(cx);
24797 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
24798 (true, project::File::from_dyn(Some(file)).is_some())
24799 } else {
24800 (false, false)
24801 };
24802
24803 // If project file is none workspace.open_project_item will fail to open the excerpt
24804 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
24805 // so we check if there's a tab match in that case first
24806 let editor = (!has_file || !is_project_file)
24807 .then(|| {
24808 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
24809 // so `workspace.open_project_item` will never find them, always opening a new editor.
24810 // Instead, we try to activate the existing editor in the pane first.
24811 let (editor, pane_item_index, pane_item_id) =
24812 pane.read(cx).items().enumerate().find_map(|(i, item)| {
24813 let editor = item.downcast::<Editor>()?;
24814 let singleton_buffer =
24815 editor.read(cx).buffer().read(cx).as_singleton()?;
24816 if singleton_buffer == buffer {
24817 Some((editor, i, item.item_id()))
24818 } else {
24819 None
24820 }
24821 })?;
24822 pane.update(cx, |pane, cx| {
24823 pane.activate_item(pane_item_index, true, true, window, cx);
24824 if !PreviewTabsSettings::get_global(cx)
24825 .enable_preview_from_multibuffer
24826 {
24827 pane.unpreview_item_if_preview(pane_item_id);
24828 }
24829 });
24830 Some(editor)
24831 })
24832 .flatten()
24833 .unwrap_or_else(|| {
24834 let keep_old_preview = PreviewTabsSettings::get_global(cx)
24835 .enable_keep_preview_on_code_navigation;
24836 let allow_new_preview = PreviewTabsSettings::get_global(cx)
24837 .enable_preview_from_multibuffer;
24838 workspace.open_project_item::<Self>(
24839 pane.clone(),
24840 buffer,
24841 true,
24842 true,
24843 keep_old_preview,
24844 allow_new_preview,
24845 window,
24846 cx,
24847 )
24848 });
24849
24850 editor.update(cx, |editor, cx| {
24851 if has_file && !is_project_file {
24852 editor.set_read_only(true);
24853 }
24854 let autoscroll = match scroll_offset {
24855 Some(scroll_offset) => {
24856 Autoscroll::top_relative(scroll_offset as usize)
24857 }
24858 None => Autoscroll::newest(),
24859 };
24860 let nav_history = editor.nav_history.take();
24861 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
24862 let Some(buffer_snapshot) = multibuffer_snapshot.as_singleton() else {
24863 return;
24864 };
24865 editor.change_selections(
24866 SelectionEffects::scroll(autoscroll),
24867 window,
24868 cx,
24869 |s| {
24870 s.select_ranges(ranges.into_iter().map(|range| {
24871 let range = buffer_snapshot.anchor_before(range.start)
24872 ..buffer_snapshot.anchor_after(range.end);
24873 multibuffer_snapshot
24874 .buffer_anchor_range_to_anchor_range(range)
24875 .unwrap()
24876 }));
24877 },
24878 );
24879 editor.nav_history = nav_history;
24880 });
24881 }
24882 })
24883 .ok();
24884 });
24885 }
24886
24887 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
24888 let snapshot = self.buffer.read(cx).read(cx);
24889 let (_, ranges) = self.text_highlights(HighlightKey::InputComposition, cx)?;
24890 Some(
24891 ranges
24892 .iter()
24893 .map(move |range| {
24894 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
24895 })
24896 .collect(),
24897 )
24898 }
24899
24900 fn selection_replacement_ranges(
24901 &self,
24902 range: Range<MultiBufferOffsetUtf16>,
24903 cx: &mut App,
24904 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
24905 let selections = self
24906 .selections
24907 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24908 let newest_selection = selections
24909 .iter()
24910 .max_by_key(|selection| selection.id)
24911 .unwrap();
24912 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
24913 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
24914 let snapshot = self.buffer.read(cx).read(cx);
24915 selections
24916 .into_iter()
24917 .map(|mut selection| {
24918 selection.start.0.0 =
24919 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
24920 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
24921 snapshot.clip_offset_utf16(selection.start, Bias::Left)
24922 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
24923 })
24924 .collect()
24925 }
24926
24927 fn report_editor_event(
24928 &self,
24929 reported_event: ReportEditorEvent,
24930 file_extension: Option<String>,
24931 cx: &App,
24932 ) {
24933 if cfg!(any(test, feature = "test-support")) {
24934 return;
24935 }
24936
24937 let Some(project) = &self.project else { return };
24938
24939 // If None, we are in a file without an extension
24940 let file = self
24941 .buffer
24942 .read(cx)
24943 .as_singleton()
24944 .and_then(|b| b.read(cx).file());
24945 let file_extension = file_extension.or(file
24946 .as_ref()
24947 .and_then(|file| Path::new(file.file_name(cx)).extension())
24948 .and_then(|e| e.to_str())
24949 .map(|a| a.to_string()));
24950
24951 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
24952 .map(|vim_mode| vim_mode.0)
24953 .unwrap_or(false);
24954
24955 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
24956 let copilot_enabled = edit_predictions_provider
24957 == language::language_settings::EditPredictionProvider::Copilot;
24958 let copilot_enabled_for_language = self
24959 .buffer
24960 .read(cx)
24961 .language_settings(cx)
24962 .show_edit_predictions;
24963
24964 let project = project.read(cx);
24965 let event_type = reported_event.event_type();
24966
24967 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
24968 telemetry::event!(
24969 event_type,
24970 type = if auto_saved {"autosave"} else {"manual"},
24971 file_extension,
24972 vim_mode,
24973 copilot_enabled,
24974 copilot_enabled_for_language,
24975 edit_predictions_provider,
24976 is_via_ssh = project.is_via_remote_server(),
24977 );
24978 } else {
24979 telemetry::event!(
24980 event_type,
24981 file_extension,
24982 vim_mode,
24983 copilot_enabled,
24984 copilot_enabled_for_language,
24985 edit_predictions_provider,
24986 is_via_ssh = project.is_via_remote_server(),
24987 );
24988 };
24989 }
24990
24991 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
24992 /// with each line being an array of {text, highlight} objects.
24993 fn copy_highlight_json(
24994 &mut self,
24995 _: &CopyHighlightJson,
24996 _: &mut Window,
24997 cx: &mut Context<Self>,
24998 ) {
24999 #[derive(Serialize)]
25000 struct Chunk<'a> {
25001 text: String,
25002 highlight: Option<&'a str>,
25003 }
25004
25005 let snapshot = self.buffer.read(cx).snapshot(cx);
25006 let mut selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
25007 let max_point = snapshot.max_point();
25008
25009 let range = if self.selections.line_mode() {
25010 selection.start = Point::new(selection.start.row, 0);
25011 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
25012 selection.goal = SelectionGoal::None;
25013 selection.range()
25014 } else if selection.is_empty() {
25015 Point::new(0, 0)..max_point
25016 } else {
25017 selection.range()
25018 };
25019
25020 let chunks = snapshot.chunks(
25021 range,
25022 LanguageAwareStyling {
25023 tree_sitter: true,
25024 diagnostics: true,
25025 },
25026 );
25027 let mut lines = Vec::new();
25028 let mut line: VecDeque<Chunk> = VecDeque::new();
25029
25030 let Some(style) = self.style.as_ref() else {
25031 return;
25032 };
25033
25034 for chunk in chunks {
25035 let highlight = chunk
25036 .syntax_highlight_id
25037 .and_then(|id| style.syntax.get_capture_name(id));
25038
25039 let mut chunk_lines = chunk.text.split('\n').peekable();
25040 while let Some(text) = chunk_lines.next() {
25041 let mut merged_with_last_token = false;
25042 if let Some(last_token) = line.back_mut()
25043 && last_token.highlight == highlight
25044 {
25045 last_token.text.push_str(text);
25046 merged_with_last_token = true;
25047 }
25048
25049 if !merged_with_last_token {
25050 line.push_back(Chunk {
25051 text: text.into(),
25052 highlight,
25053 });
25054 }
25055
25056 if chunk_lines.peek().is_some() {
25057 if line.len() > 1 && line.front().unwrap().text.is_empty() {
25058 line.pop_front();
25059 }
25060 if line.len() > 1 && line.back().unwrap().text.is_empty() {
25061 line.pop_back();
25062 }
25063
25064 lines.push(mem::take(&mut line));
25065 }
25066 }
25067 }
25068
25069 if line.iter().any(|chunk| !chunk.text.is_empty()) {
25070 lines.push(line);
25071 }
25072
25073 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
25074 return;
25075 };
25076 cx.write_to_clipboard(ClipboardItem::new_string(lines));
25077 }
25078
25079 pub fn open_context_menu(
25080 &mut self,
25081 _: &OpenContextMenu,
25082 window: &mut Window,
25083 cx: &mut Context<Self>,
25084 ) {
25085 self.request_autoscroll(Autoscroll::newest(), cx);
25086 let position = self
25087 .selections
25088 .newest_display(&self.display_snapshot(cx))
25089 .start;
25090 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
25091 }
25092
25093 pub fn replay_insert_event(
25094 &mut self,
25095 text: &str,
25096 relative_utf16_range: Option<Range<isize>>,
25097 window: &mut Window,
25098 cx: &mut Context<Self>,
25099 ) {
25100 if !self.input_enabled {
25101 cx.emit(EditorEvent::InputIgnored { text: text.into() });
25102 return;
25103 }
25104 if let Some(relative_utf16_range) = relative_utf16_range {
25105 let selections = self
25106 .selections
25107 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
25108 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25109 let new_ranges = selections.into_iter().map(|range| {
25110 let start = MultiBufferOffsetUtf16(OffsetUtf16(
25111 range
25112 .head()
25113 .0
25114 .0
25115 .saturating_add_signed(relative_utf16_range.start),
25116 ));
25117 let end = MultiBufferOffsetUtf16(OffsetUtf16(
25118 range
25119 .head()
25120 .0
25121 .0
25122 .saturating_add_signed(relative_utf16_range.end),
25123 ));
25124 start..end
25125 });
25126 s.select_ranges(new_ranges);
25127 });
25128 }
25129
25130 self.handle_input(text, window, cx);
25131 }
25132
25133 pub fn is_focused(&self, window: &Window) -> bool {
25134 self.focus_handle.is_focused(window)
25135 }
25136
25137 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25138 cx.emit(EditorEvent::Focused);
25139
25140 if let Some(descendant) = self
25141 .last_focused_descendant
25142 .take()
25143 .and_then(|descendant| descendant.upgrade())
25144 {
25145 window.focus(&descendant, cx);
25146 } else {
25147 if let Some(blame) = self.blame.as_ref() {
25148 blame.update(cx, GitBlame::focus)
25149 }
25150
25151 self.blink_manager.update(cx, BlinkManager::enable);
25152 self.show_cursor_names(window, cx);
25153 self.buffer.update(cx, |buffer, cx| {
25154 buffer.finalize_last_transaction(cx);
25155 if self.leader_id.is_none() {
25156 buffer.set_active_selections(
25157 &self.selections.disjoint_anchors_arc(),
25158 self.selections.line_mode(),
25159 self.cursor_shape,
25160 cx,
25161 );
25162 }
25163 });
25164
25165 if let Some(position_map) = self.last_position_map.clone()
25166 && !self.mouse_cursor_hidden
25167 {
25168 EditorElement::mouse_moved(
25169 self,
25170 &MouseMoveEvent {
25171 position: window.mouse_position(),
25172 pressed_button: None,
25173 modifiers: window.modifiers(),
25174 },
25175 &position_map,
25176 None,
25177 window,
25178 cx,
25179 );
25180 }
25181 }
25182 }
25183
25184 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
25185 cx.emit(EditorEvent::FocusedIn)
25186 }
25187
25188 fn handle_focus_out(
25189 &mut self,
25190 event: FocusOutEvent,
25191 _window: &mut Window,
25192 cx: &mut Context<Self>,
25193 ) {
25194 if event.blurred != self.focus_handle {
25195 self.last_focused_descendant = Some(event.blurred);
25196 }
25197 self.selection_drag_state = SelectionDragState::None;
25198 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
25199 }
25200
25201 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25202 self.blink_manager.update(cx, BlinkManager::disable);
25203 self.buffer
25204 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
25205
25206 if let Some(blame) = self.blame.as_ref() {
25207 blame.update(cx, GitBlame::blur)
25208 }
25209 if !self.hover_state.focused(window, cx) {
25210 hide_hover(self, cx);
25211 }
25212 if !self
25213 .context_menu
25214 .borrow()
25215 .as_ref()
25216 .is_some_and(|context_menu| context_menu.focused(window, cx))
25217 {
25218 self.hide_context_menu(window, cx);
25219 }
25220 self.take_active_edit_prediction(true, cx);
25221 cx.emit(EditorEvent::Blurred);
25222 cx.notify();
25223 }
25224
25225 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25226 let mut pending: String = window
25227 .pending_input_keystrokes()
25228 .into_iter()
25229 .flatten()
25230 .filter_map(|keystroke| keystroke.key_char.clone())
25231 .collect();
25232
25233 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
25234 pending = "".to_string();
25235 }
25236
25237 let existing_pending = self
25238 .text_highlights(HighlightKey::PendingInput, cx)
25239 .map(|(_, ranges)| ranges.to_vec());
25240 if existing_pending.is_none() && pending.is_empty() {
25241 return;
25242 }
25243 let transaction =
25244 self.transact(window, cx, |this, window, cx| {
25245 let selections = this
25246 .selections
25247 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
25248 let edits = selections
25249 .iter()
25250 .map(|selection| (selection.end..selection.end, pending.clone()));
25251 this.edit(edits, cx);
25252 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25253 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
25254 sel.start + ix * pending.len()..sel.end + ix * pending.len()
25255 }));
25256 });
25257 if let Some(existing_ranges) = existing_pending {
25258 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
25259 this.edit(edits, cx);
25260 }
25261 });
25262
25263 let snapshot = self.snapshot(window, cx);
25264 let ranges = self
25265 .selections
25266 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
25267 .into_iter()
25268 .map(|selection| {
25269 snapshot.buffer_snapshot().anchor_after(selection.end)
25270 ..snapshot
25271 .buffer_snapshot()
25272 .anchor_before(selection.end + pending.len())
25273 })
25274 .collect();
25275
25276 if pending.is_empty() {
25277 self.clear_highlights(HighlightKey::PendingInput, cx);
25278 } else {
25279 self.highlight_text(
25280 HighlightKey::PendingInput,
25281 ranges,
25282 HighlightStyle {
25283 underline: Some(UnderlineStyle {
25284 thickness: px(1.),
25285 color: None,
25286 wavy: false,
25287 }),
25288 ..Default::default()
25289 },
25290 cx,
25291 );
25292 }
25293
25294 self.ime_transaction = self.ime_transaction.or(transaction);
25295 if let Some(transaction) = self.ime_transaction {
25296 self.buffer.update(cx, |buffer, cx| {
25297 buffer.group_until_transaction(transaction, cx);
25298 });
25299 }
25300
25301 if self
25302 .text_highlights(HighlightKey::PendingInput, cx)
25303 .is_none()
25304 {
25305 self.ime_transaction.take();
25306 }
25307 }
25308
25309 pub fn register_action_renderer(
25310 &mut self,
25311 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
25312 ) -> Subscription {
25313 let id = self.next_editor_action_id.post_inc();
25314 self.editor_actions
25315 .borrow_mut()
25316 .insert(id, Box::new(listener));
25317
25318 let editor_actions = self.editor_actions.clone();
25319 Subscription::new(move || {
25320 editor_actions.borrow_mut().remove(&id);
25321 })
25322 }
25323
25324 pub fn register_action<A: Action>(
25325 &mut self,
25326 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
25327 ) -> Subscription {
25328 let id = self.next_editor_action_id.post_inc();
25329 let listener = Arc::new(listener);
25330 self.editor_actions.borrow_mut().insert(
25331 id,
25332 Box::new(move |_, window, _| {
25333 let listener = listener.clone();
25334 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
25335 let action = action.downcast_ref().unwrap();
25336 if phase == DispatchPhase::Bubble {
25337 listener(action, window, cx)
25338 }
25339 })
25340 }),
25341 );
25342
25343 let editor_actions = self.editor_actions.clone();
25344 Subscription::new(move || {
25345 editor_actions.borrow_mut().remove(&id);
25346 })
25347 }
25348
25349 pub fn file_header_size(&self) -> u32 {
25350 FILE_HEADER_HEIGHT
25351 }
25352
25353 pub fn restore(
25354 &mut self,
25355 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
25356 window: &mut Window,
25357 cx: &mut Context<Self>,
25358 ) {
25359 self.buffer().update(cx, |multi_buffer, cx| {
25360 for (buffer_id, changes) in revert_changes {
25361 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
25362 buffer.update(cx, |buffer, cx| {
25363 buffer.edit(
25364 changes
25365 .into_iter()
25366 .map(|(range, text)| (range, text.to_string())),
25367 None,
25368 cx,
25369 );
25370 });
25371 }
25372 }
25373 });
25374 let selections = self
25375 .selections
25376 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
25377 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25378 s.select(selections);
25379 });
25380 }
25381
25382 pub fn to_pixel_point(
25383 &mut self,
25384 source: Anchor,
25385 editor_snapshot: &EditorSnapshot,
25386 window: &mut Window,
25387 cx: &mut App,
25388 ) -> Option<gpui::Point<Pixels>> {
25389 let source_point = source.to_display_point(editor_snapshot);
25390 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
25391 }
25392
25393 pub fn display_to_pixel_point(
25394 &mut self,
25395 source: DisplayPoint,
25396 editor_snapshot: &EditorSnapshot,
25397 window: &mut Window,
25398 cx: &mut App,
25399 ) -> Option<gpui::Point<Pixels>> {
25400 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
25401 let text_layout_details = self.text_layout_details(window, cx);
25402 let scroll_top = text_layout_details
25403 .scroll_anchor
25404 .scroll_position(editor_snapshot)
25405 .y;
25406
25407 if source.row().as_f64() < scroll_top.floor() {
25408 return None;
25409 }
25410 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
25411 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
25412 Some(gpui::Point::new(source_x, source_y))
25413 }
25414
25415 pub fn has_visible_completions_menu(&self) -> bool {
25416 !self.edit_prediction_preview_is_active()
25417 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
25418 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
25419 })
25420 }
25421
25422 pub fn register_addon<T: Addon>(&mut self, instance: T) {
25423 if self.mode.is_minimap() {
25424 return;
25425 }
25426 self.addons
25427 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
25428 }
25429
25430 pub fn unregister_addon<T: Addon>(&mut self) {
25431 self.addons.remove(&std::any::TypeId::of::<T>());
25432 }
25433
25434 pub fn addon<T: Addon>(&self) -> Option<&T> {
25435 let type_id = std::any::TypeId::of::<T>();
25436 self.addons
25437 .get(&type_id)
25438 .and_then(|item| item.to_any().downcast_ref::<T>())
25439 }
25440
25441 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
25442 let type_id = std::any::TypeId::of::<T>();
25443 self.addons
25444 .get_mut(&type_id)
25445 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
25446 }
25447
25448 fn character_dimensions(&self, window: &mut Window, cx: &mut App) -> CharacterDimensions {
25449 let text_layout_details = self.text_layout_details(window, cx);
25450 let style = &text_layout_details.editor_style;
25451 let font_id = window.text_system().resolve_font(&style.text.font());
25452 let font_size = style.text.font_size.to_pixels(window.rem_size());
25453 let line_height = style.text.line_height_in_pixels(window.rem_size());
25454 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
25455 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
25456
25457 CharacterDimensions {
25458 em_width,
25459 em_advance,
25460 line_height,
25461 }
25462 }
25463
25464 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
25465 self.load_diff_task.clone()
25466 }
25467
25468 fn read_metadata_from_db(
25469 &mut self,
25470 item_id: u64,
25471 workspace_id: WorkspaceId,
25472 window: &mut Window,
25473 cx: &mut Context<Editor>,
25474 ) {
25475 if self.buffer_kind(cx) == ItemBufferKind::Singleton
25476 && !self.mode.is_minimap()
25477 && WorkspaceSettings::get(None, cx).restore_on_startup
25478 != RestoreOnStartupBehavior::EmptyTab
25479 {
25480 let buffer_snapshot = OnceCell::new();
25481
25482 // Get file path for path-based fold lookup
25483 let file_path: Option<Arc<Path>> =
25484 self.buffer().read(cx).as_singleton().and_then(|buffer| {
25485 project::File::from_dyn(buffer.read(cx).file())
25486 .map(|file| Arc::from(file.abs_path(cx)))
25487 });
25488
25489 // Try file_folds (path-based) first, fallback to editor_folds (migration)
25490 let db = EditorDb::global(cx);
25491 let (folds, needs_migration) = if let Some(ref path) = file_path {
25492 if let Some(folds) = db.get_file_folds(workspace_id, path).log_err()
25493 && !folds.is_empty()
25494 {
25495 (Some(folds), false)
25496 } else if let Some(folds) = db.get_editor_folds(item_id, workspace_id).log_err()
25497 && !folds.is_empty()
25498 {
25499 // Found old editor_folds data, will migrate to file_folds
25500 (Some(folds), true)
25501 } else {
25502 (None, false)
25503 }
25504 } else {
25505 // No file path, try editor_folds as fallback
25506 let folds = db.get_editor_folds(item_id, workspace_id).log_err();
25507 (folds.filter(|f| !f.is_empty()), false)
25508 };
25509
25510 if let Some(folds) = folds {
25511 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25512 let snapshot_len = snapshot.len().0;
25513
25514 // Helper: search for fingerprint in buffer, return offset if found
25515 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
25516 // Ensure we start at a character boundary (defensive)
25517 let search_start = snapshot
25518 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
25519 .0;
25520 let search_end = snapshot_len.saturating_sub(fingerprint.len());
25521
25522 let mut byte_offset = search_start;
25523 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
25524 if byte_offset > search_end {
25525 break;
25526 }
25527 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
25528 return Some(byte_offset);
25529 }
25530 byte_offset += ch.len_utf8();
25531 }
25532 None
25533 };
25534
25535 // Track search position to handle duplicate fingerprints correctly.
25536 // Folds are stored in document order, so we advance after each match.
25537 let mut search_start = 0usize;
25538
25539 // Collect db_folds for migration (only folds with valid fingerprints)
25540 let mut db_folds_for_migration: Vec<(usize, usize, String, String)> = Vec::new();
25541
25542 let valid_folds: Vec<_> = folds
25543 .into_iter()
25544 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
25545 // Skip folds without fingerprints (old data before migration)
25546 let sfp = start_fp?;
25547 let efp = end_fp?;
25548 let efp_len = efp.len();
25549
25550 // Fast path: check if fingerprints match at stored offsets
25551 // Note: end_fp is content BEFORE fold end, so check at (stored_end - efp_len)
25552 let start_matches = stored_start < snapshot_len
25553 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
25554 let efp_check_pos = stored_end.saturating_sub(efp_len);
25555 let end_matches = efp_check_pos >= stored_start
25556 && stored_end <= snapshot_len
25557 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
25558
25559 let (new_start, new_end) = if start_matches && end_matches {
25560 // Offsets unchanged, use stored values
25561 (stored_start, stored_end)
25562 } else if sfp == efp {
25563 // Short fold: identical fingerprints can only match once per search
25564 // Use stored fold length to compute new_end
25565 let new_start = find_fingerprint(&sfp, search_start)?;
25566 let fold_len = stored_end - stored_start;
25567 let new_end = new_start + fold_len;
25568 (new_start, new_end)
25569 } else {
25570 // Slow path: search for fingerprints in buffer
25571 let new_start = find_fingerprint(&sfp, search_start)?;
25572 // Search for end_fp after start, then add efp_len to get actual fold end
25573 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
25574 let new_end = efp_pos + efp_len;
25575 (new_start, new_end)
25576 };
25577
25578 // Advance search position for next fold
25579 search_start = new_end;
25580
25581 // Validate fold makes sense (end must be after start)
25582 if new_end <= new_start {
25583 return None;
25584 }
25585
25586 // Collect for migration if needed
25587 if needs_migration {
25588 db_folds_for_migration.push((new_start, new_end, sfp, efp));
25589 }
25590
25591 Some(
25592 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
25593 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
25594 )
25595 })
25596 .collect();
25597
25598 if !valid_folds.is_empty() {
25599 self.fold_ranges(valid_folds, false, window, cx);
25600
25601 // Migrate from editor_folds to file_folds if we loaded from old table
25602 if needs_migration {
25603 if let Some(ref path) = file_path {
25604 let path = path.clone();
25605 let db = EditorDb::global(cx);
25606 cx.spawn(async move |_, _| {
25607 db.save_file_folds(workspace_id, path, db_folds_for_migration)
25608 .await
25609 .log_err();
25610 })
25611 .detach();
25612 }
25613 }
25614 }
25615 }
25616
25617 if let Some(selections) = db.get_editor_selections(item_id, workspace_id).log_err()
25618 && !selections.is_empty()
25619 {
25620 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25621 // skip adding the initial selection to selection history
25622 self.selection_history.mode = SelectionHistoryMode::Skipping;
25623 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25624 s.select_ranges(selections.into_iter().map(|(start, end)| {
25625 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
25626 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
25627 }));
25628 });
25629 self.selection_history.mode = SelectionHistoryMode::Normal;
25630 };
25631 }
25632
25633 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
25634 }
25635
25636 /// Load folds from the file_folds database table by file path.
25637 /// Used when manually opening a file that was previously closed.
25638 fn load_folds_from_db(
25639 &mut self,
25640 workspace_id: WorkspaceId,
25641 file_path: PathBuf,
25642 window: &mut Window,
25643 cx: &mut Context<Editor>,
25644 ) {
25645 if self.mode.is_minimap()
25646 || WorkspaceSettings::get(None, cx).restore_on_startup
25647 == RestoreOnStartupBehavior::EmptyTab
25648 {
25649 return;
25650 }
25651
25652 let Some(folds) = EditorDb::global(cx)
25653 .get_file_folds(workspace_id, &file_path)
25654 .log_err()
25655 else {
25656 return;
25657 };
25658 if folds.is_empty() {
25659 return;
25660 }
25661
25662 let snapshot = self.buffer.read(cx).snapshot(cx);
25663 let snapshot_len = snapshot.len().0;
25664
25665 // Helper: search for fingerprint in buffer, return offset if found
25666 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
25667 let search_start = snapshot
25668 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
25669 .0;
25670 let search_end = snapshot_len.saturating_sub(fingerprint.len());
25671
25672 let mut byte_offset = search_start;
25673 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
25674 if byte_offset > search_end {
25675 break;
25676 }
25677 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
25678 return Some(byte_offset);
25679 }
25680 byte_offset += ch.len_utf8();
25681 }
25682 None
25683 };
25684
25685 let mut search_start = 0usize;
25686
25687 let valid_folds: Vec<_> = folds
25688 .into_iter()
25689 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
25690 let sfp = start_fp?;
25691 let efp = end_fp?;
25692 let efp_len = efp.len();
25693
25694 let start_matches = stored_start < snapshot_len
25695 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
25696 let efp_check_pos = stored_end.saturating_sub(efp_len);
25697 let end_matches = efp_check_pos >= stored_start
25698 && stored_end <= snapshot_len
25699 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
25700
25701 let (new_start, new_end) = if start_matches && end_matches {
25702 (stored_start, stored_end)
25703 } else if sfp == efp {
25704 let new_start = find_fingerprint(&sfp, search_start)?;
25705 let fold_len = stored_end - stored_start;
25706 let new_end = new_start + fold_len;
25707 (new_start, new_end)
25708 } else {
25709 let new_start = find_fingerprint(&sfp, search_start)?;
25710 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
25711 let new_end = efp_pos + efp_len;
25712 (new_start, new_end)
25713 };
25714
25715 search_start = new_end;
25716
25717 if new_end <= new_start {
25718 return None;
25719 }
25720
25721 Some(
25722 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
25723 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
25724 )
25725 })
25726 .collect();
25727
25728 if !valid_folds.is_empty() {
25729 self.fold_ranges(valid_folds, false, window, cx);
25730 }
25731 }
25732
25733 fn lsp_data_enabled(&self) -> bool {
25734 self.enable_lsp_data && self.mode().is_full()
25735 }
25736
25737 fn update_lsp_data(
25738 &mut self,
25739 for_buffer: Option<BufferId>,
25740 window: &mut Window,
25741 cx: &mut Context<'_, Self>,
25742 ) {
25743 if !self.lsp_data_enabled() {
25744 return;
25745 }
25746
25747 if let Some(buffer_id) = for_buffer {
25748 self.pull_diagnostics(buffer_id, window, cx);
25749 }
25750 self.refresh_semantic_tokens(for_buffer, None, cx);
25751 self.refresh_document_colors(for_buffer, window, cx);
25752 self.refresh_folding_ranges(for_buffer, window, cx);
25753 self.refresh_document_symbols(for_buffer, cx);
25754 }
25755
25756 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
25757 if !self.lsp_data_enabled() {
25758 return;
25759 }
25760 let visible_buffers: Vec<_> = self
25761 .visible_buffers(cx)
25762 .into_iter()
25763 .filter(|buffer| self.is_lsp_relevant(buffer.read(cx).file(), cx))
25764 .collect();
25765 for visible_buffer in visible_buffers {
25766 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
25767 }
25768 }
25769
25770 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
25771 if !self.lsp_data_enabled() {
25772 return;
25773 }
25774
25775 if !self.registered_buffers.contains_key(&buffer_id)
25776 && let Some(project) = self.project.as_ref()
25777 {
25778 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
25779 project.update(cx, |project, cx| {
25780 self.registered_buffers.insert(
25781 buffer_id,
25782 project.register_buffer_with_language_servers(&buffer, cx),
25783 );
25784 });
25785 } else {
25786 self.registered_buffers.remove(&buffer_id);
25787 }
25788 }
25789 }
25790
25791 fn create_style(&self, cx: &App) -> EditorStyle {
25792 let settings = ThemeSettings::get_global(cx);
25793
25794 let mut text_style = match self.mode {
25795 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
25796 color: cx.theme().colors().editor_foreground,
25797 font_family: settings.ui_font.family.clone(),
25798 font_features: settings.ui_font.features.clone(),
25799 font_fallbacks: settings.ui_font.fallbacks.clone(),
25800 font_size: rems(0.875).into(),
25801 font_weight: settings.ui_font.weight,
25802 line_height: relative(settings.buffer_line_height.value()),
25803 ..Default::default()
25804 },
25805 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
25806 color: cx.theme().colors().editor_foreground,
25807 font_family: settings.buffer_font.family.clone(),
25808 font_features: settings.buffer_font.features.clone(),
25809 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25810 font_size: settings.buffer_font_size(cx).into(),
25811 font_weight: settings.buffer_font.weight,
25812 line_height: relative(settings.buffer_line_height.value()),
25813 ..Default::default()
25814 },
25815 };
25816 if let Some(text_style_refinement) = &self.text_style_refinement {
25817 text_style.refine(text_style_refinement)
25818 }
25819
25820 let background = match self.mode {
25821 EditorMode::SingleLine => cx.theme().system().transparent,
25822 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
25823 EditorMode::Full { .. } => cx.theme().colors().editor_background,
25824 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
25825 };
25826
25827 EditorStyle {
25828 background,
25829 border: cx.theme().colors().border,
25830 local_player: cx.theme().players().local(),
25831 text: text_style,
25832 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
25833 syntax: cx.theme().syntax().clone(),
25834 status: cx.theme().status().clone(),
25835 inlay_hints_style: make_inlay_hints_style(cx),
25836 edit_prediction_styles: make_suggestion_styles(cx),
25837 unnecessary_code_fade: settings.unnecessary_code_fade,
25838 show_underlines: self.diagnostics_enabled(),
25839 }
25840 }
25841
25842 fn breadcrumbs_inner(&self, cx: &App) -> Option<Vec<HighlightedText>> {
25843 let multibuffer = self.buffer().read(cx);
25844 let is_singleton = multibuffer.is_singleton();
25845 let (buffer_id, symbols) = self.outline_symbols_at_cursor.as_ref()?;
25846 let buffer = multibuffer.buffer(*buffer_id)?;
25847
25848 let buffer = buffer.read(cx);
25849 // In a multi-buffer layout, we don't want to include the filename in the breadcrumbs
25850 let mut breadcrumbs = if is_singleton {
25851 let text = self.breadcrumb_header.clone().unwrap_or_else(|| {
25852 buffer
25853 .snapshot()
25854 .resolve_file_path(
25855 self.project
25856 .as_ref()
25857 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
25858 .unwrap_or_default(),
25859 cx,
25860 )
25861 .unwrap_or_else(|| {
25862 if multibuffer.is_singleton() {
25863 multibuffer.title(cx).to_string()
25864 } else {
25865 "untitled".to_string()
25866 }
25867 })
25868 });
25869 vec![HighlightedText {
25870 text: text.into(),
25871 highlights: vec![],
25872 }]
25873 } else {
25874 vec![]
25875 };
25876
25877 breadcrumbs.extend(symbols.iter().map(|symbol| HighlightedText {
25878 text: symbol.text.clone().into(),
25879 highlights: symbol.highlight_ranges.clone(),
25880 }));
25881 Some(breadcrumbs)
25882 }
25883
25884 fn disable_lsp_data(&mut self) {
25885 self.enable_lsp_data = false;
25886 }
25887
25888 fn disable_runnables(&mut self) {
25889 self.enable_runnables = false;
25890 }
25891
25892 fn update_data_on_scroll(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) {
25893 self.register_visible_buffers(cx);
25894 self.colorize_brackets(false, cx);
25895 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
25896 if !self.buffer().read(cx).is_singleton() {
25897 self.update_lsp_data(None, window, cx);
25898 self.refresh_runnables(None, window, cx);
25899 }
25900 }
25901}
25902
25903fn edit_for_markdown_paste<'a>(
25904 buffer: &MultiBufferSnapshot,
25905 range: Range<MultiBufferOffset>,
25906 to_insert: &'a str,
25907 url: Option<url::Url>,
25908) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
25909 if url.is_none() {
25910 return (range, Cow::Borrowed(to_insert));
25911 };
25912
25913 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
25914
25915 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
25916 Cow::Borrowed(to_insert)
25917 } else {
25918 Cow::Owned(format!("[{old_text}]({to_insert})"))
25919 };
25920 (range, new_text)
25921}
25922
25923fn process_completion_for_edit(
25924 completion: &Completion,
25925 intent: CompletionIntent,
25926 buffer: &Entity<Buffer>,
25927 cursor_position: &text::Anchor,
25928 cx: &mut Context<Editor>,
25929) -> CompletionEdit {
25930 let buffer = buffer.read(cx);
25931 let buffer_snapshot = buffer.snapshot();
25932 let (snippet, new_text) = if completion.is_snippet() {
25933 let mut snippet_source = completion.new_text.clone();
25934 // Workaround for typescript language server issues so that methods don't expand within
25935 // strings and functions with type expressions. The previous point is used because the query
25936 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
25937 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
25938 let previous_point = if previous_point.column > 0 {
25939 cursor_position.to_previous_offset(&buffer_snapshot)
25940 } else {
25941 cursor_position.to_offset(&buffer_snapshot)
25942 };
25943 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
25944 && scope.prefers_label_for_snippet_in_completion()
25945 && let Some(label) = completion.label()
25946 && matches!(
25947 completion.kind(),
25948 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
25949 )
25950 {
25951 snippet_source = label;
25952 }
25953 match Snippet::parse(&snippet_source).log_err() {
25954 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
25955 None => (None, completion.new_text.clone()),
25956 }
25957 } else {
25958 (None, completion.new_text.clone())
25959 };
25960
25961 let mut range_to_replace = {
25962 let replace_range = &completion.replace_range;
25963 if let CompletionSource::Lsp {
25964 insert_range: Some(insert_range),
25965 ..
25966 } = &completion.source
25967 {
25968 debug_assert_eq!(
25969 insert_range.start, replace_range.start,
25970 "insert_range and replace_range should start at the same position"
25971 );
25972 debug_assert!(
25973 insert_range
25974 .start
25975 .cmp(cursor_position, &buffer_snapshot)
25976 .is_le(),
25977 "insert_range should start before or at cursor position"
25978 );
25979 debug_assert!(
25980 replace_range
25981 .start
25982 .cmp(cursor_position, &buffer_snapshot)
25983 .is_le(),
25984 "replace_range should start before or at cursor position"
25985 );
25986
25987 let should_replace = match intent {
25988 CompletionIntent::CompleteWithInsert => false,
25989 CompletionIntent::CompleteWithReplace => true,
25990 CompletionIntent::Complete | CompletionIntent::Compose => {
25991 let insert_mode = LanguageSettings::for_buffer(&buffer, cx)
25992 .completions
25993 .lsp_insert_mode;
25994 match insert_mode {
25995 LspInsertMode::Insert => false,
25996 LspInsertMode::Replace => true,
25997 LspInsertMode::ReplaceSubsequence => {
25998 let mut text_to_replace = buffer.chars_for_range(
25999 buffer.anchor_before(replace_range.start)
26000 ..buffer.anchor_after(replace_range.end),
26001 );
26002 let mut current_needle = text_to_replace.next();
26003 for haystack_ch in completion.label.text.chars() {
26004 if let Some(needle_ch) = current_needle
26005 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
26006 {
26007 current_needle = text_to_replace.next();
26008 }
26009 }
26010 current_needle.is_none()
26011 }
26012 LspInsertMode::ReplaceSuffix => {
26013 if replace_range
26014 .end
26015 .cmp(cursor_position, &buffer_snapshot)
26016 .is_gt()
26017 {
26018 let range_after_cursor = *cursor_position..replace_range.end;
26019 let text_after_cursor = buffer
26020 .text_for_range(
26021 buffer.anchor_before(range_after_cursor.start)
26022 ..buffer.anchor_after(range_after_cursor.end),
26023 )
26024 .collect::<String>()
26025 .to_ascii_lowercase();
26026 completion
26027 .label
26028 .text
26029 .to_ascii_lowercase()
26030 .ends_with(&text_after_cursor)
26031 } else {
26032 true
26033 }
26034 }
26035 }
26036 }
26037 };
26038
26039 if should_replace {
26040 replace_range.clone()
26041 } else {
26042 insert_range.clone()
26043 }
26044 } else {
26045 replace_range.clone()
26046 }
26047 };
26048
26049 if range_to_replace
26050 .end
26051 .cmp(cursor_position, &buffer_snapshot)
26052 .is_lt()
26053 {
26054 range_to_replace.end = *cursor_position;
26055 }
26056
26057 CompletionEdit {
26058 new_text,
26059 replace_range: range_to_replace,
26060 snippet,
26061 }
26062}
26063
26064struct CompletionEdit {
26065 new_text: String,
26066 replace_range: Range<text::Anchor>,
26067 snippet: Option<Snippet>,
26068}
26069
26070fn comment_delimiter_for_newline(
26071 start_point: &Point,
26072 buffer: &MultiBufferSnapshot,
26073 language: &LanguageScope,
26074) -> Option<Arc<str>> {
26075 let delimiters = language.line_comment_prefixes();
26076 let max_len_of_delimiter = delimiters.iter().map(|delimiter| delimiter.len()).max()?;
26077 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
26078
26079 let num_of_whitespaces = snapshot
26080 .chars_for_range(range.clone())
26081 .take_while(|c| c.is_whitespace())
26082 .count();
26083 let comment_candidate = snapshot
26084 .chars_for_range(range.clone())
26085 .skip(num_of_whitespaces)
26086 .take(max_len_of_delimiter + 2)
26087 .collect::<String>();
26088 let (delimiter, trimmed_len, is_repl) = delimiters
26089 .iter()
26090 .filter_map(|delimiter| {
26091 let prefix = delimiter.trim_end();
26092 if comment_candidate.starts_with(prefix) {
26093 let is_repl = if let Some(stripped_comment) = comment_candidate.strip_prefix(prefix)
26094 {
26095 stripped_comment.starts_with(" %%")
26096 } else {
26097 false
26098 };
26099 Some((delimiter, prefix.len(), is_repl))
26100 } else {
26101 None
26102 }
26103 })
26104 .max_by_key(|(_, len, _)| *len)?;
26105
26106 if let Some(BlockCommentConfig {
26107 start: block_start, ..
26108 }) = language.block_comment()
26109 {
26110 let block_start_trimmed = block_start.trim_end();
26111 if block_start_trimmed.starts_with(delimiter.trim_end()) {
26112 let line_content = snapshot
26113 .chars_for_range(range.clone())
26114 .skip(num_of_whitespaces)
26115 .take(block_start_trimmed.len())
26116 .collect::<String>();
26117
26118 if line_content.starts_with(block_start_trimmed) {
26119 return None;
26120 }
26121 }
26122 }
26123
26124 let cursor_is_placed_after_comment_marker =
26125 num_of_whitespaces + trimmed_len <= start_point.column as usize;
26126 if cursor_is_placed_after_comment_marker {
26127 if !is_repl {
26128 return Some(delimiter.clone());
26129 }
26130
26131 let line_content_after_cursor: String = snapshot
26132 .chars_for_range(range)
26133 .skip(start_point.column as usize)
26134 .collect();
26135
26136 if line_content_after_cursor.trim().is_empty() {
26137 return None;
26138 } else {
26139 return Some(delimiter.clone());
26140 }
26141 } else {
26142 None
26143 }
26144}
26145
26146fn documentation_delimiter_for_newline(
26147 start_point: &Point,
26148 buffer: &MultiBufferSnapshot,
26149 language: &LanguageScope,
26150 newline_config: &mut NewlineConfig,
26151) -> Option<Arc<str>> {
26152 let BlockCommentConfig {
26153 start: start_tag,
26154 end: end_tag,
26155 prefix: delimiter,
26156 tab_size: len,
26157 } = language.documentation_comment()?;
26158 let is_within_block_comment = buffer
26159 .language_scope_at(*start_point)
26160 .is_some_and(|scope| scope.override_name() == Some("comment"));
26161 if !is_within_block_comment {
26162 return None;
26163 }
26164
26165 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
26166
26167 let num_of_whitespaces = snapshot
26168 .chars_for_range(range.clone())
26169 .take_while(|c| c.is_whitespace())
26170 .count();
26171
26172 // 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.
26173 let column = start_point.column;
26174 let cursor_is_after_start_tag = {
26175 let start_tag_len = start_tag.len();
26176 let start_tag_line = snapshot
26177 .chars_for_range(range.clone())
26178 .skip(num_of_whitespaces)
26179 .take(start_tag_len)
26180 .collect::<String>();
26181 if start_tag_line.starts_with(start_tag.as_ref()) {
26182 num_of_whitespaces + start_tag_len <= column as usize
26183 } else {
26184 false
26185 }
26186 };
26187
26188 let cursor_is_after_delimiter = {
26189 let delimiter_trim = delimiter.trim_end();
26190 let delimiter_line = snapshot
26191 .chars_for_range(range.clone())
26192 .skip(num_of_whitespaces)
26193 .take(delimiter_trim.len())
26194 .collect::<String>();
26195 if delimiter_line.starts_with(delimiter_trim) {
26196 num_of_whitespaces + delimiter_trim.len() <= column as usize
26197 } else {
26198 false
26199 }
26200 };
26201
26202 let mut needs_extra_line = false;
26203 let mut extra_line_additional_indent = IndentSize::spaces(0);
26204
26205 let cursor_is_before_end_tag_if_exists = {
26206 let mut char_position = 0u32;
26207 let mut end_tag_offset = None;
26208
26209 'outer: for chunk in snapshot.text_for_range(range) {
26210 if let Some(byte_pos) = chunk.find(&**end_tag) {
26211 let chars_before_match = chunk[..byte_pos].chars().count() as u32;
26212 end_tag_offset = Some(char_position + chars_before_match);
26213 break 'outer;
26214 }
26215 char_position += chunk.chars().count() as u32;
26216 }
26217
26218 if let Some(end_tag_offset) = end_tag_offset {
26219 let cursor_is_before_end_tag = column <= end_tag_offset;
26220 if cursor_is_after_start_tag {
26221 if cursor_is_before_end_tag {
26222 needs_extra_line = true;
26223 }
26224 let cursor_is_at_start_of_end_tag = column == end_tag_offset;
26225 if cursor_is_at_start_of_end_tag {
26226 extra_line_additional_indent.len = *len;
26227 }
26228 }
26229 cursor_is_before_end_tag
26230 } else {
26231 true
26232 }
26233 };
26234
26235 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
26236 && cursor_is_before_end_tag_if_exists
26237 {
26238 let additional_indent = if cursor_is_after_start_tag {
26239 IndentSize::spaces(*len)
26240 } else {
26241 IndentSize::spaces(0)
26242 };
26243
26244 *newline_config = NewlineConfig::Newline {
26245 additional_indent,
26246 extra_line_additional_indent: if needs_extra_line {
26247 Some(extra_line_additional_indent)
26248 } else {
26249 None
26250 },
26251 prevent_auto_indent: true,
26252 };
26253 Some(delimiter.clone())
26254 } else {
26255 None
26256 }
26257}
26258
26259const ORDERED_LIST_MAX_MARKER_LEN: usize = 16;
26260
26261fn list_delimiter_for_newline(
26262 start_point: &Point,
26263 buffer: &MultiBufferSnapshot,
26264 language: &LanguageScope,
26265 newline_config: &mut NewlineConfig,
26266) -> Option<Arc<str>> {
26267 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
26268
26269 let num_of_whitespaces = snapshot
26270 .chars_for_range(range.clone())
26271 .take_while(|c| c.is_whitespace())
26272 .count();
26273
26274 let task_list_entries: Vec<_> = language
26275 .task_list()
26276 .into_iter()
26277 .flat_map(|config| {
26278 config
26279 .prefixes
26280 .iter()
26281 .map(|prefix| (prefix.as_ref(), config.continuation.as_ref()))
26282 })
26283 .collect();
26284 let unordered_list_entries: Vec<_> = language
26285 .unordered_list()
26286 .iter()
26287 .map(|marker| (marker.as_ref(), marker.as_ref()))
26288 .collect();
26289
26290 let all_entries: Vec<_> = task_list_entries
26291 .into_iter()
26292 .chain(unordered_list_entries)
26293 .collect();
26294
26295 if let Some(max_prefix_len) = all_entries.iter().map(|(p, _)| p.len()).max() {
26296 let candidate: String = snapshot
26297 .chars_for_range(range.clone())
26298 .skip(num_of_whitespaces)
26299 .take(max_prefix_len)
26300 .collect();
26301
26302 if let Some((prefix, continuation)) = all_entries
26303 .iter()
26304 .filter(|(prefix, _)| candidate.starts_with(*prefix))
26305 .max_by_key(|(prefix, _)| prefix.len())
26306 {
26307 let end_of_prefix = num_of_whitespaces + prefix.len();
26308 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
26309 let has_content_after_marker = snapshot
26310 .chars_for_range(range)
26311 .skip(end_of_prefix)
26312 .any(|c| !c.is_whitespace());
26313
26314 if has_content_after_marker && cursor_is_after_prefix {
26315 return Some((*continuation).into());
26316 }
26317
26318 if start_point.column as usize == end_of_prefix {
26319 if num_of_whitespaces == 0 {
26320 *newline_config = NewlineConfig::ClearCurrentLine;
26321 } else {
26322 *newline_config = NewlineConfig::UnindentCurrentLine {
26323 continuation: (*continuation).into(),
26324 };
26325 }
26326 }
26327
26328 return None;
26329 }
26330 }
26331
26332 let candidate: String = snapshot
26333 .chars_for_range(range.clone())
26334 .skip(num_of_whitespaces)
26335 .take(ORDERED_LIST_MAX_MARKER_LEN)
26336 .collect();
26337
26338 for ordered_config in language.ordered_list() {
26339 let regex = match Regex::new(&ordered_config.pattern) {
26340 Ok(r) => r,
26341 Err(_) => continue,
26342 };
26343
26344 if let Some(captures) = regex.captures(&candidate) {
26345 let full_match = captures.get(0)?;
26346 let marker_len = full_match.len();
26347 let end_of_prefix = num_of_whitespaces + marker_len;
26348 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
26349
26350 let has_content_after_marker = snapshot
26351 .chars_for_range(range)
26352 .skip(end_of_prefix)
26353 .any(|c| !c.is_whitespace());
26354
26355 if has_content_after_marker && cursor_is_after_prefix {
26356 let number: u32 = captures.get(1)?.as_str().parse().ok()?;
26357 let continuation = ordered_config
26358 .format
26359 .replace("{1}", &(number + 1).to_string());
26360 return Some(continuation.into());
26361 }
26362
26363 if start_point.column as usize == end_of_prefix {
26364 let continuation = ordered_config.format.replace("{1}", "1");
26365 if num_of_whitespaces == 0 {
26366 *newline_config = NewlineConfig::ClearCurrentLine;
26367 } else {
26368 *newline_config = NewlineConfig::UnindentCurrentLine {
26369 continuation: continuation.into(),
26370 };
26371 }
26372 }
26373
26374 return None;
26375 }
26376 }
26377
26378 None
26379}
26380
26381fn is_list_prefix_row(
26382 row: MultiBufferRow,
26383 buffer: &MultiBufferSnapshot,
26384 language: &LanguageScope,
26385) -> bool {
26386 let Some((snapshot, range)) = buffer.buffer_line_for_row(row) else {
26387 return false;
26388 };
26389
26390 let num_of_whitespaces = snapshot
26391 .chars_for_range(range.clone())
26392 .take_while(|c| c.is_whitespace())
26393 .count();
26394
26395 let task_list_prefixes: Vec<_> = language
26396 .task_list()
26397 .into_iter()
26398 .flat_map(|config| {
26399 config
26400 .prefixes
26401 .iter()
26402 .map(|p| p.as_ref())
26403 .collect::<Vec<_>>()
26404 })
26405 .collect();
26406 let unordered_list_markers: Vec<_> = language
26407 .unordered_list()
26408 .iter()
26409 .map(|marker| marker.as_ref())
26410 .collect();
26411 let all_prefixes: Vec<_> = task_list_prefixes
26412 .into_iter()
26413 .chain(unordered_list_markers)
26414 .collect();
26415 if let Some(max_prefix_len) = all_prefixes.iter().map(|p| p.len()).max() {
26416 let candidate: String = snapshot
26417 .chars_for_range(range.clone())
26418 .skip(num_of_whitespaces)
26419 .take(max_prefix_len)
26420 .collect();
26421 if all_prefixes
26422 .iter()
26423 .any(|prefix| candidate.starts_with(*prefix))
26424 {
26425 return true;
26426 }
26427 }
26428
26429 let ordered_list_candidate: String = snapshot
26430 .chars_for_range(range)
26431 .skip(num_of_whitespaces)
26432 .take(ORDERED_LIST_MAX_MARKER_LEN)
26433 .collect();
26434 for ordered_config in language.ordered_list() {
26435 let regex = match Regex::new(&ordered_config.pattern) {
26436 Ok(r) => r,
26437 Err(_) => continue,
26438 };
26439 if let Some(captures) = regex.captures(&ordered_list_candidate) {
26440 return captures.get(0).is_some();
26441 }
26442 }
26443
26444 false
26445}
26446
26447#[derive(Debug)]
26448enum NewlineConfig {
26449 /// Insert newline with optional additional indent and optional extra blank line
26450 Newline {
26451 additional_indent: IndentSize,
26452 extra_line_additional_indent: Option<IndentSize>,
26453 prevent_auto_indent: bool,
26454 },
26455 /// Clear the current line
26456 ClearCurrentLine,
26457 /// Unindent the current line and add continuation
26458 UnindentCurrentLine { continuation: Arc<str> },
26459}
26460
26461impl NewlineConfig {
26462 fn has_extra_line(&self) -> bool {
26463 matches!(
26464 self,
26465 Self::Newline {
26466 extra_line_additional_indent: Some(_),
26467 ..
26468 }
26469 )
26470 }
26471
26472 fn insert_extra_newline_brackets(
26473 buffer: &MultiBufferSnapshot,
26474 range: Range<MultiBufferOffset>,
26475 language: &language::LanguageScope,
26476 ) -> bool {
26477 let leading_whitespace_len = buffer
26478 .reversed_chars_at(range.start)
26479 .take_while(|c| c.is_whitespace() && *c != '\n')
26480 .map(|c| c.len_utf8())
26481 .sum::<usize>();
26482 let trailing_whitespace_len = buffer
26483 .chars_at(range.end)
26484 .take_while(|c| c.is_whitespace() && *c != '\n')
26485 .map(|c| c.len_utf8())
26486 .sum::<usize>();
26487 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
26488
26489 language.brackets().any(|(pair, enabled)| {
26490 let pair_start = pair.start.trim_end();
26491 let pair_end = pair.end.trim_start();
26492
26493 enabled
26494 && pair.newline
26495 && buffer.contains_str_at(range.end, pair_end)
26496 && buffer.contains_str_at(
26497 range.start.saturating_sub_usize(pair_start.len()),
26498 pair_start,
26499 )
26500 })
26501 }
26502
26503 fn insert_extra_newline_tree_sitter(
26504 buffer: &MultiBufferSnapshot,
26505 range: Range<MultiBufferOffset>,
26506 ) -> bool {
26507 let (buffer, range) = match buffer
26508 .range_to_buffer_ranges(range.start..range.end)
26509 .as_slice()
26510 {
26511 [(buffer_snapshot, range, _)] => (buffer_snapshot.clone(), range.clone()),
26512 _ => return false,
26513 };
26514 let pair = {
26515 let mut result: Option<BracketMatch<usize>> = None;
26516
26517 for pair in buffer
26518 .all_bracket_ranges(range.start.0..range.end.0)
26519 .filter(move |pair| {
26520 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
26521 })
26522 {
26523 let len = pair.close_range.end - pair.open_range.start;
26524
26525 if let Some(existing) = &result {
26526 let existing_len = existing.close_range.end - existing.open_range.start;
26527 if len > existing_len {
26528 continue;
26529 }
26530 }
26531
26532 result = Some(pair);
26533 }
26534
26535 result
26536 };
26537 let Some(pair) = pair else {
26538 return false;
26539 };
26540 pair.newline_only
26541 && buffer
26542 .chars_for_range(pair.open_range.end..range.start.0)
26543 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
26544 .all(|c| c.is_whitespace() && c != '\n')
26545 }
26546}
26547
26548fn update_uncommitted_diff_for_buffer(
26549 editor: Entity<Editor>,
26550 project: &Entity<Project>,
26551 buffers: impl IntoIterator<Item = Entity<Buffer>>,
26552 buffer: Entity<MultiBuffer>,
26553 cx: &mut App,
26554) -> Task<()> {
26555 let mut tasks = Vec::new();
26556 project.update(cx, |project, cx| {
26557 for buffer in buffers {
26558 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
26559 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
26560 }
26561 }
26562 });
26563 cx.spawn(async move |cx| {
26564 let diffs = future::join_all(tasks).await;
26565 if editor.read_with(cx, |editor, _cx| editor.temporary_diff_override) {
26566 return;
26567 }
26568
26569 buffer.update(cx, |buffer, cx| {
26570 for diff in diffs.into_iter().flatten() {
26571 buffer.add_diff(diff, cx);
26572 }
26573 });
26574 })
26575}
26576
26577fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
26578 let tab_size = tab_size.get() as usize;
26579 let mut width = offset;
26580
26581 for ch in text.chars() {
26582 width += if ch == '\t' {
26583 tab_size - (width % tab_size)
26584 } else {
26585 1
26586 };
26587 }
26588
26589 width - offset
26590}
26591
26592#[cfg(test)]
26593mod tests {
26594 use super::*;
26595
26596 #[test]
26597 fn test_string_size_with_expanded_tabs() {
26598 let nz = |val| NonZeroU32::new(val).unwrap();
26599 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
26600 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
26601 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
26602 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
26603 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
26604 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
26605 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
26606 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
26607 }
26608}
26609
26610/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
26611struct WordBreakingTokenizer<'a> {
26612 input: &'a str,
26613}
26614
26615impl<'a> WordBreakingTokenizer<'a> {
26616 fn new(input: &'a str) -> Self {
26617 Self { input }
26618 }
26619}
26620
26621fn is_char_ideographic(ch: char) -> bool {
26622 use unicode_script::Script::*;
26623 use unicode_script::UnicodeScript;
26624 matches!(ch.script(), Han | Tangut | Yi)
26625}
26626
26627fn is_grapheme_ideographic(text: &str) -> bool {
26628 text.chars().any(is_char_ideographic)
26629}
26630
26631fn is_grapheme_whitespace(text: &str) -> bool {
26632 text.chars().any(|x| x.is_whitespace())
26633}
26634
26635fn should_stay_with_preceding_ideograph(text: &str) -> bool {
26636 text.chars()
26637 .next()
26638 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
26639}
26640
26641#[derive(PartialEq, Eq, Debug, Clone, Copy)]
26642enum WordBreakToken<'a> {
26643 Word { token: &'a str, grapheme_len: usize },
26644 InlineWhitespace { token: &'a str, grapheme_len: usize },
26645 Newline,
26646}
26647
26648impl<'a> Iterator for WordBreakingTokenizer<'a> {
26649 /// Yields a span, the count of graphemes in the token, and whether it was
26650 /// whitespace. Note that it also breaks at word boundaries.
26651 type Item = WordBreakToken<'a>;
26652
26653 fn next(&mut self) -> Option<Self::Item> {
26654 use unicode_segmentation::UnicodeSegmentation;
26655 if self.input.is_empty() {
26656 return None;
26657 }
26658
26659 let mut iter = self.input.graphemes(true).peekable();
26660 let mut offset = 0;
26661 let mut grapheme_len = 0;
26662 if let Some(first_grapheme) = iter.next() {
26663 let is_newline = first_grapheme == "\n";
26664 let is_whitespace = is_grapheme_whitespace(first_grapheme);
26665 offset += first_grapheme.len();
26666 grapheme_len += 1;
26667 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
26668 if let Some(grapheme) = iter.peek().copied()
26669 && should_stay_with_preceding_ideograph(grapheme)
26670 {
26671 offset += grapheme.len();
26672 grapheme_len += 1;
26673 }
26674 } else {
26675 let mut words = self.input[offset..].split_word_bound_indices().peekable();
26676 let mut next_word_bound = words.peek().copied();
26677 if next_word_bound.is_some_and(|(i, _)| i == 0) {
26678 next_word_bound = words.next();
26679 }
26680 while let Some(grapheme) = iter.peek().copied() {
26681 if next_word_bound.is_some_and(|(i, _)| i == offset) {
26682 break;
26683 };
26684 if is_grapheme_whitespace(grapheme) != is_whitespace
26685 || (grapheme == "\n") != is_newline
26686 {
26687 break;
26688 };
26689 offset += grapheme.len();
26690 grapheme_len += 1;
26691 iter.next();
26692 }
26693 }
26694 let token = &self.input[..offset];
26695 self.input = &self.input[offset..];
26696 if token == "\n" {
26697 Some(WordBreakToken::Newline)
26698 } else if is_whitespace {
26699 Some(WordBreakToken::InlineWhitespace {
26700 token,
26701 grapheme_len,
26702 })
26703 } else {
26704 Some(WordBreakToken::Word {
26705 token,
26706 grapheme_len,
26707 })
26708 }
26709 } else {
26710 None
26711 }
26712 }
26713}
26714
26715#[test]
26716fn test_word_breaking_tokenizer() {
26717 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
26718 ("", &[]),
26719 (" ", &[whitespace(" ", 2)]),
26720 ("Ʒ", &[word("Ʒ", 1)]),
26721 ("Ǽ", &[word("Ǽ", 1)]),
26722 ("⋑", &[word("⋑", 1)]),
26723 ("⋑⋑", &[word("⋑⋑", 2)]),
26724 (
26725 "原理,进而",
26726 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
26727 ),
26728 (
26729 "hello world",
26730 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
26731 ),
26732 (
26733 "hello, world",
26734 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
26735 ),
26736 (
26737 " hello world",
26738 &[
26739 whitespace(" ", 2),
26740 word("hello", 5),
26741 whitespace(" ", 1),
26742 word("world", 5),
26743 ],
26744 ),
26745 (
26746 "这是什么 \n 钢笔",
26747 &[
26748 word("这", 1),
26749 word("是", 1),
26750 word("什", 1),
26751 word("么", 1),
26752 whitespace(" ", 1),
26753 newline(),
26754 whitespace(" ", 1),
26755 word("钢", 1),
26756 word("笔", 1),
26757 ],
26758 ),
26759 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
26760 ];
26761
26762 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26763 WordBreakToken::Word {
26764 token,
26765 grapheme_len,
26766 }
26767 }
26768
26769 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26770 WordBreakToken::InlineWhitespace {
26771 token,
26772 grapheme_len,
26773 }
26774 }
26775
26776 fn newline() -> WordBreakToken<'static> {
26777 WordBreakToken::Newline
26778 }
26779
26780 for (input, result) in tests {
26781 assert_eq!(
26782 WordBreakingTokenizer::new(input)
26783 .collect::<Vec<_>>()
26784 .as_slice(),
26785 *result,
26786 );
26787 }
26788}
26789
26790fn wrap_with_prefix(
26791 first_line_prefix: String,
26792 subsequent_lines_prefix: String,
26793 unwrapped_text: String,
26794 wrap_column: usize,
26795 tab_size: NonZeroU32,
26796 preserve_existing_whitespace: bool,
26797) -> String {
26798 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
26799 let subsequent_lines_prefix_len =
26800 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
26801 let mut wrapped_text = String::new();
26802 let mut current_line = first_line_prefix;
26803 let mut is_first_line = true;
26804
26805 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
26806 let mut current_line_len = first_line_prefix_len;
26807 let mut in_whitespace = false;
26808 for token in tokenizer {
26809 let have_preceding_whitespace = in_whitespace;
26810 match token {
26811 WordBreakToken::Word {
26812 token,
26813 grapheme_len,
26814 } => {
26815 in_whitespace = false;
26816 let current_prefix_len = if is_first_line {
26817 first_line_prefix_len
26818 } else {
26819 subsequent_lines_prefix_len
26820 };
26821 if current_line_len + grapheme_len > wrap_column
26822 && current_line_len != current_prefix_len
26823 {
26824 wrapped_text.push_str(current_line.trim_end());
26825 wrapped_text.push('\n');
26826 is_first_line = false;
26827 current_line = subsequent_lines_prefix.clone();
26828 current_line_len = subsequent_lines_prefix_len;
26829 }
26830 current_line.push_str(token);
26831 current_line_len += grapheme_len;
26832 }
26833 WordBreakToken::InlineWhitespace {
26834 mut token,
26835 mut grapheme_len,
26836 } => {
26837 in_whitespace = true;
26838 if have_preceding_whitespace && !preserve_existing_whitespace {
26839 continue;
26840 }
26841 if !preserve_existing_whitespace {
26842 // Keep a single whitespace grapheme as-is
26843 if let Some(first) =
26844 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
26845 {
26846 token = first;
26847 } else {
26848 token = " ";
26849 }
26850 grapheme_len = 1;
26851 }
26852 let current_prefix_len = if is_first_line {
26853 first_line_prefix_len
26854 } else {
26855 subsequent_lines_prefix_len
26856 };
26857 if current_line_len + grapheme_len > wrap_column {
26858 wrapped_text.push_str(current_line.trim_end());
26859 wrapped_text.push('\n');
26860 is_first_line = false;
26861 current_line = subsequent_lines_prefix.clone();
26862 current_line_len = subsequent_lines_prefix_len;
26863 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
26864 current_line.push_str(token);
26865 current_line_len += grapheme_len;
26866 }
26867 }
26868 WordBreakToken::Newline => {
26869 in_whitespace = true;
26870 let current_prefix_len = if is_first_line {
26871 first_line_prefix_len
26872 } else {
26873 subsequent_lines_prefix_len
26874 };
26875 if preserve_existing_whitespace {
26876 wrapped_text.push_str(current_line.trim_end());
26877 wrapped_text.push('\n');
26878 is_first_line = false;
26879 current_line = subsequent_lines_prefix.clone();
26880 current_line_len = subsequent_lines_prefix_len;
26881 } else if have_preceding_whitespace {
26882 continue;
26883 } else if current_line_len + 1 > wrap_column
26884 && current_line_len != current_prefix_len
26885 {
26886 wrapped_text.push_str(current_line.trim_end());
26887 wrapped_text.push('\n');
26888 is_first_line = false;
26889 current_line = subsequent_lines_prefix.clone();
26890 current_line_len = subsequent_lines_prefix_len;
26891 } else if current_line_len != current_prefix_len {
26892 current_line.push(' ');
26893 current_line_len += 1;
26894 }
26895 }
26896 }
26897 }
26898
26899 if !current_line.is_empty() {
26900 wrapped_text.push_str(¤t_line);
26901 }
26902 wrapped_text
26903}
26904
26905#[test]
26906fn test_wrap_with_prefix() {
26907 assert_eq!(
26908 wrap_with_prefix(
26909 "# ".to_string(),
26910 "# ".to_string(),
26911 "abcdefg".to_string(),
26912 4,
26913 NonZeroU32::new(4).unwrap(),
26914 false,
26915 ),
26916 "# abcdefg"
26917 );
26918 assert_eq!(
26919 wrap_with_prefix(
26920 "".to_string(),
26921 "".to_string(),
26922 "\thello world".to_string(),
26923 8,
26924 NonZeroU32::new(4).unwrap(),
26925 false,
26926 ),
26927 "hello\nworld"
26928 );
26929 assert_eq!(
26930 wrap_with_prefix(
26931 "// ".to_string(),
26932 "// ".to_string(),
26933 "xx \nyy zz aa bb cc".to_string(),
26934 12,
26935 NonZeroU32::new(4).unwrap(),
26936 false,
26937 ),
26938 "// xx yy zz\n// aa bb cc"
26939 );
26940 assert_eq!(
26941 wrap_with_prefix(
26942 String::new(),
26943 String::new(),
26944 "这是什么 \n 钢笔".to_string(),
26945 3,
26946 NonZeroU32::new(4).unwrap(),
26947 false,
26948 ),
26949 "这是什\n么 钢\n笔"
26950 );
26951 assert_eq!(
26952 wrap_with_prefix(
26953 String::new(),
26954 String::new(),
26955 format!("foo{}bar", '\u{2009}'), // thin space
26956 80,
26957 NonZeroU32::new(4).unwrap(),
26958 false,
26959 ),
26960 format!("foo{}bar", '\u{2009}')
26961 );
26962}
26963
26964pub trait CollaborationHub {
26965 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
26966 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
26967 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
26968}
26969
26970impl CollaborationHub for Entity<Project> {
26971 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
26972 self.read(cx).collaborators()
26973 }
26974
26975 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
26976 self.read(cx).user_store().read(cx).participant_indices()
26977 }
26978
26979 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
26980 let this = self.read(cx);
26981 let user_ids = this.collaborators().values().map(|c| c.user_id);
26982 this.user_store().read(cx).participant_names(user_ids, cx)
26983 }
26984}
26985
26986pub trait SemanticsProvider {
26987 fn hover(
26988 &self,
26989 buffer: &Entity<Buffer>,
26990 position: text::Anchor,
26991 cx: &mut App,
26992 ) -> Option<Task<Option<Vec<project::Hover>>>>;
26993
26994 fn inline_values(
26995 &self,
26996 buffer_handle: Entity<Buffer>,
26997 range: Range<text::Anchor>,
26998 cx: &mut App,
26999 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
27000
27001 fn applicable_inlay_chunks(
27002 &self,
27003 buffer: &Entity<Buffer>,
27004 ranges: &[Range<text::Anchor>],
27005 cx: &mut App,
27006 ) -> Vec<Range<BufferRow>>;
27007
27008 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
27009
27010 fn inlay_hints(
27011 &self,
27012 invalidate: InvalidationStrategy,
27013 buffer: Entity<Buffer>,
27014 ranges: Vec<Range<text::Anchor>>,
27015 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
27016 cx: &mut App,
27017 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
27018
27019 fn semantic_tokens(
27020 &self,
27021 buffer: Entity<Buffer>,
27022 refresh: Option<RefreshForServer>,
27023 cx: &mut App,
27024 ) -> Option<Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>>>;
27025
27026 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
27027
27028 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
27029
27030 fn document_highlights(
27031 &self,
27032 buffer: &Entity<Buffer>,
27033 position: text::Anchor,
27034 cx: &mut App,
27035 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
27036
27037 fn definitions(
27038 &self,
27039 buffer: &Entity<Buffer>,
27040 position: text::Anchor,
27041 kind: GotoDefinitionKind,
27042 cx: &mut App,
27043 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
27044
27045 fn range_for_rename(
27046 &self,
27047 buffer: &Entity<Buffer>,
27048 position: text::Anchor,
27049 cx: &mut App,
27050 ) -> Task<Result<Option<Range<text::Anchor>>>>;
27051
27052 fn perform_rename(
27053 &self,
27054 buffer: &Entity<Buffer>,
27055 position: text::Anchor,
27056 new_name: String,
27057 cx: &mut App,
27058 ) -> Option<Task<Result<ProjectTransaction>>>;
27059}
27060
27061pub trait CompletionProvider {
27062 fn completions(
27063 &self,
27064 buffer: &Entity<Buffer>,
27065 buffer_position: text::Anchor,
27066 trigger: CompletionContext,
27067 window: &mut Window,
27068 cx: &mut Context<Editor>,
27069 ) -> Task<Result<Vec<CompletionResponse>>>;
27070
27071 fn resolve_completions(
27072 &self,
27073 _buffer: Entity<Buffer>,
27074 _completion_indices: Vec<usize>,
27075 _completions: Rc<RefCell<Box<[Completion]>>>,
27076 _cx: &mut Context<Editor>,
27077 ) -> Task<Result<bool>> {
27078 Task::ready(Ok(false))
27079 }
27080
27081 fn apply_additional_edits_for_completion(
27082 &self,
27083 _buffer: Entity<Buffer>,
27084 _completions: Rc<RefCell<Box<[Completion]>>>,
27085 _completion_index: usize,
27086 _push_to_history: bool,
27087 _all_commit_ranges: Vec<Range<language::Anchor>>,
27088 _cx: &mut Context<Editor>,
27089 ) -> Task<Result<Option<language::Transaction>>> {
27090 Task::ready(Ok(None))
27091 }
27092
27093 fn is_completion_trigger(
27094 &self,
27095 buffer: &Entity<Buffer>,
27096 position: language::Anchor,
27097 text: &str,
27098 trigger_in_words: bool,
27099 cx: &mut Context<Editor>,
27100 ) -> bool;
27101
27102 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
27103
27104 fn sort_completions(&self) -> bool {
27105 true
27106 }
27107
27108 fn filter_completions(&self) -> bool {
27109 true
27110 }
27111
27112 fn show_snippets(&self) -> bool {
27113 false
27114 }
27115}
27116
27117pub trait CodeActionProvider {
27118 fn id(&self) -> Arc<str>;
27119
27120 fn code_actions(
27121 &self,
27122 buffer: &Entity<Buffer>,
27123 range: Range<text::Anchor>,
27124 window: &mut Window,
27125 cx: &mut App,
27126 ) -> Task<Result<Vec<CodeAction>>>;
27127
27128 fn apply_code_action(
27129 &self,
27130 buffer_handle: Entity<Buffer>,
27131 action: CodeAction,
27132 push_to_history: bool,
27133 window: &mut Window,
27134 cx: &mut App,
27135 ) -> Task<Result<ProjectTransaction>>;
27136}
27137
27138impl CodeActionProvider for Entity<Project> {
27139 fn id(&self) -> Arc<str> {
27140 "project".into()
27141 }
27142
27143 fn code_actions(
27144 &self,
27145 buffer: &Entity<Buffer>,
27146 range: Range<text::Anchor>,
27147 _window: &mut Window,
27148 cx: &mut App,
27149 ) -> Task<Result<Vec<CodeAction>>> {
27150 self.update(cx, |project, cx| {
27151 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
27152 let code_actions = project.code_actions(buffer, range, None, cx);
27153 cx.background_spawn(async move {
27154 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
27155 Ok(code_lens_actions
27156 .context("code lens fetch")?
27157 .into_iter()
27158 .flatten()
27159 .chain(
27160 code_actions
27161 .context("code action fetch")?
27162 .into_iter()
27163 .flatten(),
27164 )
27165 .collect())
27166 })
27167 })
27168 }
27169
27170 fn apply_code_action(
27171 &self,
27172 buffer_handle: Entity<Buffer>,
27173 action: CodeAction,
27174 push_to_history: bool,
27175 _window: &mut Window,
27176 cx: &mut App,
27177 ) -> Task<Result<ProjectTransaction>> {
27178 self.update(cx, |project, cx| {
27179 project.apply_code_action(buffer_handle, action, push_to_history, cx)
27180 })
27181 }
27182}
27183
27184fn snippet_completions(
27185 project: &Project,
27186 buffer: &Entity<Buffer>,
27187 buffer_anchor: text::Anchor,
27188 classifier: CharClassifier,
27189 cx: &mut App,
27190) -> Task<Result<CompletionResponse>> {
27191 let languages = buffer.read(cx).languages_at(buffer_anchor);
27192 let snippet_store = project.snippets().read(cx);
27193
27194 let scopes: Vec<_> = languages
27195 .iter()
27196 .filter_map(|language| {
27197 let language_name = language.lsp_id();
27198 let snippets = snippet_store.snippets_for(Some(language_name), cx);
27199
27200 if snippets.is_empty() {
27201 None
27202 } else {
27203 Some((language.default_scope(), snippets))
27204 }
27205 })
27206 .collect();
27207
27208 if scopes.is_empty() {
27209 return Task::ready(Ok(CompletionResponse {
27210 completions: vec![],
27211 display_options: CompletionDisplayOptions::default(),
27212 is_incomplete: false,
27213 }));
27214 }
27215
27216 let snapshot = buffer.read(cx).text_snapshot();
27217 let executor = cx.background_executor().clone();
27218
27219 cx.background_spawn(async move {
27220 let is_word_char = |c| classifier.is_word(c);
27221
27222 let mut is_incomplete = false;
27223 let mut completions: Vec<Completion> = Vec::new();
27224
27225 const MAX_PREFIX_LEN: usize = 128;
27226 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
27227 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
27228 let window_start = snapshot.clip_offset(window_start, Bias::Left);
27229
27230 let max_buffer_window: String = snapshot
27231 .text_for_range(window_start..buffer_offset)
27232 .collect();
27233
27234 if max_buffer_window.is_empty() {
27235 return Ok(CompletionResponse {
27236 completions: vec![],
27237 display_options: CompletionDisplayOptions::default(),
27238 is_incomplete: true,
27239 });
27240 }
27241
27242 for (_scope, snippets) in scopes.into_iter() {
27243 // Sort snippets by word count to match longer snippet prefixes first.
27244 let mut sorted_snippet_candidates = snippets
27245 .iter()
27246 .enumerate()
27247 .flat_map(|(snippet_ix, snippet)| {
27248 snippet
27249 .prefix
27250 .iter()
27251 .enumerate()
27252 .map(move |(prefix_ix, prefix)| {
27253 let word_count =
27254 snippet_candidate_suffixes(prefix, &is_word_char).count();
27255 ((snippet_ix, prefix_ix), prefix, word_count)
27256 })
27257 })
27258 .collect_vec();
27259 sorted_snippet_candidates
27260 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
27261
27262 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
27263
27264 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, &is_word_char)
27265 .take(
27266 sorted_snippet_candidates
27267 .first()
27268 .map(|(_, _, word_count)| *word_count)
27269 .unwrap_or_default(),
27270 )
27271 .collect_vec();
27272
27273 const MAX_RESULTS: usize = 100;
27274 // Each match also remembers how many characters from the buffer it consumed
27275 let mut matches: Vec<(StringMatch, usize)> = vec![];
27276
27277 let mut snippet_list_cutoff_index = 0;
27278 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
27279 let word_count = buffer_index + 1;
27280 // Increase `snippet_list_cutoff_index` until we have all of the
27281 // snippets with sufficiently many words.
27282 while sorted_snippet_candidates
27283 .get(snippet_list_cutoff_index)
27284 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
27285 *snippet_word_count >= word_count
27286 })
27287 {
27288 snippet_list_cutoff_index += 1;
27289 }
27290
27291 // Take only the candidates with at least `word_count` many words
27292 let snippet_candidates_at_word_len =
27293 &sorted_snippet_candidates[..snippet_list_cutoff_index];
27294
27295 let candidates = snippet_candidates_at_word_len
27296 .iter()
27297 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
27298 .enumerate() // index in `sorted_snippet_candidates`
27299 // First char must match
27300 .filter(|(_ix, prefix)| {
27301 itertools::equal(
27302 prefix
27303 .chars()
27304 .next()
27305 .into_iter()
27306 .flat_map(|c| c.to_lowercase()),
27307 buffer_window
27308 .chars()
27309 .next()
27310 .into_iter()
27311 .flat_map(|c| c.to_lowercase()),
27312 )
27313 })
27314 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
27315 .collect::<Vec<StringMatchCandidate>>();
27316
27317 matches.extend(
27318 fuzzy::match_strings(
27319 &candidates,
27320 &buffer_window,
27321 buffer_window.chars().any(|c| c.is_uppercase()),
27322 true,
27323 MAX_RESULTS - matches.len(), // always prioritize longer snippets
27324 &Default::default(),
27325 executor.clone(),
27326 )
27327 .await
27328 .into_iter()
27329 .map(|string_match| (string_match, buffer_window.len())),
27330 );
27331
27332 if matches.len() >= MAX_RESULTS {
27333 break;
27334 }
27335 }
27336
27337 let to_lsp = |point: &text::Anchor| {
27338 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
27339 point_to_lsp(end)
27340 };
27341 let lsp_end = to_lsp(&buffer_anchor);
27342
27343 if matches.len() >= MAX_RESULTS {
27344 is_incomplete = true;
27345 }
27346
27347 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
27348 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
27349 sorted_snippet_candidates[string_match.candidate_id];
27350 let snippet = &snippets[snippet_index];
27351 let start = buffer_offset - buffer_window_len;
27352 let start = snapshot.anchor_before(start);
27353 let range = start..buffer_anchor;
27354 let lsp_start = to_lsp(&start);
27355 let lsp_range = lsp::Range {
27356 start: lsp_start,
27357 end: lsp_end,
27358 };
27359 Completion {
27360 replace_range: range,
27361 new_text: snippet.body.clone(),
27362 source: CompletionSource::Lsp {
27363 insert_range: None,
27364 server_id: LanguageServerId(usize::MAX),
27365 resolved: true,
27366 lsp_completion: Box::new(lsp::CompletionItem {
27367 label: snippet.prefix.first().unwrap().clone(),
27368 kind: Some(CompletionItemKind::SNIPPET),
27369 label_details: snippet.description.as_ref().map(|description| {
27370 lsp::CompletionItemLabelDetails {
27371 detail: Some(description.clone()),
27372 description: None,
27373 }
27374 }),
27375 insert_text_format: Some(InsertTextFormat::SNIPPET),
27376 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
27377 lsp::InsertReplaceEdit {
27378 new_text: snippet.body.clone(),
27379 insert: lsp_range,
27380 replace: lsp_range,
27381 },
27382 )),
27383 filter_text: Some(snippet.body.clone()),
27384 sort_text: Some(char::MAX.to_string()),
27385 ..lsp::CompletionItem::default()
27386 }),
27387 lsp_defaults: None,
27388 },
27389 label: CodeLabel {
27390 text: matching_prefix.clone(),
27391 runs: Vec::new(),
27392 filter_range: 0..matching_prefix.len(),
27393 },
27394 icon_path: None,
27395 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
27396 single_line: snippet.name.clone().into(),
27397 plain_text: snippet
27398 .description
27399 .clone()
27400 .map(|description| description.into()),
27401 }),
27402 insert_text_mode: None,
27403 confirm: None,
27404 match_start: Some(start),
27405 snippet_deduplication_key: Some((snippet_index, prefix_index)),
27406 }
27407 }));
27408 }
27409
27410 Ok(CompletionResponse {
27411 completions,
27412 display_options: CompletionDisplayOptions::default(),
27413 is_incomplete,
27414 })
27415 })
27416}
27417
27418impl CompletionProvider for Entity<Project> {
27419 fn completions(
27420 &self,
27421 buffer: &Entity<Buffer>,
27422 buffer_position: text::Anchor,
27423 options: CompletionContext,
27424 _window: &mut Window,
27425 cx: &mut Context<Editor>,
27426 ) -> Task<Result<Vec<CompletionResponse>>> {
27427 self.update(cx, |project, cx| {
27428 let task = project.completions(buffer, buffer_position, options, cx);
27429 cx.background_spawn(task)
27430 })
27431 }
27432
27433 fn resolve_completions(
27434 &self,
27435 buffer: Entity<Buffer>,
27436 completion_indices: Vec<usize>,
27437 completions: Rc<RefCell<Box<[Completion]>>>,
27438 cx: &mut Context<Editor>,
27439 ) -> Task<Result<bool>> {
27440 self.update(cx, |project, cx| {
27441 project.lsp_store().update(cx, |lsp_store, cx| {
27442 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
27443 })
27444 })
27445 }
27446
27447 fn apply_additional_edits_for_completion(
27448 &self,
27449 buffer: Entity<Buffer>,
27450 completions: Rc<RefCell<Box<[Completion]>>>,
27451 completion_index: usize,
27452 push_to_history: bool,
27453 all_commit_ranges: Vec<Range<language::Anchor>>,
27454 cx: &mut Context<Editor>,
27455 ) -> Task<Result<Option<language::Transaction>>> {
27456 self.update(cx, |project, cx| {
27457 project.lsp_store().update(cx, |lsp_store, cx| {
27458 lsp_store.apply_additional_edits_for_completion(
27459 buffer,
27460 completions,
27461 completion_index,
27462 push_to_history,
27463 all_commit_ranges,
27464 cx,
27465 )
27466 })
27467 })
27468 }
27469
27470 fn is_completion_trigger(
27471 &self,
27472 buffer: &Entity<Buffer>,
27473 position: language::Anchor,
27474 text: &str,
27475 trigger_in_words: bool,
27476 cx: &mut Context<Editor>,
27477 ) -> bool {
27478 let mut chars = text.chars();
27479 let char = if let Some(char) = chars.next() {
27480 char
27481 } else {
27482 return false;
27483 };
27484 if chars.next().is_some() {
27485 return false;
27486 }
27487
27488 let buffer = buffer.read(cx);
27489 let snapshot = buffer.snapshot();
27490 let classifier = snapshot
27491 .char_classifier_at(position)
27492 .scope_context(Some(CharScopeContext::Completion));
27493 if trigger_in_words && classifier.is_word(char) {
27494 return true;
27495 }
27496
27497 buffer.completion_triggers().contains(text)
27498 }
27499
27500 fn show_snippets(&self) -> bool {
27501 true
27502 }
27503}
27504
27505impl SemanticsProvider for WeakEntity<Project> {
27506 fn hover(
27507 &self,
27508 buffer: &Entity<Buffer>,
27509 position: text::Anchor,
27510 cx: &mut App,
27511 ) -> Option<Task<Option<Vec<project::Hover>>>> {
27512 self.update(cx, |project, cx| project.hover(buffer, position, cx))
27513 .ok()
27514 }
27515
27516 fn document_highlights(
27517 &self,
27518 buffer: &Entity<Buffer>,
27519 position: text::Anchor,
27520 cx: &mut App,
27521 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
27522 self.update(cx, |project, cx| {
27523 project.document_highlights(buffer, position, cx)
27524 })
27525 .ok()
27526 }
27527
27528 fn definitions(
27529 &self,
27530 buffer: &Entity<Buffer>,
27531 position: text::Anchor,
27532 kind: GotoDefinitionKind,
27533 cx: &mut App,
27534 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
27535 self.update(cx, |project, cx| match kind {
27536 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
27537 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
27538 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
27539 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
27540 })
27541 .ok()
27542 }
27543
27544 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27545 self.update(cx, |project, cx| {
27546 if project
27547 .active_debug_session(cx)
27548 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
27549 {
27550 return true;
27551 }
27552
27553 buffer.update(cx, |buffer, cx| {
27554 project.any_language_server_supports_inlay_hints(buffer, cx)
27555 })
27556 })
27557 .unwrap_or(false)
27558 }
27559
27560 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27561 self.update(cx, |project, cx| {
27562 buffer.update(cx, |buffer, cx| {
27563 project.any_language_server_supports_semantic_tokens(buffer, cx)
27564 })
27565 })
27566 .unwrap_or(false)
27567 }
27568
27569 fn inline_values(
27570 &self,
27571 buffer_handle: Entity<Buffer>,
27572 range: Range<text::Anchor>,
27573 cx: &mut App,
27574 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
27575 self.update(cx, |project, cx| {
27576 let (session, active_stack_frame) = project.active_debug_session(cx)?;
27577
27578 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
27579 })
27580 .ok()
27581 .flatten()
27582 }
27583
27584 fn applicable_inlay_chunks(
27585 &self,
27586 buffer: &Entity<Buffer>,
27587 ranges: &[Range<text::Anchor>],
27588 cx: &mut App,
27589 ) -> Vec<Range<BufferRow>> {
27590 self.update(cx, |project, cx| {
27591 project.lsp_store().update(cx, |lsp_store, cx| {
27592 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
27593 })
27594 })
27595 .unwrap_or_default()
27596 }
27597
27598 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
27599 self.update(cx, |project, cx| {
27600 project.lsp_store().update(cx, |lsp_store, _| {
27601 lsp_store.invalidate_inlay_hints(for_buffers)
27602 })
27603 })
27604 .ok();
27605 }
27606
27607 fn inlay_hints(
27608 &self,
27609 invalidate: InvalidationStrategy,
27610 buffer: Entity<Buffer>,
27611 ranges: Vec<Range<text::Anchor>>,
27612 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
27613 cx: &mut App,
27614 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
27615 self.update(cx, |project, cx| {
27616 project.lsp_store().update(cx, |lsp_store, cx| {
27617 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
27618 })
27619 })
27620 .ok()
27621 }
27622
27623 fn semantic_tokens(
27624 &self,
27625 buffer: Entity<Buffer>,
27626 refresh: Option<RefreshForServer>,
27627 cx: &mut App,
27628 ) -> Option<Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>>> {
27629 self.update(cx, |this, cx| {
27630 this.lsp_store().update(cx, |lsp_store, cx| {
27631 lsp_store.semantic_tokens(buffer, refresh, cx)
27632 })
27633 })
27634 .ok()
27635 }
27636
27637 fn range_for_rename(
27638 &self,
27639 buffer: &Entity<Buffer>,
27640 position: text::Anchor,
27641 cx: &mut App,
27642 ) -> Task<Result<Option<Range<text::Anchor>>>> {
27643 let Some(this) = self.upgrade() else {
27644 return Task::ready(Ok(None));
27645 };
27646
27647 this.update(cx, |project, cx| {
27648 let buffer = buffer.clone();
27649 let task = project.prepare_rename(buffer.clone(), position, cx);
27650 cx.spawn(async move |_, cx| {
27651 Ok(match task.await? {
27652 PrepareRenameResponse::Success(range) => Some(range),
27653 PrepareRenameResponse::InvalidPosition => None,
27654 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
27655 // Fallback on using TreeSitter info to determine identifier range
27656 buffer.read_with(cx, |buffer, _| {
27657 let snapshot = buffer.snapshot();
27658 let (range, kind) = snapshot.surrounding_word(position, None);
27659 if kind != Some(CharKind::Word) {
27660 return None;
27661 }
27662 Some(
27663 snapshot.anchor_before(range.start)
27664 ..snapshot.anchor_after(range.end),
27665 )
27666 })
27667 }
27668 })
27669 })
27670 })
27671 }
27672
27673 fn perform_rename(
27674 &self,
27675 buffer: &Entity<Buffer>,
27676 position: text::Anchor,
27677 new_name: String,
27678 cx: &mut App,
27679 ) -> Option<Task<Result<ProjectTransaction>>> {
27680 self.update(cx, |project, cx| {
27681 project.perform_rename(buffer.clone(), position, new_name, cx)
27682 })
27683 .ok()
27684 }
27685}
27686
27687fn consume_contiguous_rows(
27688 contiguous_row_selections: &mut Vec<Selection<Point>>,
27689 selection: &Selection<Point>,
27690 display_map: &DisplaySnapshot,
27691 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
27692) -> (MultiBufferRow, MultiBufferRow) {
27693 contiguous_row_selections.push(selection.clone());
27694 let start_row = starting_row(selection, display_map);
27695 let mut end_row = ending_row(selection, display_map);
27696
27697 while let Some(next_selection) = selections.peek() {
27698 if next_selection.start.row <= end_row.0 {
27699 end_row = ending_row(next_selection, display_map);
27700 contiguous_row_selections.push(selections.next().unwrap().clone());
27701 } else {
27702 break;
27703 }
27704 }
27705 (start_row, end_row)
27706}
27707
27708fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27709 if selection.start.column > 0 {
27710 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
27711 } else {
27712 MultiBufferRow(selection.start.row)
27713 }
27714}
27715
27716fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27717 if next_selection.end.column > 0 || next_selection.is_empty() {
27718 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
27719 } else {
27720 MultiBufferRow(next_selection.end.row)
27721 }
27722}
27723
27724impl EditorSnapshot {
27725 pub fn remote_selections_in_range<'a>(
27726 &'a self,
27727 range: &'a Range<Anchor>,
27728 collaboration_hub: &dyn CollaborationHub,
27729 cx: &'a App,
27730 ) -> impl 'a + Iterator<Item = RemoteSelection> {
27731 let participant_names = collaboration_hub.user_names(cx);
27732 let participant_indices = collaboration_hub.user_participant_indices(cx);
27733 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
27734 let collaborators_by_replica_id = collaborators_by_peer_id
27735 .values()
27736 .map(|collaborator| (collaborator.replica_id, collaborator))
27737 .collect::<HashMap<_, _>>();
27738 self.buffer_snapshot()
27739 .selections_in_range(range, false)
27740 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
27741 if replica_id == ReplicaId::AGENT {
27742 Some(RemoteSelection {
27743 replica_id,
27744 selection,
27745 cursor_shape,
27746 line_mode,
27747 collaborator_id: CollaboratorId::Agent,
27748 user_name: Some("Agent".into()),
27749 color: cx.theme().players().agent(),
27750 })
27751 } else {
27752 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
27753 let participant_index = participant_indices.get(&collaborator.user_id).copied();
27754 let user_name = participant_names.get(&collaborator.user_id).cloned();
27755 Some(RemoteSelection {
27756 replica_id,
27757 selection,
27758 cursor_shape,
27759 line_mode,
27760 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
27761 user_name,
27762 color: if let Some(index) = participant_index {
27763 cx.theme().players().color_for_participant(index.0)
27764 } else {
27765 cx.theme().players().absent()
27766 },
27767 })
27768 }
27769 })
27770 }
27771
27772 pub fn hunks_for_ranges(
27773 &self,
27774 ranges: impl IntoIterator<Item = Range<Point>>,
27775 ) -> Vec<MultiBufferDiffHunk> {
27776 let mut hunks = Vec::new();
27777 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
27778 HashMap::default();
27779 for query_range in ranges {
27780 let query_rows =
27781 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
27782 for hunk in self.buffer_snapshot().diff_hunks_in_range(
27783 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
27784 ) {
27785 // Include deleted hunks that are adjacent to the query range, because
27786 // otherwise they would be missed.
27787 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
27788 if hunk.status().is_deleted() {
27789 intersects_range |= hunk.row_range.start == query_rows.end;
27790 intersects_range |= hunk.row_range.end == query_rows.start;
27791 }
27792 if intersects_range {
27793 if !processed_buffer_rows
27794 .entry(hunk.buffer_id)
27795 .or_default()
27796 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
27797 {
27798 continue;
27799 }
27800 hunks.push(hunk);
27801 }
27802 }
27803 }
27804
27805 hunks
27806 }
27807
27808 fn display_diff_hunks_for_rows<'a>(
27809 &'a self,
27810 display_rows: Range<DisplayRow>,
27811 folded_buffers: &'a HashSet<BufferId>,
27812 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
27813 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
27814 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
27815
27816 self.buffer_snapshot()
27817 .diff_hunks_in_range(buffer_start..buffer_end)
27818 .filter_map(|hunk| {
27819 if folded_buffers.contains(&hunk.buffer_id)
27820 || (hunk.row_range.is_empty() && self.buffer.all_diff_hunks_expanded())
27821 {
27822 return None;
27823 }
27824
27825 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
27826 let hunk_end_point = if hunk.row_range.end > hunk.row_range.start {
27827 let last_row = MultiBufferRow(hunk.row_range.end.0 - 1);
27828 let line_len = self.buffer_snapshot().line_len(last_row);
27829 Point::new(last_row.0, line_len)
27830 } else {
27831 Point::new(hunk.row_range.end.0, 0)
27832 };
27833
27834 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
27835 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
27836
27837 let display_hunk = if hunk_display_start.column() != 0 {
27838 DisplayDiffHunk::Folded {
27839 display_row: hunk_display_start.row(),
27840 }
27841 } else {
27842 let mut end_row = hunk_display_end.row();
27843 if hunk.row_range.end > hunk.row_range.start || hunk_display_end.column() > 0 {
27844 end_row.0 += 1;
27845 }
27846 let is_created_file = hunk.is_created_file();
27847 let multi_buffer_range = hunk.multi_buffer_range.clone();
27848
27849 DisplayDiffHunk::Unfolded {
27850 status: hunk.status(),
27851 diff_base_byte_range: hunk.diff_base_byte_range.start.0
27852 ..hunk.diff_base_byte_range.end.0,
27853 word_diffs: hunk.word_diffs,
27854 display_row_range: hunk_display_start.row()..end_row,
27855 multi_buffer_range,
27856 is_created_file,
27857 }
27858 };
27859
27860 Some(display_hunk)
27861 })
27862 }
27863
27864 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
27865 self.display_snapshot
27866 .buffer_snapshot()
27867 .language_at(position)
27868 }
27869
27870 pub fn is_focused(&self) -> bool {
27871 self.is_focused
27872 }
27873
27874 pub fn placeholder_text(&self) -> Option<String> {
27875 self.placeholder_display_snapshot
27876 .as_ref()
27877 .map(|display_map| display_map.text())
27878 }
27879
27880 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
27881 self.scroll_anchor.scroll_position(&self.display_snapshot)
27882 }
27883
27884 pub fn gutter_dimensions(
27885 &self,
27886 font_id: FontId,
27887 font_size: Pixels,
27888 style: &EditorStyle,
27889 window: &mut Window,
27890 cx: &App,
27891 ) -> GutterDimensions {
27892 if self.show_gutter
27893 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
27894 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
27895 {
27896 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
27897 matches!(
27898 ProjectSettings::get_global(cx).git.git_gutter,
27899 GitGutterSetting::TrackedFiles
27900 )
27901 });
27902 let gutter_settings = EditorSettings::get_global(cx).gutter;
27903 let show_line_numbers = self
27904 .show_line_numbers
27905 .unwrap_or(gutter_settings.line_numbers);
27906 let line_gutter_width = if show_line_numbers {
27907 // Avoid flicker-like gutter resizes when the line number gains another digit by
27908 // only resizing the gutter on files with > 10**min_line_number_digits lines.
27909 let min_width_for_number_on_gutter =
27910 ch_advance * gutter_settings.min_line_number_digits as f32;
27911 self.max_line_number_width(style, window)
27912 .max(min_width_for_number_on_gutter)
27913 } else {
27914 0.0.into()
27915 };
27916
27917 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
27918 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
27919
27920 let git_blame_entries_width =
27921 self.git_blame_gutter_max_author_length
27922 .map(|max_author_length| {
27923 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
27924 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
27925
27926 /// The number of characters to dedicate to gaps and margins.
27927 const SPACING_WIDTH: usize = 4;
27928
27929 let max_char_count = max_author_length.min(renderer.max_author_length())
27930 + ::git::SHORT_SHA_LENGTH
27931 + MAX_RELATIVE_TIMESTAMP.len()
27932 + SPACING_WIDTH;
27933
27934 ch_advance * max_char_count
27935 });
27936
27937 let is_singleton = self.buffer_snapshot().is_singleton();
27938
27939 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
27940 left_padding += if !is_singleton {
27941 ch_width * 4.0
27942 } else if show_runnables || show_breakpoints {
27943 ch_width * 3.0
27944 } else if show_git_gutter && show_line_numbers {
27945 ch_width * 2.0
27946 } else if show_git_gutter || show_line_numbers {
27947 ch_width
27948 } else {
27949 px(0.)
27950 };
27951
27952 let shows_folds = is_singleton && gutter_settings.folds;
27953
27954 let right_padding = if shows_folds && show_line_numbers {
27955 ch_width * 4.0
27956 } else if shows_folds || (!is_singleton && show_line_numbers) {
27957 ch_width * 3.0
27958 } else if show_line_numbers {
27959 ch_width
27960 } else {
27961 px(0.)
27962 };
27963
27964 GutterDimensions {
27965 left_padding,
27966 right_padding,
27967 width: line_gutter_width + left_padding + right_padding,
27968 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
27969 git_blame_entries_width,
27970 }
27971 } else if self.offset_content {
27972 GutterDimensions::default_with_margin(font_id, font_size, cx)
27973 } else {
27974 GutterDimensions::default()
27975 }
27976 }
27977
27978 pub fn render_crease_toggle(
27979 &self,
27980 buffer_row: MultiBufferRow,
27981 row_contains_cursor: bool,
27982 editor: Entity<Editor>,
27983 window: &mut Window,
27984 cx: &mut App,
27985 ) -> Option<AnyElement> {
27986 let folded = self.is_line_folded(buffer_row);
27987 let mut is_foldable = false;
27988
27989 if let Some(crease) = self
27990 .crease_snapshot
27991 .query_row(buffer_row, self.buffer_snapshot())
27992 {
27993 is_foldable = true;
27994 match crease {
27995 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
27996 if let Some(render_toggle) = render_toggle {
27997 let toggle_callback =
27998 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
27999 if folded {
28000 editor.update(cx, |editor, cx| {
28001 editor.fold_at(buffer_row, window, cx)
28002 });
28003 } else {
28004 editor.update(cx, |editor, cx| {
28005 editor.unfold_at(buffer_row, window, cx)
28006 });
28007 }
28008 });
28009 return Some((render_toggle)(
28010 buffer_row,
28011 folded,
28012 toggle_callback,
28013 window,
28014 cx,
28015 ));
28016 }
28017 }
28018 }
28019 }
28020
28021 is_foldable |= !self.use_lsp_folding_ranges && self.starts_indent(buffer_row);
28022
28023 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
28024 Some(
28025 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
28026 .toggle_state(folded)
28027 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
28028 if folded {
28029 this.unfold_at(buffer_row, window, cx);
28030 } else {
28031 this.fold_at(buffer_row, window, cx);
28032 }
28033 }))
28034 .into_any_element(),
28035 )
28036 } else {
28037 None
28038 }
28039 }
28040
28041 pub fn render_crease_trailer(
28042 &self,
28043 buffer_row: MultiBufferRow,
28044 window: &mut Window,
28045 cx: &mut App,
28046 ) -> Option<AnyElement> {
28047 let folded = self.is_line_folded(buffer_row);
28048 if let Crease::Inline { render_trailer, .. } = self
28049 .crease_snapshot
28050 .query_row(buffer_row, self.buffer_snapshot())?
28051 {
28052 let render_trailer = render_trailer.as_ref()?;
28053 Some(render_trailer(buffer_row, folded, window, cx))
28054 } else {
28055 None
28056 }
28057 }
28058
28059 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
28060 let digit_count = self.widest_line_number().ilog10() + 1;
28061 column_pixels(style, digit_count as usize, window)
28062 }
28063
28064 /// Returns the line delta from `base` to `line` in the multibuffer, ignoring wrapped lines.
28065 ///
28066 /// This is positive if `base` is before `line`.
28067 fn relative_line_delta(
28068 &self,
28069 current_selection_head: DisplayRow,
28070 first_visible_row: DisplayRow,
28071 consider_wrapped_lines: bool,
28072 ) -> i64 {
28073 let current_selection_head = current_selection_head.as_display_point().to_point(self);
28074 let first_visible_row = first_visible_row.as_display_point().to_point(self);
28075
28076 if consider_wrapped_lines {
28077 let wrap_snapshot = self.wrap_snapshot();
28078 let base_wrap_row = wrap_snapshot
28079 .make_wrap_point(current_selection_head, Bias::Left)
28080 .row();
28081 let wrap_row = wrap_snapshot
28082 .make_wrap_point(first_visible_row, Bias::Left)
28083 .row();
28084
28085 wrap_row.0 as i64 - base_wrap_row.0 as i64
28086 } else {
28087 let fold_snapshot = self.fold_snapshot();
28088 let base_fold_row = fold_snapshot
28089 .to_fold_point(self.to_inlay_point(current_selection_head), Bias::Left)
28090 .row();
28091 let fold_row = fold_snapshot
28092 .to_fold_point(self.to_inlay_point(first_visible_row), Bias::Left)
28093 .row();
28094
28095 fold_row as i64 - base_fold_row as i64
28096 }
28097 }
28098
28099 /// Returns the unsigned relative line number to display for each row in `rows`.
28100 ///
28101 /// Wrapped rows are excluded from the hashmap if `count_relative_lines` is `false`.
28102 pub fn calculate_relative_line_numbers(
28103 &self,
28104 rows: &Range<DisplayRow>,
28105 current_selection_head: DisplayRow,
28106 count_wrapped_lines: bool,
28107 ) -> HashMap<DisplayRow, u32> {
28108 let initial_offset =
28109 self.relative_line_delta(current_selection_head, rows.start, count_wrapped_lines);
28110
28111 self.row_infos(rows.start)
28112 .take(rows.len())
28113 .enumerate()
28114 .map(|(i, row_info)| (DisplayRow(rows.start.0 + i as u32), row_info))
28115 .filter(|(_row, row_info)| {
28116 row_info.buffer_row.is_some()
28117 || (count_wrapped_lines && row_info.wrapped_buffer_row.is_some())
28118 })
28119 .enumerate()
28120 .filter_map(|(i, (row, row_info))| {
28121 // We want to ensure here that the current line has absolute
28122 // numbering, even if we are in a soft-wrapped line. With the
28123 // exception that if we are in a deleted line, we should number this
28124 // relative with 0, as otherwise it would have no line number at all
28125 let relative_line_number = (initial_offset + i as i64).unsigned_abs() as u32;
28126
28127 (relative_line_number != 0
28128 || row_info
28129 .diff_status
28130 .is_some_and(|status| status.is_deleted()))
28131 .then_some((row, relative_line_number))
28132 })
28133 .collect()
28134 }
28135}
28136
28137pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
28138 let font_size = style.text.font_size.to_pixels(window.rem_size());
28139 let layout = window.text_system().shape_line(
28140 SharedString::from(" ".repeat(column)),
28141 font_size,
28142 &[TextRun {
28143 len: column,
28144 font: style.text.font(),
28145 color: Hsla::default(),
28146 ..Default::default()
28147 }],
28148 None,
28149 );
28150
28151 layout.width
28152}
28153
28154impl Deref for EditorSnapshot {
28155 type Target = DisplaySnapshot;
28156
28157 fn deref(&self) -> &Self::Target {
28158 &self.display_snapshot
28159 }
28160}
28161
28162#[derive(Clone, Debug, PartialEq, Eq)]
28163pub enum EditorEvent {
28164 /// Emitted when the stored review comments change (added, removed, or updated).
28165 ReviewCommentsChanged {
28166 /// The new total count of review comments.
28167 total_count: usize,
28168 },
28169 InputIgnored {
28170 text: Arc<str>,
28171 },
28172 InputHandled {
28173 utf16_range_to_replace: Option<Range<isize>>,
28174 text: Arc<str>,
28175 },
28176 BufferRangesUpdated {
28177 buffer: Entity<Buffer>,
28178 path_key: PathKey,
28179 ranges: Vec<ExcerptRange<text::Anchor>>,
28180 },
28181 BuffersRemoved {
28182 removed_buffer_ids: Vec<BufferId>,
28183 },
28184 BuffersEdited {
28185 buffer_ids: Vec<BufferId>,
28186 },
28187 BufferFoldToggled {
28188 ids: Vec<BufferId>,
28189 folded: bool,
28190 },
28191 ExpandExcerptsRequested {
28192 excerpt_anchors: Vec<Anchor>,
28193 lines: u32,
28194 direction: ExpandExcerptDirection,
28195 },
28196 StageOrUnstageRequested {
28197 stage: bool,
28198 hunks: Vec<MultiBufferDiffHunk>,
28199 },
28200 OpenExcerptsRequested {
28201 selections_by_buffer: HashMap<BufferId, (Vec<Range<BufferOffset>>, Option<u32>)>,
28202 split: bool,
28203 },
28204 RestoreRequested {
28205 hunks: Vec<MultiBufferDiffHunk>,
28206 },
28207 BufferEdited,
28208 Edited {
28209 transaction_id: clock::Lamport,
28210 },
28211 Reparsed(BufferId),
28212 Focused,
28213 FocusedIn,
28214 Blurred,
28215 DirtyChanged,
28216 Saved,
28217 TitleChanged,
28218 SelectionsChanged {
28219 local: bool,
28220 },
28221 ScrollPositionChanged {
28222 local: bool,
28223 autoscroll: bool,
28224 },
28225 TransactionUndone {
28226 transaction_id: clock::Lamport,
28227 },
28228 TransactionBegun {
28229 transaction_id: clock::Lamport,
28230 },
28231 CursorShapeChanged,
28232 BreadcrumbsChanged,
28233 OutlineSymbolsChanged,
28234 PushedToNavHistory {
28235 anchor: Anchor,
28236 is_deactivate: bool,
28237 },
28238}
28239
28240impl EventEmitter<EditorEvent> for Editor {}
28241
28242impl Focusable for Editor {
28243 fn focus_handle(&self, _cx: &App) -> FocusHandle {
28244 self.focus_handle.clone()
28245 }
28246}
28247
28248impl Render for Editor {
28249 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28250 EditorElement::new(&cx.entity(), self.create_style(cx))
28251 }
28252}
28253
28254impl EntityInputHandler for Editor {
28255 fn text_for_range(
28256 &mut self,
28257 range_utf16: Range<usize>,
28258 adjusted_range: &mut Option<Range<usize>>,
28259 _: &mut Window,
28260 cx: &mut Context<Self>,
28261 ) -> Option<String> {
28262 let snapshot = self.buffer.read(cx).read(cx);
28263 let start = snapshot.clip_offset_utf16(
28264 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
28265 Bias::Left,
28266 );
28267 let end = snapshot.clip_offset_utf16(
28268 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
28269 Bias::Right,
28270 );
28271 if (start.0.0..end.0.0) != range_utf16 {
28272 adjusted_range.replace(start.0.0..end.0.0);
28273 }
28274 Some(snapshot.text_for_range(start..end).collect())
28275 }
28276
28277 fn selected_text_range(
28278 &mut self,
28279 ignore_disabled_input: bool,
28280 _: &mut Window,
28281 cx: &mut Context<Self>,
28282 ) -> Option<UTF16Selection> {
28283 // Prevent the IME menu from appearing when holding down an alphabetic key
28284 // while input is disabled.
28285 if !ignore_disabled_input && !self.input_enabled {
28286 return None;
28287 }
28288
28289 let selection = self
28290 .selections
28291 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
28292 let range = selection.range();
28293
28294 Some(UTF16Selection {
28295 range: range.start.0.0..range.end.0.0,
28296 reversed: selection.reversed,
28297 })
28298 }
28299
28300 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
28301 let snapshot = self.buffer.read(cx).read(cx);
28302 let range = self
28303 .text_highlights(HighlightKey::InputComposition, cx)?
28304 .1
28305 .first()?;
28306 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
28307 }
28308
28309 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
28310 self.clear_highlights(HighlightKey::InputComposition, cx);
28311 self.ime_transaction.take();
28312 }
28313
28314 fn replace_text_in_range(
28315 &mut self,
28316 range_utf16: Option<Range<usize>>,
28317 text: &str,
28318 window: &mut Window,
28319 cx: &mut Context<Self>,
28320 ) {
28321 if !self.input_enabled {
28322 cx.emit(EditorEvent::InputIgnored { text: text.into() });
28323 return;
28324 }
28325
28326 self.transact(window, cx, |this, window, cx| {
28327 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
28328 if let Some(marked_ranges) = this.marked_text_ranges(cx) {
28329 // During IME composition, macOS reports the replacement range
28330 // relative to the first marked region (the only one visible via
28331 // marked_text_range). The correct targets for replacement are the
28332 // marked ranges themselves — one per cursor — so use them directly.
28333 Some(marked_ranges)
28334 } else if range_utf16.start == range_utf16.end {
28335 // An empty replacement range means "insert at cursor" with no text
28336 // to replace. macOS reports the cursor position from its own
28337 // (single-cursor) view of the buffer, which diverges from our actual
28338 // cursor positions after multi-cursor edits have shifted offsets.
28339 // Treating this as range_utf16=None lets each cursor insert in place.
28340 None
28341 } else {
28342 // Outside of IME composition (e.g. Accessibility Keyboard word
28343 // completion), the range is an absolute document offset for the
28344 // newest cursor. Fan it out to all cursors via
28345 // selection_replacement_ranges, which applies the delta relative
28346 // to the newest selection to every cursor.
28347 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
28348 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
28349 Some(this.selection_replacement_ranges(range_utf16, cx))
28350 }
28351 } else {
28352 this.marked_text_ranges(cx)
28353 };
28354
28355 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
28356 let newest_selection_id = this.selections.newest_anchor().id;
28357 this.selections
28358 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
28359 .iter()
28360 .zip(ranges_to_replace.iter())
28361 .find_map(|(selection, range)| {
28362 if selection.id == newest_selection_id {
28363 Some(
28364 (range.start.0.0 as isize - selection.head().0.0 as isize)
28365 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
28366 )
28367 } else {
28368 None
28369 }
28370 })
28371 });
28372
28373 cx.emit(EditorEvent::InputHandled {
28374 utf16_range_to_replace: range_to_replace,
28375 text: text.into(),
28376 });
28377
28378 if let Some(new_selected_ranges) = new_selected_ranges {
28379 // Only backspace if at least one range covers actual text. When all
28380 // ranges are empty (e.g. a trailing-space insertion from Accessibility
28381 // Keyboard sends replacementRange=cursor..cursor), backspace would
28382 // incorrectly delete the character just before the cursor.
28383 let should_backspace = new_selected_ranges.iter().any(|r| r.start != r.end);
28384 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
28385 selections.select_ranges(new_selected_ranges)
28386 });
28387 if should_backspace {
28388 this.backspace(&Default::default(), window, cx);
28389 }
28390 }
28391
28392 this.handle_input(text, window, cx);
28393 });
28394
28395 if let Some(transaction) = self.ime_transaction {
28396 self.buffer.update(cx, |buffer, cx| {
28397 buffer.group_until_transaction(transaction, cx);
28398 });
28399 }
28400
28401 self.unmark_text(window, cx);
28402 }
28403
28404 fn replace_and_mark_text_in_range(
28405 &mut self,
28406 range_utf16: Option<Range<usize>>,
28407 text: &str,
28408 new_selected_range_utf16: Option<Range<usize>>,
28409 window: &mut Window,
28410 cx: &mut Context<Self>,
28411 ) {
28412 if !self.input_enabled {
28413 return;
28414 }
28415
28416 let transaction = self.transact(window, cx, |this, window, cx| {
28417 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
28418 let snapshot = this.buffer.read(cx).read(cx);
28419 if let Some(relative_range_utf16) = range_utf16.as_ref() {
28420 for marked_range in &mut marked_ranges {
28421 marked_range.end = marked_range.start + relative_range_utf16.end;
28422 marked_range.start += relative_range_utf16.start;
28423 marked_range.start =
28424 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
28425 marked_range.end =
28426 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
28427 }
28428 }
28429 Some(marked_ranges)
28430 } else if let Some(range_utf16) = range_utf16 {
28431 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
28432 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
28433 Some(this.selection_replacement_ranges(range_utf16, cx))
28434 } else {
28435 None
28436 };
28437
28438 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
28439 let newest_selection_id = this.selections.newest_anchor().id;
28440 this.selections
28441 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
28442 .iter()
28443 .zip(ranges_to_replace.iter())
28444 .find_map(|(selection, range)| {
28445 if selection.id == newest_selection_id {
28446 Some(
28447 (range.start.0.0 as isize - selection.head().0.0 as isize)
28448 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
28449 )
28450 } else {
28451 None
28452 }
28453 })
28454 });
28455
28456 cx.emit(EditorEvent::InputHandled {
28457 utf16_range_to_replace: range_to_replace,
28458 text: text.into(),
28459 });
28460
28461 if let Some(ranges) = ranges_to_replace {
28462 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28463 s.select_ranges(ranges)
28464 });
28465 }
28466
28467 let marked_ranges = {
28468 let snapshot = this.buffer.read(cx).read(cx);
28469 this.selections
28470 .disjoint_anchors_arc()
28471 .iter()
28472 .map(|selection| {
28473 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
28474 })
28475 .collect::<Vec<_>>()
28476 };
28477
28478 if text.is_empty() {
28479 this.unmark_text(window, cx);
28480 } else {
28481 this.highlight_text(
28482 HighlightKey::InputComposition,
28483 marked_ranges.clone(),
28484 HighlightStyle {
28485 underline: Some(UnderlineStyle {
28486 thickness: px(1.),
28487 color: None,
28488 wavy: false,
28489 }),
28490 ..Default::default()
28491 },
28492 cx,
28493 );
28494 }
28495
28496 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
28497 let use_autoclose = this.use_autoclose;
28498 let use_auto_surround = this.use_auto_surround;
28499 this.set_use_autoclose(false);
28500 this.set_use_auto_surround(false);
28501 this.handle_input(text, window, cx);
28502 this.set_use_autoclose(use_autoclose);
28503 this.set_use_auto_surround(use_auto_surround);
28504
28505 if let Some(new_selected_range) = new_selected_range_utf16 {
28506 let snapshot = this.buffer.read(cx).read(cx);
28507 let new_selected_ranges = marked_ranges
28508 .into_iter()
28509 .map(|marked_range| {
28510 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
28511 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
28512 insertion_start.0 + new_selected_range.start,
28513 ));
28514 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
28515 insertion_start.0 + new_selected_range.end,
28516 ));
28517 snapshot.clip_offset_utf16(new_start, Bias::Left)
28518 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
28519 })
28520 .collect::<Vec<_>>();
28521
28522 drop(snapshot);
28523 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
28524 selections.select_ranges(new_selected_ranges)
28525 });
28526 }
28527 });
28528
28529 self.ime_transaction = self.ime_transaction.or(transaction);
28530 if let Some(transaction) = self.ime_transaction {
28531 self.buffer.update(cx, |buffer, cx| {
28532 buffer.group_until_transaction(transaction, cx);
28533 });
28534 }
28535
28536 if self
28537 .text_highlights(HighlightKey::InputComposition, cx)
28538 .is_none()
28539 {
28540 self.ime_transaction.take();
28541 }
28542 }
28543
28544 fn bounds_for_range(
28545 &mut self,
28546 range_utf16: Range<usize>,
28547 element_bounds: gpui::Bounds<Pixels>,
28548 window: &mut Window,
28549 cx: &mut Context<Self>,
28550 ) -> Option<gpui::Bounds<Pixels>> {
28551 let text_layout_details = self.text_layout_details(window, cx);
28552 let CharacterDimensions {
28553 em_width,
28554 em_advance,
28555 line_height,
28556 } = self.character_dimensions(window, cx);
28557
28558 let snapshot = self.snapshot(window, cx);
28559 let scroll_position = snapshot.scroll_position();
28560 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
28561
28562 let start =
28563 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
28564 let x = Pixels::from(
28565 ScrollOffset::from(
28566 snapshot.x_for_display_point(start, &text_layout_details)
28567 + self.gutter_dimensions.full_width(),
28568 ) - scroll_left,
28569 );
28570 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
28571
28572 Some(Bounds {
28573 origin: element_bounds.origin + point(x, y),
28574 size: size(em_width, line_height),
28575 })
28576 }
28577
28578 fn character_index_for_point(
28579 &mut self,
28580 point: gpui::Point<Pixels>,
28581 _window: &mut Window,
28582 _cx: &mut Context<Self>,
28583 ) -> Option<usize> {
28584 let position_map = self.last_position_map.as_ref()?;
28585 if !position_map.text_hitbox.contains(&point) {
28586 return None;
28587 }
28588 let display_point = position_map.point_for_position(point).previous_valid;
28589 let anchor = position_map
28590 .snapshot
28591 .display_point_to_anchor(display_point, Bias::Left);
28592 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
28593 Some(utf16_offset.0.0)
28594 }
28595
28596 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
28597 self.expects_character_input
28598 }
28599}
28600
28601trait SelectionExt {
28602 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
28603 fn spanned_rows(
28604 &self,
28605 include_end_if_at_line_start: bool,
28606 map: &DisplaySnapshot,
28607 ) -> Range<MultiBufferRow>;
28608}
28609
28610impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
28611 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
28612 let start = self
28613 .start
28614 .to_point(map.buffer_snapshot())
28615 .to_display_point(map);
28616 let end = self
28617 .end
28618 .to_point(map.buffer_snapshot())
28619 .to_display_point(map);
28620 if self.reversed {
28621 end..start
28622 } else {
28623 start..end
28624 }
28625 }
28626
28627 fn spanned_rows(
28628 &self,
28629 include_end_if_at_line_start: bool,
28630 map: &DisplaySnapshot,
28631 ) -> Range<MultiBufferRow> {
28632 let start = self.start.to_point(map.buffer_snapshot());
28633 let mut end = self.end.to_point(map.buffer_snapshot());
28634 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
28635 end.row -= 1;
28636 }
28637
28638 let buffer_start = map.prev_line_boundary(start).0;
28639 let buffer_end = map.next_line_boundary(end).0;
28640 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
28641 }
28642}
28643
28644impl<T: InvalidationRegion> InvalidationStack<T> {
28645 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
28646 where
28647 S: Clone + ToOffset,
28648 {
28649 while let Some(region) = self.last() {
28650 let all_selections_inside_invalidation_ranges =
28651 if selections.len() == region.ranges().len() {
28652 selections
28653 .iter()
28654 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
28655 .all(|(selection, invalidation_range)| {
28656 let head = selection.head().to_offset(buffer);
28657 invalidation_range.start <= head && invalidation_range.end >= head
28658 })
28659 } else {
28660 false
28661 };
28662
28663 if all_selections_inside_invalidation_ranges {
28664 break;
28665 } else {
28666 self.pop();
28667 }
28668 }
28669 }
28670}
28671
28672#[derive(Clone)]
28673struct ErasedEditorImpl(Entity<Editor>);
28674
28675impl ui_input::ErasedEditor for ErasedEditorImpl {
28676 fn text(&self, cx: &App) -> String {
28677 self.0.read(cx).text(cx)
28678 }
28679
28680 fn set_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28681 self.0.update(cx, |this, cx| {
28682 this.set_text(text, window, cx);
28683 })
28684 }
28685
28686 fn clear(&self, window: &mut Window, cx: &mut App) {
28687 self.0.update(cx, |this, cx| this.clear(window, cx));
28688 }
28689
28690 fn set_placeholder_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28691 self.0.update(cx, |this, cx| {
28692 this.set_placeholder_text(text, window, cx);
28693 });
28694 }
28695
28696 fn focus_handle(&self, cx: &App) -> FocusHandle {
28697 self.0.read(cx).focus_handle(cx)
28698 }
28699
28700 fn render(&self, _: &mut Window, cx: &App) -> AnyElement {
28701 let settings = ThemeSettings::get_global(cx);
28702 let theme_color = cx.theme().colors();
28703
28704 let text_style = TextStyle {
28705 font_family: settings.ui_font.family.clone(),
28706 font_features: settings.ui_font.features.clone(),
28707 font_size: rems(0.875).into(),
28708 font_weight: settings.ui_font.weight,
28709 font_style: FontStyle::Normal,
28710 line_height: relative(1.2),
28711 color: theme_color.text,
28712 ..Default::default()
28713 };
28714 let editor_style = EditorStyle {
28715 background: theme_color.ghost_element_background,
28716 local_player: cx.theme().players().local(),
28717 syntax: cx.theme().syntax().clone(),
28718 text: text_style,
28719 ..Default::default()
28720 };
28721 EditorElement::new(&self.0, editor_style).into_any()
28722 }
28723
28724 fn as_any(&self) -> &dyn Any {
28725 &self.0
28726 }
28727
28728 fn move_selection_to_end(&self, window: &mut Window, cx: &mut App) {
28729 self.0.update(cx, |editor, cx| {
28730 let editor_offset = editor.buffer().read(cx).len(cx);
28731 editor.change_selections(
28732 SelectionEffects::scroll(Autoscroll::Next),
28733 window,
28734 cx,
28735 |s| s.select_ranges(Some(editor_offset..editor_offset)),
28736 );
28737 });
28738 }
28739
28740 fn subscribe(
28741 &self,
28742 mut callback: Box<dyn FnMut(ui_input::ErasedEditorEvent, &mut Window, &mut App) + 'static>,
28743 window: &mut Window,
28744 cx: &mut App,
28745 ) -> Subscription {
28746 window.subscribe(&self.0, cx, move |_, event: &EditorEvent, window, cx| {
28747 let event = match event {
28748 EditorEvent::BufferEdited => ui_input::ErasedEditorEvent::BufferEdited,
28749 EditorEvent::Blurred => ui_input::ErasedEditorEvent::Blurred,
28750 _ => return,
28751 };
28752 (callback)(event, window, cx);
28753 })
28754 }
28755
28756 fn set_masked(&self, masked: bool, _window: &mut Window, cx: &mut App) {
28757 self.0.update(cx, |editor, cx| {
28758 editor.set_masked(masked, cx);
28759 });
28760 }
28761}
28762impl<T> Default for InvalidationStack<T> {
28763 fn default() -> Self {
28764 Self(Default::default())
28765 }
28766}
28767
28768impl<T> Deref for InvalidationStack<T> {
28769 type Target = Vec<T>;
28770
28771 fn deref(&self) -> &Self::Target {
28772 &self.0
28773 }
28774}
28775
28776impl<T> DerefMut for InvalidationStack<T> {
28777 fn deref_mut(&mut self) -> &mut Self::Target {
28778 &mut self.0
28779 }
28780}
28781
28782impl InvalidationRegion for SnippetState {
28783 fn ranges(&self) -> &[Range<Anchor>] {
28784 &self.ranges[self.active_index]
28785 }
28786}
28787
28788fn edit_prediction_edit_text(
28789 current_snapshot: &BufferSnapshot,
28790 edits: &[(Range<Anchor>, impl AsRef<str>)],
28791 edit_preview: &EditPreview,
28792 include_deletions: bool,
28793 multibuffer_snapshot: &MultiBufferSnapshot,
28794 cx: &App,
28795) -> HighlightedText {
28796 let edits = edits
28797 .iter()
28798 .filter_map(|(anchor, text)| {
28799 Some((
28800 multibuffer_snapshot
28801 .anchor_range_to_buffer_anchor_range(anchor.clone())?
28802 .1,
28803 text,
28804 ))
28805 })
28806 .collect::<Vec<_>>();
28807
28808 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
28809}
28810
28811fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
28812 // Fallback for providers that don't provide edit_preview (like Copilot)
28813 // Just show the raw edit text with basic styling
28814 let mut text = String::new();
28815 let mut highlights = Vec::new();
28816
28817 let insertion_highlight_style = HighlightStyle {
28818 color: Some(cx.theme().colors().text),
28819 ..Default::default()
28820 };
28821
28822 for (_, edit_text) in edits {
28823 let start_offset = text.len();
28824 text.push_str(edit_text);
28825 let end_offset = text.len();
28826
28827 if start_offset < end_offset {
28828 highlights.push((start_offset..end_offset, insertion_highlight_style));
28829 }
28830 }
28831
28832 HighlightedText {
28833 text: text.into(),
28834 highlights,
28835 }
28836}
28837
28838pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
28839 match severity {
28840 lsp::DiagnosticSeverity::ERROR => colors.error,
28841 lsp::DiagnosticSeverity::WARNING => colors.warning,
28842 lsp::DiagnosticSeverity::INFORMATION => colors.info,
28843 lsp::DiagnosticSeverity::HINT => colors.info,
28844 _ => colors.ignored,
28845 }
28846}
28847
28848pub fn styled_runs_for_code_label<'a>(
28849 label: &'a CodeLabel,
28850 syntax_theme: &'a theme::SyntaxTheme,
28851 local_player: &'a theme::PlayerColor,
28852) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
28853 let fade_out = HighlightStyle {
28854 fade_out: Some(0.35),
28855 ..Default::default()
28856 };
28857
28858 if label.runs.is_empty() {
28859 let desc_start = label.filter_range.end;
28860 let fade_run =
28861 (desc_start < label.text.len()).then(|| (desc_start..label.text.len(), fade_out));
28862 return Either::Left(fade_run.into_iter());
28863 }
28864
28865 let mut prev_end = label.filter_range.end;
28866 Either::Right(
28867 label
28868 .runs
28869 .iter()
28870 .enumerate()
28871 .flat_map(move |(ix, (range, highlight_id))| {
28872 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
28873 HighlightStyle {
28874 color: Some(local_player.cursor),
28875 ..Default::default()
28876 }
28877 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
28878 HighlightStyle {
28879 background_color: Some(local_player.selection),
28880 ..Default::default()
28881 }
28882 } else if let Some(style) = syntax_theme.get(*highlight_id).cloned() {
28883 style
28884 } else {
28885 return Default::default();
28886 };
28887
28888 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
28889 let muted_style = style.highlight(fade_out);
28890 if range.start >= label.filter_range.end {
28891 if range.start > prev_end {
28892 runs.push((prev_end..range.start, fade_out));
28893 }
28894 runs.push((range.clone(), muted_style));
28895 } else if range.end <= label.filter_range.end {
28896 runs.push((range.clone(), style));
28897 } else {
28898 runs.push((range.start..label.filter_range.end, style));
28899 runs.push((label.filter_range.end..range.end, muted_style));
28900 }
28901 prev_end = cmp::max(prev_end, range.end);
28902
28903 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
28904 runs.push((prev_end..label.text.len(), fade_out));
28905 }
28906
28907 runs
28908 }),
28909 )
28910}
28911
28912pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
28913 let mut prev_index = 0;
28914 let mut prev_codepoint: Option<char> = None;
28915 text.char_indices()
28916 .chain([(text.len(), '\0')])
28917 .filter_map(move |(index, codepoint)| {
28918 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28919 let is_boundary = index == text.len()
28920 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
28921 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
28922 if is_boundary {
28923 let chunk = &text[prev_index..index];
28924 prev_index = index;
28925 Some(chunk)
28926 } else {
28927 None
28928 }
28929 })
28930}
28931
28932/// Given a string of text immediately before the cursor, iterates over possible
28933/// strings a snippet could match to. More precisely: returns an iterator over
28934/// suffixes of `text` created by splitting at word boundaries (before & after
28935/// every non-word character).
28936///
28937/// Shorter suffixes are returned first.
28938pub(crate) fn snippet_candidate_suffixes<'a>(
28939 text: &'a str,
28940 is_word_char: &'a dyn Fn(char) -> bool,
28941) -> impl std::iter::Iterator<Item = &'a str> + 'a {
28942 let mut prev_index = text.len();
28943 let mut prev_codepoint = None;
28944 text.char_indices()
28945 .rev()
28946 .chain([(0, '\0')])
28947 .filter_map(move |(index, codepoint)| {
28948 let prev_index = std::mem::replace(&mut prev_index, index);
28949 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28950 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
28951 None
28952 } else {
28953 let chunk = &text[prev_index..]; // go to end of string
28954 Some(chunk)
28955 }
28956 })
28957}
28958
28959pub trait RangeToAnchorExt: Sized {
28960 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
28961
28962 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
28963 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
28964 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
28965 }
28966}
28967
28968impl<T: ToOffset> RangeToAnchorExt for Range<T> {
28969 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
28970 let start_offset = self.start.to_offset(snapshot);
28971 let end_offset = self.end.to_offset(snapshot);
28972 if start_offset == end_offset {
28973 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
28974 } else {
28975 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
28976 }
28977 }
28978}
28979
28980pub trait RowExt {
28981 fn as_f64(&self) -> f64;
28982
28983 fn next_row(&self) -> Self;
28984
28985 fn previous_row(&self) -> Self;
28986
28987 fn minus(&self, other: Self) -> u32;
28988}
28989
28990impl RowExt for DisplayRow {
28991 fn as_f64(&self) -> f64 {
28992 self.0 as _
28993 }
28994
28995 fn next_row(&self) -> Self {
28996 Self(self.0 + 1)
28997 }
28998
28999 fn previous_row(&self) -> Self {
29000 Self(self.0.saturating_sub(1))
29001 }
29002
29003 fn minus(&self, other: Self) -> u32 {
29004 self.0 - other.0
29005 }
29006}
29007
29008impl RowExt for MultiBufferRow {
29009 fn as_f64(&self) -> f64 {
29010 self.0 as _
29011 }
29012
29013 fn next_row(&self) -> Self {
29014 Self(self.0 + 1)
29015 }
29016
29017 fn previous_row(&self) -> Self {
29018 Self(self.0.saturating_sub(1))
29019 }
29020
29021 fn minus(&self, other: Self) -> u32 {
29022 self.0 - other.0
29023 }
29024}
29025
29026trait RowRangeExt {
29027 type Row;
29028
29029 fn len(&self) -> usize;
29030
29031 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
29032}
29033
29034impl RowRangeExt for Range<MultiBufferRow> {
29035 type Row = MultiBufferRow;
29036
29037 fn len(&self) -> usize {
29038 (self.end.0 - self.start.0) as usize
29039 }
29040
29041 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
29042 (self.start.0..self.end.0).map(MultiBufferRow)
29043 }
29044}
29045
29046impl RowRangeExt for Range<DisplayRow> {
29047 type Row = DisplayRow;
29048
29049 fn len(&self) -> usize {
29050 (self.end.0 - self.start.0) as usize
29051 }
29052
29053 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
29054 (self.start.0..self.end.0).map(DisplayRow)
29055 }
29056}
29057
29058/// If select range has more than one line, we
29059/// just point the cursor to range.start.
29060fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
29061 if range.start.row == range.end.row {
29062 range
29063 } else {
29064 range.start..range.start
29065 }
29066}
29067pub struct KillRing(ClipboardItem);
29068impl Global for KillRing {}
29069
29070const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
29071
29072enum BreakpointPromptEditAction {
29073 Log,
29074 Condition,
29075 HitCondition,
29076}
29077
29078struct BreakpointPromptEditor {
29079 pub(crate) prompt: Entity<Editor>,
29080 editor: WeakEntity<Editor>,
29081 breakpoint_anchor: Anchor,
29082 breakpoint: Breakpoint,
29083 edit_action: BreakpointPromptEditAction,
29084 block_ids: HashSet<CustomBlockId>,
29085 editor_margins: Arc<Mutex<EditorMargins>>,
29086 _subscriptions: Vec<Subscription>,
29087}
29088
29089impl BreakpointPromptEditor {
29090 const MAX_LINES: u8 = 4;
29091
29092 fn new(
29093 editor: WeakEntity<Editor>,
29094 breakpoint_anchor: Anchor,
29095 breakpoint: Breakpoint,
29096 edit_action: BreakpointPromptEditAction,
29097 window: &mut Window,
29098 cx: &mut Context<Self>,
29099 ) -> Self {
29100 let base_text = match edit_action {
29101 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
29102 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
29103 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
29104 }
29105 .map(|msg| msg.to_string())
29106 .unwrap_or_default();
29107
29108 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
29109 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
29110
29111 let prompt = cx.new(|cx| {
29112 let mut prompt = Editor::new(
29113 EditorMode::AutoHeight {
29114 min_lines: 1,
29115 max_lines: Some(Self::MAX_LINES as usize),
29116 },
29117 buffer,
29118 None,
29119 window,
29120 cx,
29121 );
29122 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
29123 prompt.set_show_cursor_when_unfocused(false, cx);
29124 prompt.set_placeholder_text(
29125 match edit_action {
29126 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
29127 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
29128 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
29129 },
29130 window,
29131 cx,
29132 );
29133
29134 prompt
29135 });
29136
29137 Self {
29138 prompt,
29139 editor,
29140 breakpoint_anchor,
29141 breakpoint,
29142 edit_action,
29143 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
29144 block_ids: Default::default(),
29145 _subscriptions: vec![],
29146 }
29147 }
29148
29149 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
29150 self.block_ids.extend(block_ids)
29151 }
29152
29153 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
29154 if let Some(editor) = self.editor.upgrade() {
29155 let message = self
29156 .prompt
29157 .read(cx)
29158 .buffer
29159 .read(cx)
29160 .as_singleton()
29161 .expect("A multi buffer in breakpoint prompt isn't possible")
29162 .read(cx)
29163 .as_rope()
29164 .to_string();
29165
29166 editor.update(cx, |editor, cx| {
29167 editor.edit_breakpoint_at_anchor(
29168 self.breakpoint_anchor,
29169 self.breakpoint.clone(),
29170 match self.edit_action {
29171 BreakpointPromptEditAction::Log => {
29172 BreakpointEditAction::EditLogMessage(message.into())
29173 }
29174 BreakpointPromptEditAction::Condition => {
29175 BreakpointEditAction::EditCondition(message.into())
29176 }
29177 BreakpointPromptEditAction::HitCondition => {
29178 BreakpointEditAction::EditHitCondition(message.into())
29179 }
29180 },
29181 cx,
29182 );
29183
29184 editor.remove_blocks(self.block_ids.clone(), None, cx);
29185 cx.focus_self(window);
29186 });
29187 }
29188 }
29189
29190 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
29191 self.editor
29192 .update(cx, |editor, cx| {
29193 editor.remove_blocks(self.block_ids.clone(), None, cx);
29194 window.focus(&editor.focus_handle, cx);
29195 })
29196 .log_err();
29197 }
29198
29199 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
29200 let settings = ThemeSettings::get_global(cx);
29201 let text_style = TextStyle {
29202 color: if self.prompt.read(cx).read_only(cx) {
29203 cx.theme().colors().text_disabled
29204 } else {
29205 cx.theme().colors().text
29206 },
29207 font_family: settings.buffer_font.family.clone(),
29208 font_fallbacks: settings.buffer_font.fallbacks.clone(),
29209 font_size: settings.buffer_font_size(cx).into(),
29210 font_weight: settings.buffer_font.weight,
29211 line_height: relative(settings.buffer_line_height.value()),
29212 ..Default::default()
29213 };
29214 EditorElement::new(
29215 &self.prompt,
29216 EditorStyle {
29217 background: cx.theme().colors().editor_background,
29218 local_player: cx.theme().players().local(),
29219 text: text_style,
29220 ..Default::default()
29221 },
29222 )
29223 }
29224
29225 fn render_close_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
29226 let focus_handle = self.prompt.focus_handle(cx);
29227 IconButton::new("cancel", IconName::Close)
29228 .icon_color(Color::Muted)
29229 .shape(IconButtonShape::Square)
29230 .tooltip(move |_window, cx| {
29231 Tooltip::for_action_in("Cancel", &menu::Cancel, &focus_handle, cx)
29232 })
29233 .on_click(cx.listener(|this, _, window, cx| {
29234 this.cancel(&menu::Cancel, window, cx);
29235 }))
29236 }
29237
29238 fn render_confirm_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
29239 let focus_handle = self.prompt.focus_handle(cx);
29240 IconButton::new("confirm", IconName::Return)
29241 .icon_color(Color::Muted)
29242 .shape(IconButtonShape::Square)
29243 .tooltip(move |_window, cx| {
29244 Tooltip::for_action_in("Confirm", &menu::Confirm, &focus_handle, cx)
29245 })
29246 .on_click(cx.listener(|this, _, window, cx| {
29247 this.confirm(&menu::Confirm, window, cx);
29248 }))
29249 }
29250}
29251
29252impl Render for BreakpointPromptEditor {
29253 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
29254 let ui_font_size = ThemeSettings::get_global(cx).ui_font_size(cx);
29255 let editor_margins = *self.editor_margins.lock();
29256 let gutter_dimensions = editor_margins.gutter;
29257 let left_gutter_width = gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0);
29258 let right_padding = editor_margins.right + px(9.);
29259 h_flex()
29260 .key_context("Editor")
29261 .bg(cx.theme().colors().editor_background)
29262 .border_y_1()
29263 .border_color(cx.theme().status().info_border)
29264 .size_full()
29265 .py(window.line_height() / 2.5)
29266 .pr(right_padding)
29267 .on_action(cx.listener(Self::confirm))
29268 .on_action(cx.listener(Self::cancel))
29269 .child(
29270 WithRemSize::new(ui_font_size)
29271 .h_full()
29272 .w(left_gutter_width)
29273 .flex()
29274 .flex_row()
29275 .flex_shrink_0()
29276 .items_center()
29277 .justify_center()
29278 .gap_1()
29279 .child(self.render_close_button(cx)),
29280 )
29281 .child(
29282 h_flex()
29283 .w_full()
29284 .justify_between()
29285 .child(div().flex_1().child(self.render_prompt_editor(cx)))
29286 .child(
29287 WithRemSize::new(ui_font_size)
29288 .flex()
29289 .flex_row()
29290 .items_center()
29291 .child(self.render_confirm_button(cx)),
29292 ),
29293 )
29294 }
29295}
29296
29297impl Focusable for BreakpointPromptEditor {
29298 fn focus_handle(&self, cx: &App) -> FocusHandle {
29299 self.prompt.focus_handle(cx)
29300 }
29301}
29302
29303fn all_edits_insertions_or_deletions(
29304 edits: &Vec<(Range<Anchor>, Arc<str>)>,
29305 snapshot: &MultiBufferSnapshot,
29306) -> bool {
29307 let mut all_insertions = true;
29308 let mut all_deletions = true;
29309
29310 for (range, new_text) in edits.iter() {
29311 let range_is_empty = range.to_offset(snapshot).is_empty();
29312 let text_is_empty = new_text.is_empty();
29313
29314 if range_is_empty != text_is_empty {
29315 if range_is_empty {
29316 all_deletions = false;
29317 } else {
29318 all_insertions = false;
29319 }
29320 } else {
29321 return false;
29322 }
29323
29324 if !all_insertions && !all_deletions {
29325 return false;
29326 }
29327 }
29328 all_insertions || all_deletions
29329}
29330
29331struct MissingEditPredictionKeybindingTooltip;
29332
29333impl Render for MissingEditPredictionKeybindingTooltip {
29334 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
29335 ui::tooltip_container(cx, |container, cx| {
29336 container
29337 .flex_shrink_0()
29338 .max_w_80()
29339 .min_h(rems_from_px(124.))
29340 .justify_between()
29341 .child(
29342 v_flex()
29343 .flex_1()
29344 .text_ui_sm(cx)
29345 .child(Label::new("Conflict with Accept Keybinding"))
29346 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
29347 )
29348 .child(
29349 h_flex()
29350 .pb_1()
29351 .gap_1()
29352 .items_end()
29353 .w_full()
29354 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
29355 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
29356 }))
29357 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
29358 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
29359 })),
29360 )
29361 })
29362 }
29363}
29364
29365#[derive(Debug, Clone, Copy, PartialEq)]
29366pub struct LineHighlight {
29367 pub background: Background,
29368 pub border: Option<gpui::Hsla>,
29369 pub include_gutter: bool,
29370 pub type_id: Option<TypeId>,
29371}
29372
29373struct LineManipulationResult {
29374 pub new_text: String,
29375 pub line_count_before: usize,
29376 pub line_count_after: usize,
29377}
29378
29379fn render_diff_hunk_controls(
29380 row: u32,
29381 status: &DiffHunkStatus,
29382 hunk_range: Range<Anchor>,
29383 is_created_file: bool,
29384 line_height: Pixels,
29385 editor: &Entity<Editor>,
29386 _window: &mut Window,
29387 cx: &mut App,
29388) -> AnyElement {
29389 h_flex()
29390 .h(line_height)
29391 .mr_1()
29392 .gap_1()
29393 .px_0p5()
29394 .pb_1()
29395 .border_x_1()
29396 .border_b_1()
29397 .border_color(cx.theme().colors().border_variant)
29398 .rounded_b_lg()
29399 .bg(cx.theme().colors().editor_background)
29400 .gap_1()
29401 .block_mouse_except_scroll()
29402 .shadow_md()
29403 .child(if status.has_secondary_hunk() {
29404 Button::new(("stage", row as u64), "Stage")
29405 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
29406 .tooltip({
29407 let focus_handle = editor.focus_handle(cx);
29408 move |_window, cx| {
29409 Tooltip::for_action_in(
29410 "Stage Hunk",
29411 &::git::ToggleStaged,
29412 &focus_handle,
29413 cx,
29414 )
29415 }
29416 })
29417 .on_click({
29418 let editor = editor.clone();
29419 move |_event, _window, cx| {
29420 editor.update(cx, |editor, cx| {
29421 editor.stage_or_unstage_diff_hunks(
29422 true,
29423 vec![hunk_range.start..hunk_range.start],
29424 cx,
29425 );
29426 });
29427 }
29428 })
29429 } else {
29430 Button::new(("unstage", row as u64), "Unstage")
29431 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
29432 .tooltip({
29433 let focus_handle = editor.focus_handle(cx);
29434 move |_window, cx| {
29435 Tooltip::for_action_in(
29436 "Unstage Hunk",
29437 &::git::ToggleStaged,
29438 &focus_handle,
29439 cx,
29440 )
29441 }
29442 })
29443 .on_click({
29444 let editor = editor.clone();
29445 move |_event, _window, cx| {
29446 editor.update(cx, |editor, cx| {
29447 editor.stage_or_unstage_diff_hunks(
29448 false,
29449 vec![hunk_range.start..hunk_range.start],
29450 cx,
29451 );
29452 });
29453 }
29454 })
29455 })
29456 .child(
29457 Button::new(("restore", row as u64), "Restore")
29458 .tooltip({
29459 let focus_handle = editor.focus_handle(cx);
29460 move |_window, cx| {
29461 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
29462 }
29463 })
29464 .on_click({
29465 let editor = editor.clone();
29466 move |_event, window, cx| {
29467 editor.update(cx, |editor, cx| {
29468 let snapshot = editor.snapshot(window, cx);
29469 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
29470 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
29471 });
29472 }
29473 })
29474 .disabled(is_created_file),
29475 )
29476 .when(
29477 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
29478 |el| {
29479 el.child(
29480 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
29481 .shape(IconButtonShape::Square)
29482 .icon_size(IconSize::Small)
29483 // .disabled(!has_multiple_hunks)
29484 .tooltip({
29485 let focus_handle = editor.focus_handle(cx);
29486 move |_window, cx| {
29487 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
29488 }
29489 })
29490 .on_click({
29491 let editor = editor.clone();
29492 move |_event, window, cx| {
29493 editor.update(cx, |editor, cx| {
29494 let snapshot = editor.snapshot(window, cx);
29495 let position =
29496 hunk_range.end.to_point(&snapshot.buffer_snapshot());
29497 editor.go_to_hunk_before_or_after_position(
29498 &snapshot,
29499 position,
29500 Direction::Next,
29501 true,
29502 window,
29503 cx,
29504 );
29505 editor.expand_selected_diff_hunks(cx);
29506 });
29507 }
29508 }),
29509 )
29510 .child(
29511 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
29512 .shape(IconButtonShape::Square)
29513 .icon_size(IconSize::Small)
29514 // .disabled(!has_multiple_hunks)
29515 .tooltip({
29516 let focus_handle = editor.focus_handle(cx);
29517 move |_window, cx| {
29518 Tooltip::for_action_in(
29519 "Previous Hunk",
29520 &GoToPreviousHunk,
29521 &focus_handle,
29522 cx,
29523 )
29524 }
29525 })
29526 .on_click({
29527 let editor = editor.clone();
29528 move |_event, window, cx| {
29529 editor.update(cx, |editor, cx| {
29530 let snapshot = editor.snapshot(window, cx);
29531 let point =
29532 hunk_range.start.to_point(&snapshot.buffer_snapshot());
29533 editor.go_to_hunk_before_or_after_position(
29534 &snapshot,
29535 point,
29536 Direction::Prev,
29537 true,
29538 window,
29539 cx,
29540 );
29541 editor.expand_selected_diff_hunks(cx);
29542 });
29543 }
29544 }),
29545 )
29546 },
29547 )
29548 .into_any_element()
29549}
29550
29551pub fn multibuffer_context_lines(cx: &App) -> u32 {
29552 EditorSettings::try_get(cx)
29553 .map(|settings| settings.excerpt_context_lines)
29554 .unwrap_or(2)
29555 .min(32)
29556}