1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//!
11//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
12//!
13//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
14pub mod actions;
15pub mod blink_manager;
16mod bracket_colorization;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod document_colors;
21mod document_symbols;
22mod editor_settings;
23mod element;
24mod folding_ranges;
25mod git;
26mod highlight_matching_bracket;
27mod hover_links;
28pub mod hover_popover;
29mod indent_guides;
30mod inlays;
31pub mod items;
32mod jsx_tag_auto_close;
33mod linked_editing_ranges;
34mod lsp_ext;
35mod mouse_context_menu;
36pub mod movement;
37mod persistence;
38mod runnables;
39mod rust_analyzer_ext;
40pub mod scroll;
41mod selections_collection;
42pub mod semantic_tokens;
43mod split;
44pub mod split_editor_view;
45
46#[cfg(test)]
47mod code_completion_tests;
48#[cfg(test)]
49mod edit_prediction_tests;
50#[cfg(test)]
51mod editor_tests;
52mod signature_help;
53#[cfg(any(test, feature = "test-support"))]
54pub mod test;
55
56pub(crate) use actions::*;
57pub use display_map::{
58 ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder, HighlightKey,
59 SemanticTokenHighlight,
60};
61pub use edit_prediction_types::Direction;
62pub use editor_settings::{
63 CompletionDetailAlignment, CurrentLineHighlight, DiffViewStyle, DocumentColorsRenderMode,
64 EditorSettings, HideMouseMode, ScrollBeyondLastLine, ScrollbarAxes, SearchSettings,
65 ShowMinimap,
66};
67pub use element::{
68 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
69 render_breadcrumb_text,
70};
71pub use git::blame::BlameRenderer;
72pub use hover_popover::hover_markdown_style;
73pub use inlays::Inlay;
74pub use items::MAX_TAB_TITLE_LEN;
75pub use linked_editing_ranges::LinkedEdits;
76pub use lsp::CompletionContext;
77pub use lsp_ext::lsp_tasks;
78pub use multi_buffer::{
79 Anchor, AnchorRangeExt, BufferOffset, ExcerptId, ExcerptRange, MBTextSummary, MultiBuffer,
80 MultiBufferOffset, MultiBufferOffsetUtf16, MultiBufferSnapshot, PathKey, RowInfo, ToOffset,
81 ToPoint,
82};
83pub use split::{SplittableEditor, ToggleSplitDiff};
84pub use split_editor_view::SplitEditorView;
85pub use text::Bias;
86
87use ::git::{Restore, blame::BlameEntry, commit::ParsedCommitMessage, status::FileStatus};
88use aho_corasick::{AhoCorasick, AhoCorasickBuilder, BuildError};
89use anyhow::{Context as _, Result, anyhow, bail};
90use blink_manager::BlinkManager;
91use buffer_diff::DiffHunkStatus;
92use client::{Collaborator, ParticipantIndex, parse_zed_link};
93use clock::ReplicaId;
94use code_context_menus::{
95 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
96 CompletionsMenu, ContextMenuOrigin,
97};
98use collections::{BTreeMap, HashMap, HashSet, VecDeque};
99use convert_case::{Case, Casing};
100use dap::TelemetrySpawnLocation;
101use display_map::*;
102use document_colors::LspColorData;
103use edit_prediction_types::{
104 EditPredictionDelegate, EditPredictionDelegateHandle, EditPredictionDiscardReason,
105 EditPredictionGranularity, SuggestionDisplayType,
106};
107use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
108use element::{LineWithInvisibles, PositionMap, layout_line};
109use futures::{
110 FutureExt,
111 future::{self, Shared, join},
112};
113use fuzzy::{StringMatch, StringMatchCandidate};
114use git::blame::{GitBlame, GlobalBlameRenderer};
115use gpui::{
116 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
117 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
118 DispatchPhase, Edges, Entity, EntityId, EntityInputHandler, EventEmitter, FocusHandle,
119 FocusOutEvent, Focusable, FontId, FontStyle, FontWeight, Global, HighlightStyle, Hsla,
120 KeyContext, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, PaintQuad, ParentElement,
121 Pixels, PressureStage, Render, ScrollHandle, SharedString, SharedUri, Size, Stateful, Styled,
122 Subscription, Task, TextRun, TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle,
123 UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window, div, point, prelude::*,
124 pulsating_between, px, relative, size,
125};
126use hover_links::{HoverLink, HoveredLinkState, find_file};
127use hover_popover::{HoverState, hide_hover};
128use indent_guides::ActiveIndentGuidesState;
129use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
130use itertools::{Either, Itertools};
131use language::{
132 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
133 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
134 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
135 IndentSize, Language, LanguageName, LanguageRegistry, LanguageScope, LocalFile, OffsetRangeExt,
136 OutlineItem, Point, Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions,
137 WordsQuery,
138 language_settings::{
139 self, AllLanguageSettings, LanguageSettings, LspInsertMode, RewrapBehavior,
140 WordsCompletionMode, all_language_settings,
141 },
142 point_from_lsp, point_to_lsp, text_diff_with_options,
143};
144use linked_editing_ranges::refresh_linked_ranges;
145use lsp::{
146 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
147 LanguageServerId,
148};
149use markdown::Markdown;
150use mouse_context_menu::MouseContextMenu;
151use movement::TextLayoutDetails;
152use multi_buffer::{
153 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
154};
155use parking_lot::Mutex;
156use persistence::EditorDb;
157use project::{
158 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
159 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
160 InvalidationStrategy, Location, LocationLink, LspAction, PrepareRenameResponse, Project,
161 ProjectItem, ProjectPath, ProjectTransaction,
162 debugger::{
163 breakpoint_store::{
164 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
165 BreakpointStore, BreakpointStoreEvent,
166 },
167 session::{Session, SessionEvent},
168 },
169 git_store::GitStoreEvent,
170 lsp_store::{
171 BufferSemanticTokens, CacheInlayHints, CompletionDocumentation, FormatTrigger,
172 LspFormatTarget, OpenLspBufferHandle, RefreshForServer,
173 },
174 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
175};
176use rand::seq::SliceRandom;
177use regex::Regex;
178use rpc::{ErrorCode, ErrorExt, proto::PeerId};
179use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, SharedScrollAnchor};
180use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
181use serde::{Deserialize, Serialize};
182use settings::{
183 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
184 update_settings_file,
185};
186use smallvec::{SmallVec, smallvec};
187use snippet::Snippet;
188use std::{
189 any::{Any, TypeId},
190 borrow::Cow,
191 cell::{OnceCell, RefCell},
192 cmp::{self, Ordering, Reverse},
193 collections::hash_map,
194 iter::{self, Peekable},
195 mem,
196 num::NonZeroU32,
197 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
198 path::{Path, PathBuf},
199 rc::Rc,
200 sync::Arc,
201 time::{Duration, Instant},
202};
203use task::TaskVariables;
204use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _, ToPoint as _};
205use theme::{
206 AccentColors, ActiveTheme, GlobalTheme, PlayerColor, StatusColors, SyntaxTheme, Theme,
207 ThemeSettings, observe_buffer_font_size_adjustment,
208};
209use ui::{
210 Avatar, ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape,
211 IconName, IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
212 utils::WithRemSize,
213};
214use ui_input::ErasedEditor;
215use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
216use workspace::{
217 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, NavigationEntry, OpenInTerminal,
218 OpenTerminal, Pane, RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection,
219 TabBarSettings, Toast, ViewId, Workspace, WorkspaceId, WorkspaceSettings,
220 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
221 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
222 searchable::SearchEvent,
223};
224pub use zed_actions::editor::RevealInFileManager;
225use zed_actions::editor::{MoveDown, MoveUp};
226
227use crate::{
228 code_context_menus::CompletionsMenuSource,
229 editor_settings::MultiCursorModifier,
230 hover_links::{find_url, find_url_from_range},
231 inlays::{
232 InlineValueCache,
233 inlay_hints::{LspInlayHintData, inlay_hint_settings},
234 },
235 runnables::{ResolvedTasks, RunnableData, RunnableTasks},
236 scroll::{ScrollOffset, ScrollPixelOffset},
237 selections_collection::resolve_selections_wrapping_blocks,
238 semantic_tokens::SemanticTokenState,
239 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
240};
241
242pub const FILE_HEADER_HEIGHT: u32 = 2;
243pub const BUFFER_HEADER_PADDING: Rems = rems(0.25);
244pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
245const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
246const MAX_LINE_LEN: usize = 1024;
247const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
248const MAX_SELECTION_HISTORY_LEN: usize = 1024;
249pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
250#[doc(hidden)]
251pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
252pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
253
254pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
255pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
256pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
257pub const LSP_REQUEST_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(50);
258
259pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
260pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
261
262pub type RenderDiffHunkControlsFn = Arc<
263 dyn Fn(
264 u32,
265 &DiffHunkStatus,
266 Range<Anchor>,
267 bool,
268 Pixels,
269 &Entity<Editor>,
270 &mut Window,
271 &mut App,
272 ) -> AnyElement,
273>;
274
275enum ReportEditorEvent {
276 Saved { auto_saved: bool },
277 EditorOpened,
278 Closed,
279}
280
281impl ReportEditorEvent {
282 pub fn event_type(&self) -> &'static str {
283 match self {
284 Self::Saved { .. } => "Editor Saved",
285 Self::EditorOpened => "Editor Opened",
286 Self::Closed => "Editor Closed",
287 }
288 }
289}
290
291pub enum ActiveDebugLine {}
292pub enum DebugStackFrameLine {}
293
294pub enum ConflictsOuter {}
295pub enum ConflictsOurs {}
296pub enum ConflictsTheirs {}
297pub enum ConflictsOursMarker {}
298pub enum ConflictsTheirsMarker {}
299
300pub struct HunkAddedColor;
301pub struct HunkRemovedColor;
302
303#[derive(Debug, Copy, Clone, PartialEq, Eq)]
304pub enum Navigated {
305 Yes,
306 No,
307}
308
309impl Navigated {
310 pub fn from_bool(yes: bool) -> Navigated {
311 if yes { Navigated::Yes } else { Navigated::No }
312 }
313}
314
315#[derive(Debug, Clone, PartialEq, Eq)]
316enum DisplayDiffHunk {
317 Folded {
318 display_row: DisplayRow,
319 },
320 Unfolded {
321 is_created_file: bool,
322 diff_base_byte_range: Range<usize>,
323 display_row_range: Range<DisplayRow>,
324 multi_buffer_range: Range<Anchor>,
325 status: DiffHunkStatus,
326 word_diffs: Vec<Range<MultiBufferOffset>>,
327 },
328}
329
330pub enum HideMouseCursorOrigin {
331 TypingAction,
332 MovementAction,
333}
334
335pub fn init(cx: &mut App) {
336 cx.set_global(GlobalBlameRenderer(Arc::new(())));
337 cx.set_global(breadcrumbs::RenderBreadcrumbText(render_breadcrumb_text));
338
339 workspace::register_project_item::<Editor>(cx);
340 workspace::FollowableViewRegistry::register::<Editor>(cx);
341 workspace::register_serializable_item::<Editor>(cx);
342
343 cx.observe_new(
344 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
345 workspace.register_action(Editor::new_file);
346 workspace.register_action(Editor::new_file_split);
347 workspace.register_action(Editor::new_file_vertical);
348 workspace.register_action(Editor::new_file_horizontal);
349 workspace.register_action(Editor::cancel_language_server_work);
350 workspace.register_action(Editor::toggle_focus);
351 },
352 )
353 .detach();
354
355 cx.on_action(move |_: &workspace::NewFile, cx| {
356 let app_state = workspace::AppState::global(cx);
357 if let Some(app_state) = app_state.upgrade() {
358 workspace::open_new(
359 Default::default(),
360 app_state,
361 cx,
362 |workspace, window, cx| {
363 Editor::new_file(workspace, &Default::default(), window, cx)
364 },
365 )
366 .detach_and_log_err(cx);
367 }
368 })
369 .on_action(move |_: &workspace::NewWindow, cx| {
370 let app_state = workspace::AppState::global(cx);
371 if let Some(app_state) = app_state.upgrade() {
372 workspace::open_new(
373 Default::default(),
374 app_state,
375 cx,
376 |workspace, window, cx| {
377 cx.activate(true);
378 Editor::new_file(workspace, &Default::default(), window, cx)
379 },
380 )
381 .detach_and_log_err(cx);
382 }
383 });
384 _ = ui_input::ERASED_EDITOR_FACTORY.set(|window, cx| {
385 Arc::new(ErasedEditorImpl(
386 cx.new(|cx| Editor::single_line(window, cx)),
387 )) as Arc<dyn ErasedEditor>
388 });
389 _ = multi_buffer::EXCERPT_CONTEXT_LINES.set(multibuffer_context_lines);
390}
391
392pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
393 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
394}
395
396pub trait DiagnosticRenderer {
397 fn render_group(
398 &self,
399 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
400 buffer_id: BufferId,
401 snapshot: EditorSnapshot,
402 editor: WeakEntity<Editor>,
403 language_registry: Option<Arc<LanguageRegistry>>,
404 cx: &mut App,
405 ) -> Vec<BlockProperties<Anchor>>;
406
407 fn render_hover(
408 &self,
409 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
410 range: Range<Point>,
411 buffer_id: BufferId,
412 language_registry: Option<Arc<LanguageRegistry>>,
413 cx: &mut App,
414 ) -> Option<Entity<markdown::Markdown>>;
415
416 fn open_link(
417 &self,
418 editor: &mut Editor,
419 link: SharedString,
420 window: &mut Window,
421 cx: &mut Context<Editor>,
422 );
423}
424
425pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
426
427impl GlobalDiagnosticRenderer {
428 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
429 cx.try_global::<Self>().map(|g| g.0.clone())
430 }
431}
432
433impl gpui::Global for GlobalDiagnosticRenderer {}
434pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
435 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
436}
437
438pub struct SearchWithinRange;
439
440trait InvalidationRegion {
441 fn ranges(&self) -> &[Range<Anchor>];
442}
443
444#[derive(Clone, Debug, PartialEq)]
445pub enum SelectPhase {
446 Begin {
447 position: DisplayPoint,
448 add: bool,
449 click_count: usize,
450 },
451 BeginColumnar {
452 position: DisplayPoint,
453 reset: bool,
454 mode: ColumnarMode,
455 goal_column: u32,
456 },
457 Extend {
458 position: DisplayPoint,
459 click_count: usize,
460 },
461 Update {
462 position: DisplayPoint,
463 goal_column: u32,
464 scroll_delta: gpui::Point<f32>,
465 },
466 End,
467}
468
469#[derive(Clone, Debug, PartialEq)]
470pub enum ColumnarMode {
471 FromMouse,
472 FromSelection,
473}
474
475#[derive(Clone, Debug)]
476pub enum SelectMode {
477 Character,
478 Word(Range<Anchor>),
479 Line(Range<Anchor>),
480 All,
481}
482
483#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
484pub enum SizingBehavior {
485 /// The editor will layout itself using `size_full` and will include the vertical
486 /// scroll margin as requested by user settings.
487 #[default]
488 Default,
489 /// The editor will layout itself using `size_full`, but will not have any
490 /// vertical overscroll.
491 ExcludeOverscrollMargin,
492 /// The editor will request a vertical size according to its content and will be
493 /// layouted without a vertical scroll margin.
494 SizeByContent,
495}
496
497#[derive(Clone, PartialEq, Eq, Debug)]
498pub enum EditorMode {
499 SingleLine,
500 AutoHeight {
501 min_lines: usize,
502 max_lines: Option<usize>,
503 },
504 Full {
505 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
506 scale_ui_elements_with_buffer_font_size: bool,
507 /// When set to `true`, the editor will render a background for the active line.
508 show_active_line_background: bool,
509 /// Determines the sizing behavior for this editor
510 sizing_behavior: SizingBehavior,
511 },
512 Minimap {
513 parent: WeakEntity<Editor>,
514 },
515}
516
517impl EditorMode {
518 pub fn full() -> Self {
519 Self::Full {
520 scale_ui_elements_with_buffer_font_size: true,
521 show_active_line_background: true,
522 sizing_behavior: SizingBehavior::Default,
523 }
524 }
525
526 #[inline]
527 pub fn is_full(&self) -> bool {
528 matches!(self, Self::Full { .. })
529 }
530
531 #[inline]
532 pub fn is_single_line(&self) -> bool {
533 matches!(self, Self::SingleLine { .. })
534 }
535
536 #[inline]
537 fn is_minimap(&self) -> bool {
538 matches!(self, Self::Minimap { .. })
539 }
540}
541
542#[derive(Copy, Clone, Debug)]
543pub enum SoftWrap {
544 /// Prefer not to wrap at all.
545 ///
546 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
547 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
548 GitDiff,
549 /// Prefer a single line generally, unless an overly long line is encountered.
550 None,
551 /// Soft wrap lines that exceed the editor width.
552 EditorWidth,
553 /// Soft wrap lines at the preferred line length.
554 Column(u32),
555 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
556 Bounded(u32),
557}
558
559#[derive(Clone)]
560pub struct EditorStyle {
561 pub background: Hsla,
562 pub border: Hsla,
563 pub local_player: PlayerColor,
564 pub text: TextStyle,
565 pub scrollbar_width: Pixels,
566 pub syntax: Arc<SyntaxTheme>,
567 pub status: StatusColors,
568 pub inlay_hints_style: HighlightStyle,
569 pub edit_prediction_styles: EditPredictionStyles,
570 pub unnecessary_code_fade: f32,
571 pub show_underlines: bool,
572}
573
574impl Default for EditorStyle {
575 fn default() -> Self {
576 Self {
577 background: Hsla::default(),
578 border: Hsla::default(),
579 local_player: PlayerColor::default(),
580 text: TextStyle::default(),
581 scrollbar_width: Pixels::default(),
582 syntax: Default::default(),
583 // HACK: Status colors don't have a real default.
584 // We should look into removing the status colors from the editor
585 // style and retrieve them directly from the theme.
586 status: StatusColors::dark(),
587 inlay_hints_style: HighlightStyle::default(),
588 edit_prediction_styles: EditPredictionStyles {
589 insertion: HighlightStyle::default(),
590 whitespace: HighlightStyle::default(),
591 },
592 unnecessary_code_fade: Default::default(),
593 show_underlines: true,
594 }
595 }
596}
597
598pub fn make_inlay_hints_style(cx: &App) -> HighlightStyle {
599 let show_background = AllLanguageSettings::get_global(cx)
600 .defaults
601 .inlay_hints
602 .show_background;
603
604 let mut style = cx.theme().syntax().get("hint");
605
606 if style.color.is_none() {
607 style.color = Some(cx.theme().status().hint);
608 }
609
610 if !show_background {
611 style.background_color = None;
612 return style;
613 }
614
615 if style.background_color.is_none() {
616 style.background_color = Some(cx.theme().status().hint_background);
617 }
618
619 style
620}
621
622pub fn make_suggestion_styles(cx: &App) -> EditPredictionStyles {
623 EditPredictionStyles {
624 insertion: HighlightStyle {
625 color: Some(cx.theme().status().predictive),
626 ..HighlightStyle::default()
627 },
628 whitespace: HighlightStyle {
629 background_color: Some(cx.theme().status().created_background),
630 ..HighlightStyle::default()
631 },
632 }
633}
634
635type CompletionId = usize;
636
637pub(crate) enum EditDisplayMode {
638 TabAccept,
639 DiffPopover,
640 Inline,
641}
642
643enum EditPrediction {
644 Edit {
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 _: &ExcerptInfo,
893 _: &Window,
894 _: &App,
895 ) -> Option<AnyElement> {
896 None
897 }
898
899 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
900 None
901 }
902
903 fn to_any(&self) -> &dyn std::any::Any;
904
905 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
906 None
907 }
908}
909
910struct ChangeLocation {
911 current: Option<Vec<Anchor>>,
912 original: Vec<Anchor>,
913}
914impl ChangeLocation {
915 fn locations(&self) -> &[Anchor] {
916 self.current.as_ref().unwrap_or(&self.original)
917 }
918}
919
920/// A set of caret positions, registered when the editor was edited.
921pub struct ChangeList {
922 changes: Vec<ChangeLocation>,
923 /// Currently "selected" change.
924 position: Option<usize>,
925}
926
927impl ChangeList {
928 pub fn new() -> Self {
929 Self {
930 changes: Vec::new(),
931 position: None,
932 }
933 }
934
935 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
936 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
937 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
938 if self.changes.is_empty() {
939 return None;
940 }
941
942 let prev = self.position.unwrap_or(self.changes.len());
943 let next = if direction == Direction::Prev {
944 prev.saturating_sub(count)
945 } else {
946 (prev + count).min(self.changes.len() - 1)
947 };
948 self.position = Some(next);
949 self.changes.get(next).map(|change| change.locations())
950 }
951
952 /// Adds a new change to the list, resetting the change list position.
953 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
954 self.position.take();
955 if let Some(last) = self.changes.last_mut()
956 && group
957 {
958 last.current = Some(new_positions)
959 } else {
960 self.changes.push(ChangeLocation {
961 original: new_positions,
962 current: None,
963 });
964 }
965 }
966
967 pub fn last(&self) -> Option<&[Anchor]> {
968 self.changes.last().map(|change| change.locations())
969 }
970
971 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
972 self.changes.last().map(|change| change.original.as_slice())
973 }
974
975 pub fn invert_last_group(&mut self) {
976 if let Some(last) = self.changes.last_mut()
977 && let Some(current) = last.current.as_mut()
978 {
979 mem::swap(&mut last.original, current);
980 }
981 }
982}
983
984#[derive(Clone)]
985struct InlineBlamePopoverState {
986 scroll_handle: ScrollHandle,
987 commit_message: Option<ParsedCommitMessage>,
988 markdown: Entity<Markdown>,
989}
990
991struct InlineBlamePopover {
992 position: gpui::Point<Pixels>,
993 hide_task: Option<Task<()>>,
994 popover_bounds: Option<Bounds<Pixels>>,
995 popover_state: InlineBlamePopoverState,
996 keyboard_grace: bool,
997}
998
999enum SelectionDragState {
1000 /// State when no drag related activity is detected.
1001 None,
1002 /// State when the mouse is down on a selection that is about to be dragged.
1003 ReadyToDrag {
1004 selection: Selection<Anchor>,
1005 click_position: gpui::Point<Pixels>,
1006 mouse_down_time: Instant,
1007 },
1008 /// State when the mouse is dragging the selection in the editor.
1009 Dragging {
1010 selection: Selection<Anchor>,
1011 drop_cursor: Selection<Anchor>,
1012 hide_drop_cursor: bool,
1013 },
1014}
1015
1016enum ColumnarSelectionState {
1017 FromMouse {
1018 selection_tail: Anchor,
1019 display_point: Option<DisplayPoint>,
1020 },
1021 FromSelection {
1022 selection_tail: Anchor,
1023 },
1024}
1025
1026/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1027/// a breakpoint on them.
1028#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1029struct PhantomBreakpointIndicator {
1030 display_row: DisplayRow,
1031 /// There's a small debounce between hovering over the line and showing the indicator.
1032 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1033 is_active: bool,
1034 collides_with_existing_breakpoint: bool,
1035}
1036
1037/// Represents a diff review button indicator that shows up when hovering over lines in the gutter
1038/// in diff view mode.
1039#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1040pub(crate) struct PhantomDiffReviewIndicator {
1041 /// The starting anchor of the selection (or the only row if not dragging).
1042 pub start: Anchor,
1043 /// The ending anchor of the selection. Equal to start_anchor for single-line selection.
1044 pub end: Anchor,
1045 /// There's a small debounce between hovering over the line and showing the indicator.
1046 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1047 pub is_active: bool,
1048}
1049
1050#[derive(Clone, Debug)]
1051pub(crate) struct DiffReviewDragState {
1052 pub start_anchor: Anchor,
1053 pub current_anchor: Anchor,
1054}
1055
1056impl DiffReviewDragState {
1057 pub fn row_range(&self, snapshot: &DisplaySnapshot) -> std::ops::RangeInclusive<DisplayRow> {
1058 let start = self.start_anchor.to_display_point(snapshot).row();
1059 let current = self.current_anchor.to_display_point(snapshot).row();
1060
1061 (start..=current).sorted()
1062 }
1063}
1064
1065/// Identifies a specific hunk in the diff buffer.
1066/// Used as a key to group comments by their location.
1067#[derive(Clone, Debug)]
1068pub struct DiffHunkKey {
1069 /// The file path (relative to worktree) this hunk belongs to.
1070 pub file_path: Arc<util::rel_path::RelPath>,
1071 /// An anchor at the start of the hunk. This tracks position as the buffer changes.
1072 pub hunk_start_anchor: Anchor,
1073}
1074
1075/// A review comment stored locally before being sent to the Agent panel.
1076#[derive(Clone)]
1077pub struct StoredReviewComment {
1078 /// Unique identifier for this comment (for edit/delete operations).
1079 pub id: usize,
1080 /// The comment text entered by the user.
1081 pub comment: String,
1082 /// Anchors for the code range being reviewed.
1083 pub range: Range<Anchor>,
1084 /// Timestamp when the comment was created (for chronological ordering).
1085 pub created_at: Instant,
1086 /// Whether this comment is currently being edited inline.
1087 pub is_editing: bool,
1088}
1089
1090impl StoredReviewComment {
1091 pub fn new(id: usize, comment: String, anchor_range: Range<Anchor>) -> Self {
1092 Self {
1093 id,
1094 comment,
1095 range: anchor_range,
1096 created_at: Instant::now(),
1097 is_editing: false,
1098 }
1099 }
1100}
1101
1102/// Represents an active diff review overlay that appears when clicking the "Add Review" button.
1103pub(crate) struct DiffReviewOverlay {
1104 pub anchor_range: Range<Anchor>,
1105 /// The block ID for the overlay.
1106 pub block_id: CustomBlockId,
1107 /// The editor entity for the review input.
1108 pub prompt_editor: Entity<Editor>,
1109 /// The hunk key this overlay belongs to.
1110 pub hunk_key: DiffHunkKey,
1111 /// Whether the comments section is expanded.
1112 pub comments_expanded: bool,
1113 /// Editors for comments currently being edited inline.
1114 /// Key: comment ID, Value: Editor entity for inline editing.
1115 pub inline_edit_editors: HashMap<usize, Entity<Editor>>,
1116 /// Subscriptions for inline edit editors' action handlers.
1117 /// Key: comment ID, Value: Subscription keeping the Newline action handler alive.
1118 pub inline_edit_subscriptions: HashMap<usize, Subscription>,
1119 /// The current user's avatar URI for display in comment rows.
1120 pub user_avatar_uri: Option<SharedUri>,
1121 /// Subscription to keep the action handler alive.
1122 _subscription: Subscription,
1123}
1124
1125/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1126///
1127/// See the [module level documentation](self) for more information.
1128pub struct Editor {
1129 focus_handle: FocusHandle,
1130 last_focused_descendant: Option<WeakFocusHandle>,
1131 /// The text buffer being edited
1132 buffer: Entity<MultiBuffer>,
1133 /// Map of how text in the buffer should be displayed.
1134 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1135 pub display_map: Entity<DisplayMap>,
1136 placeholder_display_map: Option<Entity<DisplayMap>>,
1137 pub selections: SelectionsCollection,
1138 pub scroll_manager: ScrollManager,
1139 /// When inline assist editors are linked, they all render cursors because
1140 /// typing enters text into each of them, even the ones that aren't focused.
1141 pub(crate) show_cursor_when_unfocused: bool,
1142 columnar_selection_state: Option<ColumnarSelectionState>,
1143 add_selections_state: Option<AddSelectionsState>,
1144 select_next_state: Option<SelectNextState>,
1145 select_prev_state: Option<SelectNextState>,
1146 selection_history: SelectionHistory,
1147 defer_selection_effects: bool,
1148 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1149 autoclose_regions: Vec<AutocloseRegion>,
1150 snippet_stack: InvalidationStack<SnippetState>,
1151 select_syntax_node_history: SelectSyntaxNodeHistory,
1152 ime_transaction: Option<TransactionId>,
1153 pub diagnostics_max_severity: DiagnosticSeverity,
1154 active_diagnostics: ActiveDiagnostic,
1155 show_inline_diagnostics: bool,
1156 inline_diagnostics_update: Task<()>,
1157 inline_diagnostics_enabled: bool,
1158 diagnostics_enabled: bool,
1159 word_completions_enabled: bool,
1160 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1161 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1162 hard_wrap: Option<usize>,
1163 project: Option<Entity<Project>>,
1164 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1165 completion_provider: Option<Rc<dyn CompletionProvider>>,
1166 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1167 blink_manager: Entity<BlinkManager>,
1168 show_cursor_names: bool,
1169 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1170 pub show_local_selections: bool,
1171 mode: EditorMode,
1172 show_breadcrumbs: bool,
1173 show_gutter: bool,
1174 show_scrollbars: ScrollbarAxes,
1175 minimap_visibility: MinimapVisibility,
1176 offset_content: bool,
1177 disable_expand_excerpt_buttons: bool,
1178 delegate_expand_excerpts: bool,
1179 delegate_stage_and_restore: bool,
1180 delegate_open_excerpts: bool,
1181 enable_lsp_data: bool,
1182 enable_runnables: bool,
1183 show_line_numbers: Option<bool>,
1184 use_relative_line_numbers: Option<bool>,
1185 show_git_diff_gutter: Option<bool>,
1186 show_code_actions: Option<bool>,
1187 show_runnables: Option<bool>,
1188 show_breakpoints: Option<bool>,
1189 show_diff_review_button: bool,
1190 show_wrap_guides: Option<bool>,
1191 show_indent_guides: Option<bool>,
1192 buffers_with_disabled_indent_guides: HashSet<BufferId>,
1193 highlight_order: usize,
1194 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1195 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1196 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1197 scrollbar_marker_state: ScrollbarMarkerState,
1198 active_indent_guides_state: ActiveIndentGuidesState,
1199 nav_history: Option<ItemNavHistory>,
1200 context_menu: RefCell<Option<CodeContextMenu>>,
1201 context_menu_options: Option<ContextMenuOptions>,
1202 mouse_context_menu: Option<MouseContextMenu>,
1203 completion_tasks: Vec<(CompletionId, Task<()>)>,
1204 inline_blame_popover: Option<InlineBlamePopover>,
1205 inline_blame_popover_show_task: Option<Task<()>>,
1206 signature_help_state: SignatureHelpState,
1207 auto_signature_help: Option<bool>,
1208 find_all_references_task_sources: Vec<Anchor>,
1209 next_completion_id: CompletionId,
1210 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1211 code_actions_task: Option<Task<Result<()>>>,
1212 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1213 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1214 debounced_selection_highlight_complete: bool,
1215 document_highlights_task: Option<Task<()>>,
1216 linked_editing_range_task: Option<Task<Option<()>>>,
1217 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1218 pending_rename: Option<RenameState>,
1219 searchable: bool,
1220 cursor_shape: CursorShape,
1221 /// Whether the cursor is offset one character to the left when something is
1222 /// selected (needed for vim visual mode)
1223 cursor_offset_on_selection: bool,
1224 current_line_highlight: Option<CurrentLineHighlight>,
1225 /// Whether to collapse search match ranges to just their start position.
1226 /// When true, navigating to a match positions the cursor at the match
1227 /// without selecting the matched text.
1228 collapse_matches: bool,
1229 autoindent_mode: Option<AutoindentMode>,
1230 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1231 input_enabled: bool,
1232 expects_character_input: bool,
1233 use_modal_editing: bool,
1234 read_only: bool,
1235 leader_id: Option<CollaboratorId>,
1236 remote_id: Option<ViewId>,
1237 pub hover_state: HoverState,
1238 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1239 prev_pressure_stage: Option<PressureStage>,
1240 gutter_hovered: bool,
1241 hovered_link_state: Option<HoveredLinkState>,
1242 edit_prediction_provider: Option<RegisteredEditPredictionDelegate>,
1243 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1244 active_edit_prediction: Option<EditPredictionState>,
1245 /// Used to prevent flickering as the user types while the menu is open
1246 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1247 edit_prediction_settings: EditPredictionSettings,
1248 edit_predictions_hidden_for_vim_mode: bool,
1249 show_edit_predictions_override: Option<bool>,
1250 show_completions_on_input_override: Option<bool>,
1251 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1252 edit_prediction_preview: EditPredictionPreview,
1253 in_leading_whitespace: bool,
1254 next_inlay_id: usize,
1255 next_color_inlay_id: usize,
1256 _subscriptions: Vec<Subscription>,
1257 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1258 gutter_dimensions: GutterDimensions,
1259 style: Option<EditorStyle>,
1260 text_style_refinement: Option<TextStyleRefinement>,
1261 next_editor_action_id: EditorActionId,
1262 editor_actions: Rc<
1263 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1264 >,
1265 use_autoclose: bool,
1266 use_auto_surround: bool,
1267 auto_replace_emoji_shortcode: bool,
1268 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1269 show_git_blame_gutter: bool,
1270 show_git_blame_inline: bool,
1271 show_git_blame_inline_delay_task: Option<Task<()>>,
1272 git_blame_inline_enabled: bool,
1273 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1274 buffer_serialization: Option<BufferSerialization>,
1275 show_selection_menu: Option<bool>,
1276 blame: Option<Entity<GitBlame>>,
1277 blame_subscription: Option<Subscription>,
1278 custom_context_menu: Option<
1279 Box<
1280 dyn 'static
1281 + Fn(
1282 &mut Self,
1283 DisplayPoint,
1284 &mut Window,
1285 &mut Context<Self>,
1286 ) -> Option<Entity<ui::ContextMenu>>,
1287 >,
1288 >,
1289 last_bounds: Option<Bounds<Pixels>>,
1290 last_position_map: Option<Rc<PositionMap>>,
1291 expect_bounds_change: Option<Bounds<Pixels>>,
1292 runnables: RunnableData,
1293 breakpoint_store: Option<Entity<BreakpointStore>>,
1294 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1295 pub(crate) gutter_diff_review_indicator: (Option<PhantomDiffReviewIndicator>, Option<Task<()>>),
1296 pub(crate) diff_review_drag_state: Option<DiffReviewDragState>,
1297 /// Active diff review overlays. Multiple overlays can be open simultaneously
1298 /// when hunks have comments stored.
1299 pub(crate) diff_review_overlays: Vec<DiffReviewOverlay>,
1300 /// Stored review comments grouped by hunk.
1301 /// Uses a Vec instead of HashMap because DiffHunkKey contains an Anchor
1302 /// which doesn't implement Hash/Eq in a way suitable for HashMap keys.
1303 stored_review_comments: Vec<(DiffHunkKey, Vec<StoredReviewComment>)>,
1304 /// Counter for generating unique comment IDs.
1305 next_review_comment_id: usize,
1306 hovered_diff_hunk_row: Option<DisplayRow>,
1307 pull_diagnostics_task: Task<()>,
1308 in_project_search: bool,
1309 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1310 breadcrumb_header: Option<String>,
1311 focused_block: Option<FocusedBlock>,
1312 next_scroll_position: NextScrollCursorCenterTopBottom,
1313 addons: HashMap<TypeId, Box<dyn Addon>>,
1314 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1315 load_diff_task: Option<Shared<Task<()>>>,
1316 /// Whether we are temporarily displaying a diff other than git's
1317 temporary_diff_override: bool,
1318 selection_mark_mode: bool,
1319 toggle_fold_multiple_buffers: Task<()>,
1320 _scroll_cursor_center_top_bottom_task: Task<()>,
1321 serialize_selections: Task<()>,
1322 serialize_folds: Task<()>,
1323 mouse_cursor_hidden: bool,
1324 minimap: Option<Entity<Self>>,
1325 hide_mouse_mode: HideMouseMode,
1326 pub change_list: ChangeList,
1327 inline_value_cache: InlineValueCache,
1328 number_deleted_lines: bool,
1329
1330 selection_drag_state: SelectionDragState,
1331 colors: Option<LspColorData>,
1332 post_scroll_update: Task<()>,
1333 refresh_colors_task: Task<()>,
1334 use_document_folding_ranges: bool,
1335 refresh_folding_ranges_task: Task<()>,
1336 inlay_hints: Option<LspInlayHintData>,
1337 folding_newlines: Task<()>,
1338 select_next_is_case_sensitive: Option<bool>,
1339 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1340 on_local_selections_changed:
1341 Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
1342 suppress_selection_callback: bool,
1343 applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
1344 accent_data: Option<AccentData>,
1345 bracket_fetched_tree_sitter_chunks: HashMap<ExcerptId, HashSet<Range<BufferRow>>>,
1346 semantic_token_state: SemanticTokenState,
1347 pub(crate) refresh_matching_bracket_highlights_task: Task<()>,
1348 refresh_document_symbols_task: Shared<Task<()>>,
1349 lsp_document_symbols: HashMap<BufferId, Vec<OutlineItem<text::Anchor>>>,
1350 refresh_outline_symbols_at_cursor_at_cursor_task: Task<()>,
1351 outline_symbols_at_cursor: Option<(BufferId, Vec<OutlineItem<Anchor>>)>,
1352 sticky_headers_task: Task<()>,
1353 sticky_headers: Option<Vec<OutlineItem<Anchor>>>,
1354 pub(crate) colorize_brackets_task: Task<()>,
1355}
1356
1357#[derive(Debug, PartialEq)]
1358struct AccentData {
1359 colors: AccentColors,
1360 overrides: Vec<SharedString>,
1361}
1362
1363fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1364 if debounce_ms > 0 {
1365 Some(Duration::from_millis(debounce_ms))
1366 } else {
1367 None
1368 }
1369}
1370
1371#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1372enum NextScrollCursorCenterTopBottom {
1373 #[default]
1374 Center,
1375 Top,
1376 Bottom,
1377}
1378
1379impl NextScrollCursorCenterTopBottom {
1380 fn next(&self) -> Self {
1381 match self {
1382 Self::Center => Self::Top,
1383 Self::Top => Self::Bottom,
1384 Self::Bottom => Self::Center,
1385 }
1386 }
1387}
1388
1389#[derive(Clone)]
1390pub struct EditorSnapshot {
1391 pub mode: EditorMode,
1392 show_gutter: bool,
1393 offset_content: bool,
1394 show_line_numbers: Option<bool>,
1395 number_deleted_lines: bool,
1396 show_git_diff_gutter: Option<bool>,
1397 show_code_actions: Option<bool>,
1398 show_runnables: Option<bool>,
1399 show_breakpoints: Option<bool>,
1400 git_blame_gutter_max_author_length: Option<usize>,
1401 pub display_snapshot: DisplaySnapshot,
1402 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1403 is_focused: bool,
1404 scroll_anchor: SharedScrollAnchor,
1405 ongoing_scroll: OngoingScroll,
1406 current_line_highlight: CurrentLineHighlight,
1407 gutter_hovered: bool,
1408 semantic_tokens_enabled: bool,
1409}
1410
1411#[derive(Default, Debug, Clone, Copy)]
1412pub struct GutterDimensions {
1413 pub left_padding: Pixels,
1414 pub right_padding: Pixels,
1415 pub width: Pixels,
1416 pub margin: Pixels,
1417 pub git_blame_entries_width: Option<Pixels>,
1418}
1419
1420impl GutterDimensions {
1421 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1422 Self {
1423 margin: Self::default_gutter_margin(font_id, font_size, cx),
1424 ..Default::default()
1425 }
1426 }
1427
1428 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1429 -cx.text_system().descent(font_id, font_size)
1430 }
1431 /// The full width of the space taken up by the gutter.
1432 pub fn full_width(&self) -> Pixels {
1433 self.margin + self.width
1434 }
1435
1436 /// The width of the space reserved for the fold indicators,
1437 /// use alongside 'justify_end' and `gutter_width` to
1438 /// right align content with the line numbers
1439 pub fn fold_area_width(&self) -> Pixels {
1440 self.margin + self.right_padding
1441 }
1442}
1443
1444struct CharacterDimensions {
1445 em_width: Pixels,
1446 em_advance: Pixels,
1447 line_height: Pixels,
1448}
1449
1450#[derive(Debug)]
1451pub struct RemoteSelection {
1452 pub replica_id: ReplicaId,
1453 pub selection: Selection<Anchor>,
1454 pub cursor_shape: CursorShape,
1455 pub collaborator_id: CollaboratorId,
1456 pub line_mode: bool,
1457 pub user_name: Option<SharedString>,
1458 pub color: PlayerColor,
1459}
1460
1461#[derive(Clone, Debug)]
1462struct SelectionHistoryEntry {
1463 selections: Arc<[Selection<Anchor>]>,
1464 select_next_state: Option<SelectNextState>,
1465 select_prev_state: Option<SelectNextState>,
1466 add_selections_state: Option<AddSelectionsState>,
1467}
1468
1469#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1470enum SelectionHistoryMode {
1471 #[default]
1472 Normal,
1473 Undoing,
1474 Redoing,
1475 Skipping,
1476}
1477
1478#[derive(Clone, PartialEq, Eq, Hash)]
1479struct HoveredCursor {
1480 replica_id: ReplicaId,
1481 selection_id: usize,
1482}
1483
1484#[derive(Debug)]
1485/// SelectionEffects controls the side-effects of updating the selection.
1486///
1487/// The default behaviour does "what you mostly want":
1488/// - it pushes to the nav history if the cursor moved by >10 lines
1489/// - it re-triggers completion requests
1490/// - it scrolls to fit
1491///
1492/// You might want to modify these behaviours. For example when doing a "jump"
1493/// like go to definition, we always want to add to nav history; but when scrolling
1494/// in vim mode we never do.
1495///
1496/// Similarly, you might want to disable scrolling if you don't want the viewport to
1497/// move.
1498#[derive(Clone)]
1499pub struct SelectionEffects {
1500 nav_history: Option<bool>,
1501 completions: bool,
1502 scroll: Option<Autoscroll>,
1503}
1504
1505impl Default for SelectionEffects {
1506 fn default() -> Self {
1507 Self {
1508 nav_history: None,
1509 completions: true,
1510 scroll: Some(Autoscroll::fit()),
1511 }
1512 }
1513}
1514impl SelectionEffects {
1515 pub fn scroll(scroll: Autoscroll) -> Self {
1516 Self {
1517 scroll: Some(scroll),
1518 ..Default::default()
1519 }
1520 }
1521
1522 pub fn no_scroll() -> Self {
1523 Self {
1524 scroll: None,
1525 ..Default::default()
1526 }
1527 }
1528
1529 pub fn completions(self, completions: bool) -> Self {
1530 Self {
1531 completions,
1532 ..self
1533 }
1534 }
1535
1536 pub fn nav_history(self, nav_history: bool) -> Self {
1537 Self {
1538 nav_history: Some(nav_history),
1539 ..self
1540 }
1541 }
1542}
1543
1544struct DeferredSelectionEffectsState {
1545 changed: bool,
1546 effects: SelectionEffects,
1547 old_cursor_position: Anchor,
1548 history_entry: SelectionHistoryEntry,
1549}
1550
1551#[derive(Default)]
1552struct SelectionHistory {
1553 #[allow(clippy::type_complexity)]
1554 selections_by_transaction:
1555 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1556 mode: SelectionHistoryMode,
1557 undo_stack: VecDeque<SelectionHistoryEntry>,
1558 redo_stack: VecDeque<SelectionHistoryEntry>,
1559}
1560
1561impl SelectionHistory {
1562 #[track_caller]
1563 fn insert_transaction(
1564 &mut self,
1565 transaction_id: TransactionId,
1566 selections: Arc<[Selection<Anchor>]>,
1567 ) {
1568 if selections.is_empty() {
1569 log::error!(
1570 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1571 std::panic::Location::caller()
1572 );
1573 return;
1574 }
1575 self.selections_by_transaction
1576 .insert(transaction_id, (selections, None));
1577 }
1578
1579 #[allow(clippy::type_complexity)]
1580 fn transaction(
1581 &self,
1582 transaction_id: TransactionId,
1583 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1584 self.selections_by_transaction.get(&transaction_id)
1585 }
1586
1587 #[allow(clippy::type_complexity)]
1588 fn transaction_mut(
1589 &mut self,
1590 transaction_id: TransactionId,
1591 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1592 self.selections_by_transaction.get_mut(&transaction_id)
1593 }
1594
1595 fn push(&mut self, entry: SelectionHistoryEntry) {
1596 if !entry.selections.is_empty() {
1597 match self.mode {
1598 SelectionHistoryMode::Normal => {
1599 self.push_undo(entry);
1600 self.redo_stack.clear();
1601 }
1602 SelectionHistoryMode::Undoing => self.push_redo(entry),
1603 SelectionHistoryMode::Redoing => self.push_undo(entry),
1604 SelectionHistoryMode::Skipping => {}
1605 }
1606 }
1607 }
1608
1609 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1610 if self
1611 .undo_stack
1612 .back()
1613 .is_none_or(|e| e.selections != entry.selections)
1614 {
1615 self.undo_stack.push_back(entry);
1616 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1617 self.undo_stack.pop_front();
1618 }
1619 }
1620 }
1621
1622 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1623 if self
1624 .redo_stack
1625 .back()
1626 .is_none_or(|e| e.selections != entry.selections)
1627 {
1628 self.redo_stack.push_back(entry);
1629 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1630 self.redo_stack.pop_front();
1631 }
1632 }
1633 }
1634}
1635
1636#[derive(Clone, Copy)]
1637pub struct RowHighlightOptions {
1638 pub autoscroll: bool,
1639 pub include_gutter: bool,
1640}
1641
1642impl Default for RowHighlightOptions {
1643 fn default() -> Self {
1644 Self {
1645 autoscroll: Default::default(),
1646 include_gutter: true,
1647 }
1648 }
1649}
1650
1651struct RowHighlight {
1652 index: usize,
1653 range: Range<Anchor>,
1654 color: Hsla,
1655 options: RowHighlightOptions,
1656 type_id: TypeId,
1657}
1658
1659#[derive(Clone, Debug)]
1660struct AddSelectionsState {
1661 groups: Vec<AddSelectionsGroup>,
1662}
1663
1664#[derive(Clone, Debug)]
1665struct AddSelectionsGroup {
1666 above: bool,
1667 stack: Vec<usize>,
1668}
1669
1670#[derive(Clone)]
1671struct SelectNextState {
1672 query: AhoCorasick,
1673 wordwise: bool,
1674 done: bool,
1675}
1676
1677impl std::fmt::Debug for SelectNextState {
1678 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1679 f.debug_struct(std::any::type_name::<Self>())
1680 .field("wordwise", &self.wordwise)
1681 .field("done", &self.done)
1682 .finish()
1683 }
1684}
1685
1686#[derive(Debug)]
1687struct AutocloseRegion {
1688 selection_id: usize,
1689 range: Range<Anchor>,
1690 pair: BracketPair,
1691}
1692
1693#[derive(Debug)]
1694struct SnippetState {
1695 ranges: Vec<Vec<Range<Anchor>>>,
1696 active_index: usize,
1697 choices: Vec<Option<Vec<String>>>,
1698}
1699
1700#[doc(hidden)]
1701pub struct RenameState {
1702 pub range: Range<Anchor>,
1703 pub old_name: Arc<str>,
1704 pub editor: Entity<Editor>,
1705 block_id: CustomBlockId,
1706}
1707
1708struct InvalidationStack<T>(Vec<T>);
1709
1710struct RegisteredEditPredictionDelegate {
1711 provider: Arc<dyn EditPredictionDelegateHandle>,
1712 _subscription: Subscription,
1713}
1714
1715#[derive(Debug, PartialEq, Eq)]
1716pub struct ActiveDiagnosticGroup {
1717 pub active_range: Range<Anchor>,
1718 pub active_message: String,
1719 pub group_id: usize,
1720 pub blocks: HashSet<CustomBlockId>,
1721}
1722
1723#[derive(Debug, PartialEq, Eq)]
1724
1725pub(crate) enum ActiveDiagnostic {
1726 None,
1727 All,
1728 Group(ActiveDiagnosticGroup),
1729}
1730
1731#[derive(Serialize, Deserialize, Clone, Debug)]
1732pub struct ClipboardSelection {
1733 /// The number of bytes in this selection.
1734 pub len: usize,
1735 /// Whether this was a full-line selection.
1736 pub is_entire_line: bool,
1737 /// The indentation of the first line when this content was originally copied.
1738 pub first_line_indent: u32,
1739 #[serde(default)]
1740 pub file_path: Option<PathBuf>,
1741 #[serde(default)]
1742 pub line_range: Option<RangeInclusive<u32>>,
1743}
1744
1745impl ClipboardSelection {
1746 pub fn for_buffer(
1747 len: usize,
1748 is_entire_line: bool,
1749 range: Range<Point>,
1750 buffer: &MultiBufferSnapshot,
1751 project: Option<&Entity<Project>>,
1752 cx: &App,
1753 ) -> Self {
1754 let first_line_indent = buffer
1755 .indent_size_for_line(MultiBufferRow(range.start.row))
1756 .len;
1757
1758 let file_path = util::maybe!({
1759 let project = project?.read(cx);
1760 let file = buffer.file_at(range.start)?;
1761 let project_path = ProjectPath {
1762 worktree_id: file.worktree_id(cx),
1763 path: file.path().clone(),
1764 };
1765 project.absolute_path(&project_path, cx)
1766 });
1767
1768 let line_range = file_path.as_ref().and_then(|_| {
1769 let (_, start_point, start_excerpt_id) = buffer.point_to_buffer_point(range.start)?;
1770 let (_, end_point, end_excerpt_id) = buffer.point_to_buffer_point(range.end)?;
1771 if start_excerpt_id == end_excerpt_id {
1772 Some(start_point.row..=end_point.row)
1773 } else {
1774 None
1775 }
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 excerpt_id: ExcerptId,
1858 position: Point,
1859 anchor: text::Anchor,
1860 line_offset_from_top: u32,
1861 },
1862}
1863
1864pub enum MultibufferSelectionMode {
1865 First,
1866 All,
1867}
1868
1869#[derive(Clone, Copy, Debug, Default)]
1870pub struct RewrapOptions {
1871 pub override_language_settings: bool,
1872 pub preserve_existing_whitespace: bool,
1873 pub line_length: Option<usize>,
1874}
1875
1876impl Editor {
1877 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1878 let buffer = cx.new(|cx| Buffer::local("", cx));
1879 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1880 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1881 }
1882
1883 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1884 let buffer = cx.new(|cx| Buffer::local("", cx));
1885 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1886 Self::new(EditorMode::full(), buffer, None, window, cx)
1887 }
1888
1889 pub fn auto_height(
1890 min_lines: usize,
1891 max_lines: usize,
1892 window: &mut Window,
1893 cx: &mut Context<Self>,
1894 ) -> Self {
1895 let buffer = cx.new(|cx| Buffer::local("", cx));
1896 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1897 Self::new(
1898 EditorMode::AutoHeight {
1899 min_lines,
1900 max_lines: Some(max_lines),
1901 },
1902 buffer,
1903 None,
1904 window,
1905 cx,
1906 )
1907 }
1908
1909 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1910 /// The editor grows as tall as needed to fit its content.
1911 pub fn auto_height_unbounded(
1912 min_lines: usize,
1913 window: &mut Window,
1914 cx: &mut Context<Self>,
1915 ) -> Self {
1916 let buffer = cx.new(|cx| Buffer::local("", cx));
1917 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1918 Self::new(
1919 EditorMode::AutoHeight {
1920 min_lines,
1921 max_lines: None,
1922 },
1923 buffer,
1924 None,
1925 window,
1926 cx,
1927 )
1928 }
1929
1930 pub fn for_buffer(
1931 buffer: Entity<Buffer>,
1932 project: Option<Entity<Project>>,
1933 window: &mut Window,
1934 cx: &mut Context<Self>,
1935 ) -> Self {
1936 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1937 Self::new(EditorMode::full(), buffer, project, window, cx)
1938 }
1939
1940 pub fn for_multibuffer(
1941 buffer: Entity<MultiBuffer>,
1942 project: Option<Entity<Project>>,
1943 window: &mut Window,
1944 cx: &mut Context<Self>,
1945 ) -> Self {
1946 Self::new(EditorMode::full(), buffer, project, window, cx)
1947 }
1948
1949 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1950 let mut clone = Self::new(
1951 self.mode.clone(),
1952 self.buffer.clone(),
1953 self.project.clone(),
1954 window,
1955 cx,
1956 );
1957 let my_snapshot = self.display_map.update(cx, |display_map, cx| {
1958 let snapshot = display_map.snapshot(cx);
1959 clone.display_map.update(cx, |display_map, cx| {
1960 display_map.set_state(&snapshot, cx);
1961 });
1962 snapshot
1963 });
1964 let clone_snapshot = clone.display_map.update(cx, |map, cx| map.snapshot(cx));
1965 clone.folds_did_change(cx);
1966 clone.selections.clone_state(&self.selections);
1967 clone
1968 .scroll_manager
1969 .clone_state(&self.scroll_manager, &my_snapshot, &clone_snapshot, cx);
1970 clone.searchable = self.searchable;
1971 clone.read_only = self.read_only;
1972 clone.buffers_with_disabled_indent_guides =
1973 self.buffers_with_disabled_indent_guides.clone();
1974 clone
1975 }
1976
1977 pub fn new(
1978 mode: EditorMode,
1979 buffer: Entity<MultiBuffer>,
1980 project: Option<Entity<Project>>,
1981 window: &mut Window,
1982 cx: &mut Context<Self>,
1983 ) -> Self {
1984 Editor::new_internal(mode, buffer, project, None, window, cx)
1985 }
1986
1987 pub fn refresh_sticky_headers(
1988 &mut self,
1989 display_snapshot: &DisplaySnapshot,
1990 cx: &mut Context<Editor>,
1991 ) {
1992 if !self.mode.is_full() {
1993 return;
1994 }
1995 let multi_buffer = display_snapshot.buffer_snapshot();
1996 let scroll_anchor = self
1997 .scroll_manager
1998 .native_anchor(display_snapshot, cx)
1999 .anchor;
2000 let Some((excerpt_id, _, buffer)) = multi_buffer.as_singleton() else {
2001 return;
2002 };
2003 let buffer = buffer.clone();
2004
2005 let buffer_visible_start = scroll_anchor.text_anchor.to_point(&buffer);
2006 let max_row = buffer.max_point().row;
2007 let start_row = buffer_visible_start.row.min(max_row);
2008 let end_row = (buffer_visible_start.row + 10).min(max_row);
2009
2010 let syntax = self.style(cx).syntax.clone();
2011 let background_task = cx.background_spawn(async move {
2012 buffer
2013 .outline_items_containing(
2014 Point::new(start_row, 0)..Point::new(end_row, 0),
2015 true,
2016 Some(syntax.as_ref()),
2017 )
2018 .into_iter()
2019 .map(|outline_item| OutlineItem {
2020 depth: outline_item.depth,
2021 range: Anchor::range_in_buffer(excerpt_id, outline_item.range),
2022 source_range_for_text: Anchor::range_in_buffer(
2023 excerpt_id,
2024 outline_item.source_range_for_text,
2025 ),
2026 text: outline_item.text,
2027 highlight_ranges: outline_item.highlight_ranges,
2028 name_ranges: outline_item.name_ranges,
2029 body_range: outline_item
2030 .body_range
2031 .map(|range| Anchor::range_in_buffer(excerpt_id, range)),
2032 annotation_range: outline_item
2033 .annotation_range
2034 .map(|range| Anchor::range_in_buffer(excerpt_id, range)),
2035 })
2036 .collect()
2037 });
2038 self.sticky_headers_task = cx.spawn(async move |this, cx| {
2039 let sticky_headers = background_task.await;
2040 this.update(cx, |this, cx| {
2041 this.sticky_headers = Some(sticky_headers);
2042 cx.notify();
2043 })
2044 .ok();
2045 });
2046 }
2047
2048 fn new_internal(
2049 mode: EditorMode,
2050 multi_buffer: Entity<MultiBuffer>,
2051 project: Option<Entity<Project>>,
2052 display_map: Option<Entity<DisplayMap>>,
2053 window: &mut Window,
2054 cx: &mut Context<Self>,
2055 ) -> Self {
2056 debug_assert!(
2057 display_map.is_none() || mode.is_minimap(),
2058 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
2059 );
2060
2061 let full_mode = mode.is_full();
2062 let is_minimap = mode.is_minimap();
2063 let diagnostics_max_severity = if full_mode {
2064 EditorSettings::get_global(cx)
2065 .diagnostics_max_severity
2066 .unwrap_or(DiagnosticSeverity::Hint)
2067 } else {
2068 DiagnosticSeverity::Off
2069 };
2070 let style = window.text_style();
2071 let font_size = style.font_size.to_pixels(window.rem_size());
2072 let editor = cx.entity().downgrade();
2073 let fold_placeholder = FoldPlaceholder {
2074 constrain_width: false,
2075 render: Arc::new(move |fold_id, fold_range, cx| {
2076 let editor = editor.clone();
2077 FoldPlaceholder::fold_element(fold_id, cx)
2078 .cursor_pointer()
2079 .child("⋯")
2080 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
2081 .on_click(move |_, _window, cx| {
2082 editor
2083 .update(cx, |editor, cx| {
2084 editor.unfold_ranges(
2085 &[fold_range.start..fold_range.end],
2086 true,
2087 false,
2088 cx,
2089 );
2090 cx.stop_propagation();
2091 })
2092 .ok();
2093 })
2094 .into_any()
2095 }),
2096 merge_adjacent: true,
2097 ..FoldPlaceholder::default()
2098 };
2099 let display_map = display_map.unwrap_or_else(|| {
2100 cx.new(|cx| {
2101 DisplayMap::new(
2102 multi_buffer.clone(),
2103 style.font(),
2104 font_size,
2105 None,
2106 FILE_HEADER_HEIGHT,
2107 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2108 fold_placeholder,
2109 diagnostics_max_severity,
2110 cx,
2111 )
2112 })
2113 });
2114
2115 let selections = SelectionsCollection::new();
2116
2117 let blink_manager = cx.new(|cx| {
2118 let mut blink_manager = BlinkManager::new(
2119 CURSOR_BLINK_INTERVAL,
2120 |cx| EditorSettings::get_global(cx).cursor_blink,
2121 cx,
2122 );
2123 if is_minimap {
2124 blink_manager.disable(cx);
2125 }
2126 blink_manager
2127 });
2128
2129 let soft_wrap_mode_override =
2130 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
2131
2132 let mut project_subscriptions = Vec::new();
2133 if full_mode && let Some(project) = project.as_ref() {
2134 project_subscriptions.push(cx.subscribe_in(
2135 project,
2136 window,
2137 |editor, _, event, window, cx| match event {
2138 project::Event::RefreshCodeLens => {
2139 // we always query lens with actions, without storing them, always refreshing them
2140 }
2141 project::Event::RefreshInlayHints {
2142 server_id,
2143 request_id,
2144 } => {
2145 editor.refresh_inlay_hints(
2146 InlayHintRefreshReason::RefreshRequested {
2147 server_id: *server_id,
2148 request_id: *request_id,
2149 },
2150 cx,
2151 );
2152 }
2153 project::Event::RefreshSemanticTokens {
2154 server_id,
2155 request_id,
2156 } => {
2157 editor.refresh_semantic_tokens(
2158 None,
2159 Some(RefreshForServer {
2160 server_id: *server_id,
2161 request_id: *request_id,
2162 }),
2163 cx,
2164 );
2165 }
2166 project::Event::LanguageServerRemoved(_) => {
2167 editor.registered_buffers.clear();
2168 editor.register_visible_buffers(cx);
2169 editor.invalidate_semantic_tokens(None);
2170 editor.refresh_runnables(None, window, cx);
2171 editor.update_lsp_data(None, window, cx);
2172 editor.refresh_inlay_hints(InlayHintRefreshReason::ServerRemoved, cx);
2173 }
2174 project::Event::SnippetEdit(id, snippet_edits) => {
2175 // todo(lw): Non singletons
2176 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
2177 let snapshot = buffer.read(cx).snapshot();
2178 let focus_handle = editor.focus_handle(cx);
2179 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
2180 for (range, snippet) in snippet_edits {
2181 let buffer_range =
2182 language::range_from_lsp(*range).to_offset(&snapshot);
2183 editor
2184 .insert_snippet(
2185 &[MultiBufferOffset(buffer_range.start)
2186 ..MultiBufferOffset(buffer_range.end)],
2187 snippet.clone(),
2188 window,
2189 cx,
2190 )
2191 .ok();
2192 }
2193 }
2194 }
2195 }
2196 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
2197 let buffer_id = *buffer_id;
2198 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
2199 editor.register_buffer(buffer_id, cx);
2200 editor.refresh_runnables(Some(buffer_id), window, cx);
2201 editor.update_lsp_data(Some(buffer_id), window, cx);
2202 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
2203 refresh_linked_ranges(editor, window, cx);
2204 editor.refresh_code_actions(window, cx);
2205 editor.refresh_document_highlights(cx);
2206 }
2207 }
2208
2209 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
2210 let Some(workspace) = editor.workspace() else {
2211 return;
2212 };
2213 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2214 else {
2215 return;
2216 };
2217
2218 if active_editor.entity_id() == cx.entity_id() {
2219 let entity_id = cx.entity_id();
2220 workspace.update(cx, |this, cx| {
2221 this.panes_mut()
2222 .iter_mut()
2223 .filter(|pane| pane.entity_id() != entity_id)
2224 .for_each(|p| {
2225 p.update(cx, |pane, _| {
2226 pane.nav_history_mut().rename_item(
2227 entity_id,
2228 project_path.clone(),
2229 abs_path.clone().into(),
2230 );
2231 })
2232 });
2233 });
2234
2235 Self::open_transaction_for_hidden_buffers(
2236 workspace,
2237 transaction.clone(),
2238 "Rename".to_string(),
2239 window,
2240 cx,
2241 );
2242 }
2243 }
2244
2245 project::Event::WorkspaceEditApplied(transaction) => {
2246 let Some(workspace) = editor.workspace() else {
2247 return;
2248 };
2249 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2250 else {
2251 return;
2252 };
2253
2254 if active_editor.entity_id() == cx.entity_id() {
2255 Self::open_transaction_for_hidden_buffers(
2256 workspace,
2257 transaction.clone(),
2258 "LSP Edit".to_string(),
2259 window,
2260 cx,
2261 );
2262 }
2263 }
2264
2265 _ => {}
2266 },
2267 ));
2268 if let Some(task_inventory) = project
2269 .read(cx)
2270 .task_store()
2271 .read(cx)
2272 .task_inventory()
2273 .cloned()
2274 {
2275 project_subscriptions.push(cx.observe_in(
2276 &task_inventory,
2277 window,
2278 |editor, _, window, cx| {
2279 editor.refresh_runnables(None, window, cx);
2280 },
2281 ));
2282 };
2283
2284 project_subscriptions.push(cx.subscribe_in(
2285 &project.read(cx).breakpoint_store(),
2286 window,
2287 |editor, _, event, window, cx| match event {
2288 BreakpointStoreEvent::ClearDebugLines => {
2289 editor.clear_row_highlights::<ActiveDebugLine>();
2290 editor.refresh_inline_values(cx);
2291 }
2292 BreakpointStoreEvent::SetDebugLine => {
2293 if editor.go_to_active_debug_line(window, cx) {
2294 cx.stop_propagation();
2295 }
2296
2297 editor.refresh_inline_values(cx);
2298 }
2299 _ => {}
2300 },
2301 ));
2302 let git_store = project.read(cx).git_store().clone();
2303 let project = project.clone();
2304 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2305 if let GitStoreEvent::RepositoryAdded = event {
2306 this.load_diff_task = Some(
2307 update_uncommitted_diff_for_buffer(
2308 cx.entity(),
2309 &project,
2310 this.buffer.read(cx).all_buffers(),
2311 this.buffer.clone(),
2312 cx,
2313 )
2314 .shared(),
2315 );
2316 }
2317 }));
2318 }
2319
2320 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2321
2322 let inlay_hint_settings =
2323 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2324 let focus_handle = cx.focus_handle();
2325 if !is_minimap {
2326 cx.on_focus(&focus_handle, window, Self::handle_focus)
2327 .detach();
2328 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2329 .detach();
2330 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2331 .detach();
2332 cx.on_blur(&focus_handle, window, Self::handle_blur)
2333 .detach();
2334 cx.observe_pending_input(window, Self::observe_pending_input)
2335 .detach();
2336 }
2337
2338 let show_indent_guides =
2339 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2340 Some(false)
2341 } else {
2342 None
2343 };
2344
2345 let breakpoint_store = match (&mode, project.as_ref()) {
2346 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2347 _ => None,
2348 };
2349
2350 let mut code_action_providers = Vec::new();
2351 let mut load_uncommitted_diff = None;
2352 if let Some(project) = project.clone() {
2353 load_uncommitted_diff = Some(
2354 update_uncommitted_diff_for_buffer(
2355 cx.entity(),
2356 &project,
2357 multi_buffer.read(cx).all_buffers(),
2358 multi_buffer.clone(),
2359 cx,
2360 )
2361 .shared(),
2362 );
2363 code_action_providers.push(Rc::new(project) as Rc<_>);
2364 }
2365
2366 let mut editor = Self {
2367 focus_handle,
2368 show_cursor_when_unfocused: false,
2369 last_focused_descendant: None,
2370 buffer: multi_buffer.clone(),
2371 display_map: display_map.clone(),
2372 placeholder_display_map: None,
2373 selections,
2374 scroll_manager: ScrollManager::new(cx),
2375 columnar_selection_state: None,
2376 add_selections_state: None,
2377 select_next_state: None,
2378 select_prev_state: None,
2379 selection_history: SelectionHistory::default(),
2380 defer_selection_effects: false,
2381 deferred_selection_effects_state: None,
2382 autoclose_regions: Vec::new(),
2383 snippet_stack: InvalidationStack::default(),
2384 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2385 ime_transaction: None,
2386 active_diagnostics: ActiveDiagnostic::None,
2387 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2388 inline_diagnostics_update: Task::ready(()),
2389 inline_diagnostics: Vec::new(),
2390 soft_wrap_mode_override,
2391 diagnostics_max_severity,
2392 hard_wrap: None,
2393 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2394 semantics_provider: project
2395 .as_ref()
2396 .map(|project| Rc::new(project.downgrade()) as _),
2397 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2398 project,
2399 blink_manager: blink_manager.clone(),
2400 show_local_selections: true,
2401 show_scrollbars: ScrollbarAxes {
2402 horizontal: full_mode,
2403 vertical: full_mode,
2404 },
2405 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2406 offset_content: !matches!(mode, EditorMode::SingleLine),
2407 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2408 show_gutter: full_mode,
2409 show_line_numbers: (!full_mode).then_some(false),
2410 use_relative_line_numbers: None,
2411 disable_expand_excerpt_buttons: !full_mode,
2412 delegate_expand_excerpts: false,
2413 delegate_stage_and_restore: false,
2414 delegate_open_excerpts: false,
2415 enable_lsp_data: true,
2416 enable_runnables: true,
2417 show_git_diff_gutter: None,
2418 show_code_actions: None,
2419 show_runnables: None,
2420 show_breakpoints: None,
2421 show_diff_review_button: false,
2422 show_wrap_guides: None,
2423 show_indent_guides,
2424 buffers_with_disabled_indent_guides: HashSet::default(),
2425 highlight_order: 0,
2426 highlighted_rows: HashMap::default(),
2427 background_highlights: HashMap::default(),
2428 gutter_highlights: HashMap::default(),
2429 scrollbar_marker_state: ScrollbarMarkerState::default(),
2430 active_indent_guides_state: ActiveIndentGuidesState::default(),
2431 nav_history: None,
2432 context_menu: RefCell::new(None),
2433 context_menu_options: None,
2434 mouse_context_menu: None,
2435 completion_tasks: Vec::new(),
2436 inline_blame_popover: None,
2437 inline_blame_popover_show_task: None,
2438 signature_help_state: SignatureHelpState::default(),
2439 auto_signature_help: None,
2440 find_all_references_task_sources: Vec::new(),
2441 next_completion_id: 0,
2442 next_inlay_id: 0,
2443 code_action_providers,
2444 available_code_actions: None,
2445 code_actions_task: None,
2446 quick_selection_highlight_task: None,
2447 debounced_selection_highlight_task: None,
2448 debounced_selection_highlight_complete: false,
2449 document_highlights_task: None,
2450 linked_editing_range_task: None,
2451 pending_rename: None,
2452 searchable: !is_minimap,
2453 cursor_shape: EditorSettings::get_global(cx)
2454 .cursor_shape
2455 .unwrap_or_default(),
2456 cursor_offset_on_selection: false,
2457 current_line_highlight: None,
2458 autoindent_mode: Some(AutoindentMode::EachLine),
2459 collapse_matches: false,
2460 workspace: None,
2461 input_enabled: !is_minimap,
2462 expects_character_input: !is_minimap,
2463 use_modal_editing: full_mode,
2464 read_only: is_minimap,
2465 use_autoclose: true,
2466 use_auto_surround: true,
2467 auto_replace_emoji_shortcode: false,
2468 jsx_tag_auto_close_enabled_in_any_buffer: false,
2469 leader_id: None,
2470 remote_id: None,
2471 hover_state: HoverState::default(),
2472 pending_mouse_down: None,
2473 prev_pressure_stage: None,
2474 hovered_link_state: None,
2475 edit_prediction_provider: None,
2476 active_edit_prediction: None,
2477 stale_edit_prediction_in_menu: None,
2478 edit_prediction_preview: EditPredictionPreview::Inactive {
2479 released_too_fast: false,
2480 },
2481 inline_diagnostics_enabled: full_mode,
2482 diagnostics_enabled: full_mode,
2483 word_completions_enabled: full_mode,
2484 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2485 gutter_hovered: false,
2486 pixel_position_of_newest_cursor: None,
2487 last_bounds: None,
2488 last_position_map: None,
2489 expect_bounds_change: None,
2490 gutter_dimensions: GutterDimensions::default(),
2491 style: None,
2492 show_cursor_names: false,
2493 hovered_cursors: HashMap::default(),
2494 next_editor_action_id: EditorActionId::default(),
2495 editor_actions: Rc::default(),
2496 edit_predictions_hidden_for_vim_mode: false,
2497 show_edit_predictions_override: None,
2498 show_completions_on_input_override: None,
2499 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2500 edit_prediction_settings: EditPredictionSettings::Disabled,
2501 in_leading_whitespace: false,
2502 custom_context_menu: None,
2503 show_git_blame_gutter: false,
2504 show_git_blame_inline: false,
2505 show_selection_menu: None,
2506 show_git_blame_inline_delay_task: None,
2507 git_blame_inline_enabled: full_mode
2508 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2509 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2510 buffer_serialization: is_minimap.not().then(|| {
2511 BufferSerialization::new(
2512 ProjectSettings::get_global(cx)
2513 .session
2514 .restore_unsaved_buffers,
2515 )
2516 }),
2517 blame: None,
2518 blame_subscription: None,
2519
2520 breakpoint_store,
2521 gutter_breakpoint_indicator: (None, None),
2522 gutter_diff_review_indicator: (None, None),
2523 diff_review_drag_state: None,
2524 diff_review_overlays: Vec::new(),
2525 stored_review_comments: Vec::new(),
2526 next_review_comment_id: 0,
2527 hovered_diff_hunk_row: None,
2528 _subscriptions: (!is_minimap)
2529 .then(|| {
2530 vec![
2531 cx.observe(&multi_buffer, Self::on_buffer_changed),
2532 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2533 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2534 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2535 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2536 cx.observe_global_in::<GlobalTheme>(window, Self::theme_changed),
2537 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2538 cx.observe_window_activation(window, |editor, window, cx| {
2539 let active = window.is_window_active();
2540 editor.blink_manager.update(cx, |blink_manager, cx| {
2541 if active {
2542 blink_manager.enable(cx);
2543 } else {
2544 blink_manager.disable(cx);
2545 }
2546 });
2547 if active {
2548 editor.show_mouse_cursor(cx);
2549 }
2550 }),
2551 ]
2552 })
2553 .unwrap_or_default(),
2554 runnables: RunnableData::new(),
2555 pull_diagnostics_task: Task::ready(()),
2556 colors: None,
2557 refresh_colors_task: Task::ready(()),
2558 use_document_folding_ranges: false,
2559 refresh_folding_ranges_task: Task::ready(()),
2560 inlay_hints: None,
2561 next_color_inlay_id: 0,
2562 post_scroll_update: Task::ready(()),
2563 linked_edit_ranges: Default::default(),
2564 in_project_search: false,
2565 previous_search_ranges: None,
2566 breadcrumb_header: None,
2567 focused_block: None,
2568 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2569 addons: HashMap::default(),
2570 registered_buffers: HashMap::default(),
2571 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2572 selection_mark_mode: false,
2573 toggle_fold_multiple_buffers: Task::ready(()),
2574 serialize_selections: Task::ready(()),
2575 serialize_folds: Task::ready(()),
2576 text_style_refinement: None,
2577 load_diff_task: load_uncommitted_diff,
2578 temporary_diff_override: false,
2579 mouse_cursor_hidden: false,
2580 minimap: None,
2581 hide_mouse_mode: EditorSettings::get_global(cx)
2582 .hide_mouse
2583 .unwrap_or_default(),
2584 change_list: ChangeList::new(),
2585 mode,
2586 selection_drag_state: SelectionDragState::None,
2587 folding_newlines: Task::ready(()),
2588 lookup_key: None,
2589 select_next_is_case_sensitive: None,
2590 on_local_selections_changed: None,
2591 suppress_selection_callback: false,
2592 applicable_language_settings: HashMap::default(),
2593 semantic_token_state: SemanticTokenState::new(cx, full_mode),
2594 accent_data: None,
2595 bracket_fetched_tree_sitter_chunks: HashMap::default(),
2596 number_deleted_lines: false,
2597 refresh_matching_bracket_highlights_task: Task::ready(()),
2598 refresh_document_symbols_task: Task::ready(()).shared(),
2599 lsp_document_symbols: HashMap::default(),
2600 refresh_outline_symbols_at_cursor_at_cursor_task: Task::ready(()),
2601 outline_symbols_at_cursor: None,
2602 sticky_headers_task: Task::ready(()),
2603 sticky_headers: None,
2604 colorize_brackets_task: Task::ready(()),
2605 };
2606
2607 if is_minimap {
2608 return editor;
2609 }
2610
2611 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2612 editor.accent_data = editor.fetch_accent_data(cx);
2613
2614 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2615 editor
2616 ._subscriptions
2617 .push(cx.observe(breakpoints, |_, _, cx| {
2618 cx.notify();
2619 }));
2620 }
2621 editor._subscriptions.extend(project_subscriptions);
2622
2623 editor._subscriptions.push(cx.subscribe_in(
2624 &cx.entity(),
2625 window,
2626 |editor, _, e: &EditorEvent, window, cx| match e {
2627 EditorEvent::ScrollPositionChanged { local, .. } => {
2628 if *local {
2629 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2630 editor.inline_blame_popover.take();
2631 let snapshot = editor.snapshot(window, cx);
2632 let new_anchor = editor
2633 .scroll_manager
2634 .native_anchor(&snapshot.display_snapshot, cx);
2635 editor.update_restoration_data(cx, move |data| {
2636 data.scroll_position = (
2637 new_anchor.top_row(snapshot.buffer_snapshot()),
2638 new_anchor.offset,
2639 );
2640 });
2641
2642 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2643 cx.background_executor()
2644 .timer(Duration::from_millis(50))
2645 .await;
2646 editor
2647 .update_in(cx, |editor, window, cx| {
2648 editor.update_data_on_scroll(window, cx)
2649 })
2650 .ok();
2651 });
2652 }
2653 editor.refresh_sticky_headers(&editor.snapshot(window, cx), cx);
2654 }
2655 EditorEvent::Edited { .. } => {
2656 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2657 .map(|vim_mode| vim_mode.0)
2658 .unwrap_or(false);
2659 if !vim_mode {
2660 let display_map = editor.display_snapshot(cx);
2661 let selections = editor.selections.all_adjusted_display(&display_map);
2662 let pop_state = editor
2663 .change_list
2664 .last()
2665 .map(|previous| {
2666 previous.len() == selections.len()
2667 && previous.iter().enumerate().all(|(ix, p)| {
2668 p.to_display_point(&display_map).row()
2669 == selections[ix].head().row()
2670 })
2671 })
2672 .unwrap_or(false);
2673 let new_positions = selections
2674 .into_iter()
2675 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2676 .collect();
2677 editor
2678 .change_list
2679 .push_to_change_list(pop_state, new_positions);
2680 }
2681 }
2682 _ => (),
2683 },
2684 ));
2685
2686 if let Some(dap_store) = editor
2687 .project
2688 .as_ref()
2689 .map(|project| project.read(cx).dap_store())
2690 {
2691 let weak_editor = cx.weak_entity();
2692
2693 editor
2694 ._subscriptions
2695 .push(
2696 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2697 let session_entity = cx.entity();
2698 weak_editor
2699 .update(cx, |editor, cx| {
2700 editor._subscriptions.push(
2701 cx.subscribe(&session_entity, Self::on_debug_session_event),
2702 );
2703 })
2704 .ok();
2705 }),
2706 );
2707
2708 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2709 editor
2710 ._subscriptions
2711 .push(cx.subscribe(&session, Self::on_debug_session_event));
2712 }
2713 }
2714
2715 // skip adding the initial selection to selection history
2716 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2717 editor.end_selection(window, cx);
2718 editor.selection_history.mode = SelectionHistoryMode::Normal;
2719
2720 editor.scroll_manager.show_scrollbars(window, cx);
2721 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2722
2723 if full_mode {
2724 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2725 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2726
2727 if editor.git_blame_inline_enabled {
2728 editor.start_git_blame_inline(false, window, cx);
2729 }
2730
2731 editor.go_to_active_debug_line(window, cx);
2732
2733 editor.minimap =
2734 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2735 editor.colors = Some(LspColorData::new(cx));
2736 editor.use_document_folding_ranges = true;
2737 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2738
2739 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2740 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2741 }
2742 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2743 }
2744
2745 editor
2746 }
2747
2748 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2749 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2750 }
2751
2752 pub fn deploy_mouse_context_menu(
2753 &mut self,
2754 position: gpui::Point<Pixels>,
2755 context_menu: Entity<ContextMenu>,
2756 window: &mut Window,
2757 cx: &mut Context<Self>,
2758 ) {
2759 self.mouse_context_menu = Some(MouseContextMenu::new(
2760 self,
2761 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2762 context_menu,
2763 window,
2764 cx,
2765 ));
2766 }
2767
2768 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2769 self.mouse_context_menu
2770 .as_ref()
2771 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2772 }
2773
2774 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2775 if self
2776 .selections
2777 .pending_anchor()
2778 .is_some_and(|pending_selection| {
2779 let snapshot = self.buffer().read(cx).snapshot(cx);
2780 pending_selection.range().includes(range, &snapshot)
2781 })
2782 {
2783 return true;
2784 }
2785
2786 self.selections
2787 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2788 .into_iter()
2789 .any(|selection| {
2790 // This is needed to cover a corner case, if we just check for an existing
2791 // selection in the fold range, having a cursor at the start of the fold
2792 // marks it as selected. Non-empty selections don't cause this.
2793 let length = selection.end - selection.start;
2794 length > 0
2795 })
2796 }
2797
2798 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2799 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2800 }
2801
2802 fn key_context_internal(
2803 &self,
2804 has_active_edit_prediction: bool,
2805 window: &mut Window,
2806 cx: &mut App,
2807 ) -> KeyContext {
2808 let mut key_context = KeyContext::new_with_defaults();
2809 key_context.add("Editor");
2810 let mode = match self.mode {
2811 EditorMode::SingleLine => "single_line",
2812 EditorMode::AutoHeight { .. } => "auto_height",
2813 EditorMode::Minimap { .. } => "minimap",
2814 EditorMode::Full { .. } => "full",
2815 };
2816
2817 if EditorSettings::jupyter_enabled(cx) {
2818 key_context.add("jupyter");
2819 }
2820
2821 key_context.set("mode", mode);
2822 if self.pending_rename.is_some() {
2823 key_context.add("renaming");
2824 }
2825
2826 if let Some(snippet_stack) = self.snippet_stack.last() {
2827 key_context.add("in_snippet");
2828
2829 if snippet_stack.active_index > 0 {
2830 key_context.add("has_previous_tabstop");
2831 }
2832
2833 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2834 key_context.add("has_next_tabstop");
2835 }
2836 }
2837
2838 match self.context_menu.borrow().as_ref() {
2839 Some(CodeContextMenu::Completions(menu)) => {
2840 if menu.visible() {
2841 key_context.add("menu");
2842 key_context.add("showing_completions");
2843 }
2844 }
2845 Some(CodeContextMenu::CodeActions(menu)) => {
2846 if menu.visible() {
2847 key_context.add("menu");
2848 key_context.add("showing_code_actions")
2849 }
2850 }
2851 None => {}
2852 }
2853
2854 if self.signature_help_state.has_multiple_signatures() {
2855 key_context.add("showing_signature_help");
2856 }
2857
2858 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2859 if !self.focus_handle(cx).contains_focused(window, cx)
2860 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2861 {
2862 for addon in self.addons.values() {
2863 addon.extend_key_context(&mut key_context, cx)
2864 }
2865 }
2866
2867 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2868 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2869 Some(
2870 file.full_path(cx)
2871 .extension()?
2872 .to_string_lossy()
2873 .to_lowercase(),
2874 )
2875 }) {
2876 key_context.set("extension", extension);
2877 }
2878 } else {
2879 key_context.add("multibuffer");
2880 }
2881
2882 if has_active_edit_prediction {
2883 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2884 key_context.add("copilot_suggestion");
2885 }
2886
2887 if self.in_leading_whitespace {
2888 key_context.add("in_leading_whitespace");
2889 }
2890 if self.edit_prediction_requires_modifier() {
2891 key_context.set("edit_prediction_mode", "subtle")
2892 } else {
2893 key_context.set("edit_prediction_mode", "eager");
2894 }
2895
2896 if self.selection_mark_mode {
2897 key_context.add("selection_mode");
2898 }
2899
2900 let disjoint = self.selections.disjoint_anchors();
2901 if matches!(
2902 &self.mode,
2903 EditorMode::SingleLine | EditorMode::AutoHeight { .. }
2904 ) && let [selection] = disjoint
2905 && selection.start == selection.end
2906 {
2907 let snapshot = self.snapshot(window, cx);
2908 let snapshot = snapshot.buffer_snapshot();
2909 let caret_offset = selection.end.to_offset(snapshot);
2910
2911 if caret_offset == MultiBufferOffset(0) {
2912 key_context.add("start_of_input");
2913 }
2914
2915 if caret_offset == snapshot.len() {
2916 key_context.add("end_of_input");
2917 }
2918 }
2919
2920 if self.has_any_expanded_diff_hunks(cx) {
2921 key_context.add("diffs_expanded");
2922 }
2923
2924 key_context
2925 }
2926
2927 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2928 self.last_bounds.as_ref()
2929 }
2930
2931 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2932 if self.mouse_cursor_hidden {
2933 self.mouse_cursor_hidden = false;
2934 cx.notify();
2935 }
2936 }
2937
2938 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2939 let hide_mouse_cursor = match origin {
2940 HideMouseCursorOrigin::TypingAction => {
2941 matches!(
2942 self.hide_mouse_mode,
2943 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2944 )
2945 }
2946 HideMouseCursorOrigin::MovementAction => {
2947 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2948 }
2949 };
2950 if self.mouse_cursor_hidden != hide_mouse_cursor {
2951 self.mouse_cursor_hidden = hide_mouse_cursor;
2952 cx.notify();
2953 }
2954 }
2955
2956 fn accept_edit_prediction_keystroke(
2957 &self,
2958 granularity: EditPredictionGranularity,
2959 window: &mut Window,
2960 cx: &mut App,
2961 ) -> Option<gpui::KeybindingKeystroke> {
2962 let key_context = self.key_context_internal(true, window, cx);
2963
2964 let bindings =
2965 match granularity {
2966 EditPredictionGranularity::Word => window
2967 .bindings_for_action_in_context(&AcceptNextWordEditPrediction, key_context),
2968 EditPredictionGranularity::Line => window
2969 .bindings_for_action_in_context(&AcceptNextLineEditPrediction, key_context),
2970 EditPredictionGranularity::Full => {
2971 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2972 }
2973 };
2974
2975 bindings
2976 .into_iter()
2977 .rev()
2978 .find_map(|binding| match binding.keystrokes() {
2979 [keystroke, ..] => Some(keystroke.clone()),
2980 _ => None,
2981 })
2982 }
2983
2984 fn preview_edit_prediction_keystroke(
2985 &self,
2986 window: &mut Window,
2987 cx: &mut App,
2988 ) -> Option<gpui::KeybindingKeystroke> {
2989 let key_context = self.key_context_internal(true, window, cx);
2990 let bindings = window.bindings_for_action_in_context(&AcceptEditPrediction, key_context);
2991 bindings
2992 .into_iter()
2993 .rev()
2994 .find_map(|binding| match binding.keystrokes() {
2995 [keystroke, ..] if keystroke.modifiers().modified() => Some(keystroke.clone()),
2996 _ => None,
2997 })
2998 }
2999
3000 fn edit_prediction_preview_modifiers_held(
3001 &self,
3002 modifiers: &Modifiers,
3003 window: &mut Window,
3004 cx: &mut App,
3005 ) -> bool {
3006 let key_context = self.key_context_internal(true, window, cx);
3007 let actions: [&dyn Action; 3] = [
3008 &AcceptEditPrediction,
3009 &AcceptNextWordEditPrediction,
3010 &AcceptNextLineEditPrediction,
3011 ];
3012
3013 actions.into_iter().any(|action| {
3014 window
3015 .bindings_for_action_in_context(action, key_context.clone())
3016 .into_iter()
3017 .rev()
3018 .any(|binding| {
3019 binding.keystrokes().first().is_some_and(|keystroke| {
3020 keystroke.modifiers().modified() && keystroke.modifiers() == modifiers
3021 })
3022 })
3023 })
3024 }
3025
3026 fn edit_prediction_cursor_popover_prefers_preview(
3027 &self,
3028 completion: &EditPredictionState,
3029 ) -> bool {
3030 match &completion.completion {
3031 EditPrediction::Edit {
3032 edits, snapshot, ..
3033 } => {
3034 let mut start_row: Option<u32> = None;
3035 let mut end_row: Option<u32> = None;
3036
3037 for (range, text) in edits {
3038 let edit_start_row = range.start.text_anchor.to_point(snapshot).row;
3039 let old_end_row = range.end.text_anchor.to_point(snapshot).row;
3040 let inserted_newline_count = text
3041 .as_ref()
3042 .chars()
3043 .filter(|character| *character == '\n')
3044 .count() as u32;
3045 let deleted_newline_count = old_end_row - edit_start_row;
3046 let preview_end_row = edit_start_row + inserted_newline_count;
3047
3048 start_row =
3049 Some(start_row.map_or(edit_start_row, |row| row.min(edit_start_row)));
3050 end_row = Some(end_row.map_or(preview_end_row, |row| row.max(preview_end_row)));
3051
3052 if deleted_newline_count > 1 {
3053 end_row = Some(end_row.map_or(old_end_row, |row| row.max(old_end_row)));
3054 }
3055 }
3056
3057 start_row
3058 .zip(end_row)
3059 .is_some_and(|(start_row, end_row)| end_row > start_row)
3060 }
3061 EditPrediction::MoveWithin { .. } | EditPrediction::MoveOutside { .. } => false,
3062 }
3063 }
3064
3065 fn edit_prediction_keybind_display(
3066 &self,
3067 surface: EditPredictionKeybindSurface,
3068 window: &mut Window,
3069 cx: &mut App,
3070 ) -> EditPredictionKeybindDisplay {
3071 let accept_keystroke =
3072 self.accept_edit_prediction_keystroke(EditPredictionGranularity::Full, window, cx);
3073 let preview_keystroke = self.preview_edit_prediction_keystroke(window, cx);
3074
3075 let action = match surface {
3076 EditPredictionKeybindSurface::Inline
3077 | EditPredictionKeybindSurface::CursorPopoverCompact => {
3078 if self.edit_prediction_requires_modifier() {
3079 EditPredictionKeybindAction::Preview
3080 } else {
3081 EditPredictionKeybindAction::Accept
3082 }
3083 }
3084 EditPredictionKeybindSurface::CursorPopoverExpanded => self
3085 .active_edit_prediction
3086 .as_ref()
3087 .filter(|completion| {
3088 self.edit_prediction_cursor_popover_prefers_preview(completion)
3089 })
3090 .map_or(EditPredictionKeybindAction::Accept, |_| {
3091 EditPredictionKeybindAction::Preview
3092 }),
3093 };
3094 #[cfg(test)]
3095 let preview_copy = preview_keystroke.clone();
3096 #[cfg(test)]
3097 let accept_copy = accept_keystroke.clone();
3098
3099 let displayed_keystroke = match surface {
3100 EditPredictionKeybindSurface::Inline => match action {
3101 EditPredictionKeybindAction::Accept => accept_keystroke,
3102 EditPredictionKeybindAction::Preview => preview_keystroke,
3103 },
3104 EditPredictionKeybindSurface::CursorPopoverCompact
3105 | EditPredictionKeybindSurface::CursorPopoverExpanded => match action {
3106 EditPredictionKeybindAction::Accept => accept_keystroke,
3107 EditPredictionKeybindAction::Preview => {
3108 preview_keystroke.or_else(|| accept_keystroke.clone())
3109 }
3110 },
3111 };
3112
3113 let missing_accept_keystroke = displayed_keystroke.is_none();
3114
3115 EditPredictionKeybindDisplay {
3116 #[cfg(test)]
3117 accept_keystroke: accept_copy,
3118 #[cfg(test)]
3119 preview_keystroke: preview_copy,
3120 displayed_keystroke,
3121 action,
3122 missing_accept_keystroke,
3123 show_hold_label: matches!(surface, EditPredictionKeybindSurface::CursorPopoverCompact)
3124 && self.edit_prediction_preview.released_too_fast(),
3125 }
3126 }
3127
3128 pub fn new_file(
3129 workspace: &mut Workspace,
3130 _: &workspace::NewFile,
3131 window: &mut Window,
3132 cx: &mut Context<Workspace>,
3133 ) {
3134 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
3135 "Failed to create buffer",
3136 window,
3137 cx,
3138 |e, _, _| match e.error_code() {
3139 ErrorCode::RemoteUpgradeRequired => Some(format!(
3140 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
3141 e.error_tag("required").unwrap_or("the latest version")
3142 )),
3143 _ => None,
3144 },
3145 );
3146 }
3147
3148 pub fn new_in_workspace(
3149 workspace: &mut Workspace,
3150 window: &mut Window,
3151 cx: &mut Context<Workspace>,
3152 ) -> Task<Result<Entity<Editor>>> {
3153 let project = workspace.project().clone();
3154 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
3155
3156 cx.spawn_in(window, async move |workspace, cx| {
3157 let buffer = create.await?;
3158 workspace.update_in(cx, |workspace, window, cx| {
3159 let editor =
3160 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
3161 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
3162 editor
3163 })
3164 })
3165 }
3166
3167 fn new_file_vertical(
3168 workspace: &mut Workspace,
3169 _: &workspace::NewFileSplitVertical,
3170 window: &mut Window,
3171 cx: &mut Context<Workspace>,
3172 ) {
3173 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
3174 }
3175
3176 fn new_file_horizontal(
3177 workspace: &mut Workspace,
3178 _: &workspace::NewFileSplitHorizontal,
3179 window: &mut Window,
3180 cx: &mut Context<Workspace>,
3181 ) {
3182 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
3183 }
3184
3185 fn new_file_split(
3186 workspace: &mut Workspace,
3187 action: &workspace::NewFileSplit,
3188 window: &mut Window,
3189 cx: &mut Context<Workspace>,
3190 ) {
3191 Self::new_file_in_direction(workspace, action.0, window, cx)
3192 }
3193
3194 fn new_file_in_direction(
3195 workspace: &mut Workspace,
3196 direction: SplitDirection,
3197 window: &mut Window,
3198 cx: &mut Context<Workspace>,
3199 ) {
3200 let project = workspace.project().clone();
3201 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
3202
3203 cx.spawn_in(window, async move |workspace, cx| {
3204 let buffer = create.await?;
3205 workspace.update_in(cx, move |workspace, window, cx| {
3206 workspace.split_item(
3207 direction,
3208 Box::new(
3209 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
3210 ),
3211 window,
3212 cx,
3213 )
3214 })?;
3215 anyhow::Ok(())
3216 })
3217 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
3218 match e.error_code() {
3219 ErrorCode::RemoteUpgradeRequired => Some(format!(
3220 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
3221 e.error_tag("required").unwrap_or("the latest version")
3222 )),
3223 _ => None,
3224 }
3225 });
3226 }
3227
3228 pub fn leader_id(&self) -> Option<CollaboratorId> {
3229 self.leader_id
3230 }
3231
3232 pub fn buffer(&self) -> &Entity<MultiBuffer> {
3233 &self.buffer
3234 }
3235
3236 pub fn project(&self) -> Option<&Entity<Project>> {
3237 self.project.as_ref()
3238 }
3239
3240 pub fn workspace(&self) -> Option<Entity<Workspace>> {
3241 self.workspace.as_ref()?.0.upgrade()
3242 }
3243
3244 /// Detaches a task and shows an error notification in the workspace if available,
3245 /// otherwise just logs the error.
3246 pub fn detach_and_notify_err<R, E>(
3247 &self,
3248 task: Task<Result<R, E>>,
3249 window: &mut Window,
3250 cx: &mut App,
3251 ) where
3252 E: std::fmt::Debug + std::fmt::Display + 'static,
3253 R: 'static,
3254 {
3255 if let Some(workspace) = self.workspace() {
3256 task.detach_and_notify_err(workspace.downgrade(), window, cx);
3257 } else {
3258 task.detach_and_log_err(cx);
3259 }
3260 }
3261
3262 /// Returns the workspace serialization ID if this editor should be serialized.
3263 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
3264 self.workspace
3265 .as_ref()
3266 .filter(|_| self.should_serialize_buffer())
3267 .and_then(|workspace| workspace.1)
3268 }
3269
3270 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
3271 self.buffer().read(cx).title(cx)
3272 }
3273
3274 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
3275 let git_blame_gutter_max_author_length = self
3276 .render_git_blame_gutter(cx)
3277 .then(|| {
3278 if let Some(blame) = self.blame.as_ref() {
3279 let max_author_length =
3280 blame.update(cx, |blame, cx| blame.max_author_length(cx));
3281 Some(max_author_length)
3282 } else {
3283 None
3284 }
3285 })
3286 .flatten();
3287
3288 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3289
3290 EditorSnapshot {
3291 mode: self.mode.clone(),
3292 show_gutter: self.show_gutter,
3293 offset_content: self.offset_content,
3294 show_line_numbers: self.show_line_numbers,
3295 number_deleted_lines: self.number_deleted_lines,
3296 show_git_diff_gutter: self.show_git_diff_gutter,
3297 semantic_tokens_enabled: self.semantic_token_state.enabled(),
3298 show_code_actions: self.show_code_actions,
3299 show_runnables: self.show_runnables,
3300 show_breakpoints: self.show_breakpoints,
3301 git_blame_gutter_max_author_length,
3302 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
3303 display_snapshot,
3304 placeholder_display_snapshot: self
3305 .placeholder_display_map
3306 .as_ref()
3307 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
3308 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
3309 is_focused: self.focus_handle.is_focused(window),
3310 current_line_highlight: self
3311 .current_line_highlight
3312 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
3313 gutter_hovered: self.gutter_hovered,
3314 }
3315 }
3316
3317 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
3318 self.buffer.read(cx).language_at(point, cx)
3319 }
3320
3321 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
3322 self.buffer.read(cx).read(cx).file_at(point).cloned()
3323 }
3324
3325 pub fn active_excerpt(
3326 &self,
3327 cx: &App,
3328 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
3329 self.buffer
3330 .read(cx)
3331 .excerpt_containing(self.selections.newest_anchor().head(), cx)
3332 }
3333
3334 pub fn mode(&self) -> &EditorMode {
3335 &self.mode
3336 }
3337
3338 pub fn set_mode(&mut self, mode: EditorMode) {
3339 self.mode = mode;
3340 }
3341
3342 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
3343 self.collaboration_hub.as_deref()
3344 }
3345
3346 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
3347 self.collaboration_hub = Some(hub);
3348 }
3349
3350 pub fn set_in_project_search(&mut self, in_project_search: bool) {
3351 self.in_project_search = in_project_search;
3352 }
3353
3354 pub fn set_custom_context_menu(
3355 &mut self,
3356 f: impl 'static
3357 + Fn(
3358 &mut Self,
3359 DisplayPoint,
3360 &mut Window,
3361 &mut Context<Self>,
3362 ) -> Option<Entity<ui::ContextMenu>>,
3363 ) {
3364 self.custom_context_menu = Some(Box::new(f))
3365 }
3366
3367 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
3368 self.completion_provider = provider;
3369 }
3370
3371 #[cfg(any(test, feature = "test-support"))]
3372 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
3373 self.completion_provider.clone()
3374 }
3375
3376 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
3377 self.semantics_provider.clone()
3378 }
3379
3380 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
3381 self.semantics_provider = provider;
3382 }
3383
3384 pub fn set_edit_prediction_provider<T>(
3385 &mut self,
3386 provider: Option<Entity<T>>,
3387 window: &mut Window,
3388 cx: &mut Context<Self>,
3389 ) where
3390 T: EditPredictionDelegate,
3391 {
3392 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionDelegate {
3393 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
3394 if this.focus_handle.is_focused(window) {
3395 this.update_visible_edit_prediction(window, cx);
3396 }
3397 }),
3398 provider: Arc::new(provider),
3399 });
3400 self.update_edit_prediction_settings(cx);
3401 self.refresh_edit_prediction(false, false, window, cx);
3402 }
3403
3404 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
3405 self.placeholder_display_map
3406 .as_ref()
3407 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
3408 }
3409
3410 pub fn set_placeholder_text(
3411 &mut self,
3412 placeholder_text: &str,
3413 window: &mut Window,
3414 cx: &mut Context<Self>,
3415 ) {
3416 let multibuffer = cx
3417 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3418
3419 let style = window.text_style();
3420
3421 self.placeholder_display_map = Some(cx.new(|cx| {
3422 DisplayMap::new(
3423 multibuffer,
3424 style.font(),
3425 style.font_size.to_pixels(window.rem_size()),
3426 None,
3427 FILE_HEADER_HEIGHT,
3428 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3429 Default::default(),
3430 DiagnosticSeverity::Off,
3431 cx,
3432 )
3433 }));
3434 cx.notify();
3435 }
3436
3437 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3438 self.cursor_shape = cursor_shape;
3439
3440 // Disrupt blink for immediate user feedback that the cursor shape has changed
3441 self.blink_manager.update(cx, BlinkManager::show_cursor);
3442
3443 cx.notify();
3444 }
3445
3446 pub fn cursor_shape(&self) -> CursorShape {
3447 self.cursor_shape
3448 }
3449
3450 pub fn set_cursor_offset_on_selection(&mut self, set_cursor_offset_on_selection: bool) {
3451 self.cursor_offset_on_selection = set_cursor_offset_on_selection;
3452 }
3453
3454 pub fn set_current_line_highlight(
3455 &mut self,
3456 current_line_highlight: Option<CurrentLineHighlight>,
3457 ) {
3458 self.current_line_highlight = current_line_highlight;
3459 }
3460
3461 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3462 self.collapse_matches = collapse_matches;
3463 }
3464
3465 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3466 if self.collapse_matches {
3467 return range.start..range.start;
3468 }
3469 range.clone()
3470 }
3471
3472 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3473 self.display_map.read(cx).clip_at_line_ends
3474 }
3475
3476 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3477 if self.display_map.read(cx).clip_at_line_ends != clip {
3478 self.display_map
3479 .update(cx, |map, _| map.clip_at_line_ends = clip);
3480 }
3481 }
3482
3483 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3484 self.input_enabled = input_enabled;
3485 }
3486
3487 pub fn set_expects_character_input(&mut self, expects_character_input: bool) {
3488 self.expects_character_input = expects_character_input;
3489 }
3490
3491 pub fn set_edit_predictions_hidden_for_vim_mode(
3492 &mut self,
3493 hidden: bool,
3494 window: &mut Window,
3495 cx: &mut Context<Self>,
3496 ) {
3497 if hidden != self.edit_predictions_hidden_for_vim_mode {
3498 self.edit_predictions_hidden_for_vim_mode = hidden;
3499 if hidden {
3500 self.update_visible_edit_prediction(window, cx);
3501 } else {
3502 self.refresh_edit_prediction(true, false, window, cx);
3503 }
3504 }
3505 }
3506
3507 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3508 self.menu_edit_predictions_policy = value;
3509 }
3510
3511 pub fn set_autoindent(&mut self, autoindent: bool) {
3512 if autoindent {
3513 self.autoindent_mode = Some(AutoindentMode::EachLine);
3514 } else {
3515 self.autoindent_mode = None;
3516 }
3517 }
3518
3519 pub fn capability(&self, cx: &App) -> Capability {
3520 if self.read_only {
3521 Capability::ReadOnly
3522 } else {
3523 self.buffer.read(cx).capability()
3524 }
3525 }
3526
3527 pub fn read_only(&self, cx: &App) -> bool {
3528 self.read_only || self.buffer.read(cx).read_only()
3529 }
3530
3531 pub fn set_read_only(&mut self, read_only: bool) {
3532 self.read_only = read_only;
3533 }
3534
3535 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3536 self.use_autoclose = autoclose;
3537 }
3538
3539 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3540 self.use_auto_surround = auto_surround;
3541 }
3542
3543 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3544 self.auto_replace_emoji_shortcode = auto_replace;
3545 }
3546
3547 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3548 self.buffer_serialization = should_serialize.then(|| {
3549 BufferSerialization::new(
3550 ProjectSettings::get_global(cx)
3551 .session
3552 .restore_unsaved_buffers,
3553 )
3554 })
3555 }
3556
3557 fn should_serialize_buffer(&self) -> bool {
3558 self.buffer_serialization.is_some()
3559 }
3560
3561 pub fn toggle_edit_predictions(
3562 &mut self,
3563 _: &ToggleEditPrediction,
3564 window: &mut Window,
3565 cx: &mut Context<Self>,
3566 ) {
3567 if self.show_edit_predictions_override.is_some() {
3568 self.set_show_edit_predictions(None, window, cx);
3569 } else {
3570 let show_edit_predictions = !self.edit_predictions_enabled();
3571 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3572 }
3573 }
3574
3575 pub fn set_show_completions_on_input(&mut self, show_completions_on_input: Option<bool>) {
3576 self.show_completions_on_input_override = show_completions_on_input;
3577 }
3578
3579 pub fn set_show_edit_predictions(
3580 &mut self,
3581 show_edit_predictions: Option<bool>,
3582 window: &mut Window,
3583 cx: &mut Context<Self>,
3584 ) {
3585 self.show_edit_predictions_override = show_edit_predictions;
3586 self.update_edit_prediction_settings(cx);
3587
3588 if let Some(false) = show_edit_predictions {
3589 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
3590 } else {
3591 self.refresh_edit_prediction(false, true, window, cx);
3592 }
3593 }
3594
3595 fn edit_predictions_disabled_in_scope(
3596 &self,
3597 buffer: &Entity<Buffer>,
3598 buffer_position: language::Anchor,
3599 cx: &App,
3600 ) -> bool {
3601 let snapshot = buffer.read(cx).snapshot();
3602 let settings = snapshot.settings_at(buffer_position, cx);
3603
3604 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3605 return false;
3606 };
3607
3608 scope.override_name().is_some_and(|scope_name| {
3609 settings
3610 .edit_predictions_disabled_in
3611 .iter()
3612 .any(|s| s == scope_name)
3613 })
3614 }
3615
3616 pub fn set_use_modal_editing(&mut self, to: bool) {
3617 self.use_modal_editing = to;
3618 }
3619
3620 pub fn use_modal_editing(&self) -> bool {
3621 self.use_modal_editing
3622 }
3623
3624 fn selections_did_change(
3625 &mut self,
3626 local: bool,
3627 old_cursor_position: &Anchor,
3628 effects: SelectionEffects,
3629 window: &mut Window,
3630 cx: &mut Context<Self>,
3631 ) {
3632 window.invalidate_character_coordinates();
3633
3634 // Copy selections to primary selection buffer
3635 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3636 if local {
3637 let selections = self
3638 .selections
3639 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3640 let buffer_handle = self.buffer.read(cx).read(cx);
3641
3642 let mut text = String::new();
3643 for (index, selection) in selections.iter().enumerate() {
3644 let text_for_selection = buffer_handle
3645 .text_for_range(selection.start..selection.end)
3646 .collect::<String>();
3647
3648 text.push_str(&text_for_selection);
3649 if index != selections.len() - 1 {
3650 text.push('\n');
3651 }
3652 }
3653
3654 if !text.is_empty() {
3655 cx.write_to_primary(ClipboardItem::new_string(text));
3656 }
3657 }
3658
3659 let selection_anchors = self.selections.disjoint_anchors_arc();
3660
3661 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3662 self.buffer.update(cx, |buffer, cx| {
3663 buffer.set_active_selections(
3664 &selection_anchors,
3665 self.selections.line_mode(),
3666 self.cursor_shape,
3667 cx,
3668 )
3669 });
3670 }
3671 let display_map = self
3672 .display_map
3673 .update(cx, |display_map, cx| display_map.snapshot(cx));
3674 let buffer = display_map.buffer_snapshot();
3675 if self.selections.count() == 1 {
3676 self.add_selections_state = None;
3677 }
3678 self.select_next_state = None;
3679 self.select_prev_state = None;
3680 self.select_syntax_node_history.try_clear();
3681 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3682 self.snippet_stack.invalidate(&selection_anchors, buffer);
3683 self.take_rename(false, window, cx);
3684
3685 let newest_selection = self.selections.newest_anchor();
3686 let new_cursor_position = newest_selection.head();
3687 let selection_start = newest_selection.start;
3688
3689 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3690 self.push_to_nav_history(
3691 *old_cursor_position,
3692 Some(new_cursor_position.to_point(buffer)),
3693 false,
3694 effects.nav_history == Some(true),
3695 cx,
3696 );
3697 }
3698
3699 if local {
3700 if let Some(buffer_id) = new_cursor_position.text_anchor.buffer_id {
3701 self.register_buffer(buffer_id, cx);
3702 }
3703
3704 let mut context_menu = self.context_menu.borrow_mut();
3705 let completion_menu = match context_menu.as_ref() {
3706 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3707 Some(CodeContextMenu::CodeActions(_)) => {
3708 *context_menu = None;
3709 None
3710 }
3711 None => None,
3712 };
3713 let completion_position = completion_menu.map(|menu| menu.initial_position);
3714 drop(context_menu);
3715
3716 if effects.completions
3717 && let Some(completion_position) = completion_position
3718 {
3719 let start_offset = selection_start.to_offset(buffer);
3720 let position_matches = start_offset == completion_position.to_offset(buffer);
3721 let continue_showing = if let Some((snap, ..)) =
3722 buffer.point_to_buffer_offset(completion_position)
3723 && !snap.capability.editable()
3724 {
3725 false
3726 } else if position_matches {
3727 if self.snippet_stack.is_empty() {
3728 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3729 == Some(CharKind::Word)
3730 } else {
3731 // Snippet choices can be shown even when the cursor is in whitespace.
3732 // Dismissing the menu with actions like backspace is handled by
3733 // invalidation regions.
3734 true
3735 }
3736 } else {
3737 false
3738 };
3739
3740 if continue_showing {
3741 self.open_or_update_completions_menu(None, None, false, window, cx);
3742 } else {
3743 self.hide_context_menu(window, cx);
3744 }
3745 }
3746
3747 hide_hover(self, cx);
3748
3749 if old_cursor_position.to_display_point(&display_map).row()
3750 != new_cursor_position.to_display_point(&display_map).row()
3751 {
3752 self.available_code_actions.take();
3753 }
3754 self.refresh_code_actions(window, cx);
3755 self.refresh_document_highlights(cx);
3756 refresh_linked_ranges(self, window, cx);
3757
3758 self.refresh_selected_text_highlights(&display_map, false, window, cx);
3759 self.refresh_matching_bracket_highlights(&display_map, cx);
3760 self.refresh_outline_symbols_at_cursor(cx);
3761 self.update_visible_edit_prediction(window, cx);
3762 self.inline_blame_popover.take();
3763 if self.git_blame_inline_enabled {
3764 self.start_inline_blame_timer(window, cx);
3765 }
3766 }
3767
3768 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3769
3770 if local && !self.suppress_selection_callback {
3771 if let Some(callback) = self.on_local_selections_changed.as_ref() {
3772 let cursor_position = self.selections.newest::<Point>(&display_map).head();
3773 callback(cursor_position, window, cx);
3774 }
3775 }
3776
3777 cx.emit(EditorEvent::SelectionsChanged { local });
3778
3779 let selections = &self.selections.disjoint_anchors_arc();
3780 if selections.len() == 1 {
3781 cx.emit(SearchEvent::ActiveMatchChanged)
3782 }
3783 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3784 let inmemory_selections = selections
3785 .iter()
3786 .map(|s| {
3787 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3788 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3789 })
3790 .collect();
3791 self.update_restoration_data(cx, |data| {
3792 data.selections = inmemory_selections;
3793 });
3794
3795 if WorkspaceSettings::get(None, cx).restore_on_startup
3796 != RestoreOnStartupBehavior::EmptyTab
3797 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3798 {
3799 let snapshot = self.buffer().read(cx).snapshot(cx);
3800 let selections = selections.clone();
3801 let background_executor = cx.background_executor().clone();
3802 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3803 let db = EditorDb::global(cx);
3804 self.serialize_selections = cx.background_spawn(async move {
3805 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3806 let db_selections = selections
3807 .iter()
3808 .map(|selection| {
3809 (
3810 selection.start.to_offset(&snapshot).0,
3811 selection.end.to_offset(&snapshot).0,
3812 )
3813 })
3814 .collect();
3815
3816 db.save_editor_selections(editor_id, workspace_id, db_selections)
3817 .await
3818 .with_context(|| {
3819 format!(
3820 "persisting editor selections for editor {editor_id}, \
3821 workspace {workspace_id:?}"
3822 )
3823 })
3824 .log_err();
3825 });
3826 }
3827 }
3828
3829 cx.notify();
3830 }
3831
3832 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3833 use text::ToOffset as _;
3834 use text::ToPoint as _;
3835
3836 if self.mode.is_minimap()
3837 || WorkspaceSettings::get(None, cx).restore_on_startup
3838 == RestoreOnStartupBehavior::EmptyTab
3839 {
3840 return;
3841 }
3842
3843 if !self.buffer().read(cx).is_singleton() {
3844 return;
3845 }
3846
3847 let display_snapshot = self
3848 .display_map
3849 .update(cx, |display_map, cx| display_map.snapshot(cx));
3850 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3851 return;
3852 };
3853 let inmemory_folds = display_snapshot
3854 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3855 .map(|fold| {
3856 fold.range.start.text_anchor.to_point(&snapshot)
3857 ..fold.range.end.text_anchor.to_point(&snapshot)
3858 })
3859 .collect();
3860 self.update_restoration_data(cx, |data| {
3861 data.folds = inmemory_folds;
3862 });
3863
3864 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3865 return;
3866 };
3867
3868 // Get file path for path-based fold storage (survives tab close)
3869 let Some(file_path) = self.buffer().read(cx).as_singleton().and_then(|buffer| {
3870 project::File::from_dyn(buffer.read(cx).file())
3871 .map(|file| Arc::<Path>::from(file.abs_path(cx)))
3872 }) else {
3873 return;
3874 };
3875
3876 let background_executor = cx.background_executor().clone();
3877 const FINGERPRINT_LEN: usize = 32;
3878 let db_folds = display_snapshot
3879 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3880 .map(|fold| {
3881 let start = fold.range.start.text_anchor.to_offset(&snapshot);
3882 let end = fold.range.end.text_anchor.to_offset(&snapshot);
3883
3884 // Extract fingerprints - content at fold boundaries for validation on restore
3885 // Both fingerprints must be INSIDE the fold to avoid capturing surrounding
3886 // content that might change independently.
3887 // start_fp: first min(32, fold_len) bytes of fold content
3888 // end_fp: last min(32, fold_len) bytes of fold content
3889 // Clip to character boundaries to handle multibyte UTF-8 characters.
3890 let fold_len = end - start;
3891 let start_fp_end = snapshot
3892 .clip_offset(start + std::cmp::min(FINGERPRINT_LEN, fold_len), Bias::Left);
3893 let start_fp: String = snapshot.text_for_range(start..start_fp_end).collect();
3894 let end_fp_start = snapshot
3895 .clip_offset(end.saturating_sub(FINGERPRINT_LEN).max(start), Bias::Right);
3896 let end_fp: String = snapshot.text_for_range(end_fp_start..end).collect();
3897
3898 (start, end, start_fp, end_fp)
3899 })
3900 .collect::<Vec<_>>();
3901 let db = EditorDb::global(cx);
3902 self.serialize_folds = cx.background_spawn(async move {
3903 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3904 if db_folds.is_empty() {
3905 // No folds - delete any persisted folds for this file
3906 db.delete_file_folds(workspace_id, file_path)
3907 .await
3908 .with_context(|| format!("deleting file folds for workspace {workspace_id:?}"))
3909 .log_err();
3910 } else {
3911 db.save_file_folds(workspace_id, file_path, db_folds)
3912 .await
3913 .with_context(|| {
3914 format!("persisting file folds for workspace {workspace_id:?}")
3915 })
3916 .log_err();
3917 }
3918 });
3919 }
3920
3921 pub fn sync_selections(
3922 &mut self,
3923 other: Entity<Editor>,
3924 cx: &mut Context<Self>,
3925 ) -> gpui::Subscription {
3926 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3927 if !other_selections.is_empty() {
3928 self.selections
3929 .change_with(&self.display_snapshot(cx), |selections| {
3930 selections.select_anchors(other_selections);
3931 });
3932 }
3933
3934 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3935 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3936 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3937 if other_selections.is_empty() {
3938 return;
3939 }
3940 let snapshot = this.display_snapshot(cx);
3941 this.selections.change_with(&snapshot, |selections| {
3942 selections.select_anchors(other_selections);
3943 });
3944 }
3945 });
3946
3947 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3948 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3949 let these_selections = this.selections.disjoint_anchors().to_vec();
3950 if these_selections.is_empty() {
3951 return;
3952 }
3953 other.update(cx, |other_editor, cx| {
3954 let snapshot = other_editor.display_snapshot(cx);
3955 other_editor
3956 .selections
3957 .change_with(&snapshot, |selections| {
3958 selections.select_anchors(these_selections);
3959 })
3960 });
3961 }
3962 });
3963
3964 Subscription::join(other_subscription, this_subscription)
3965 }
3966
3967 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3968 if self.buffer().read(cx).is_singleton() {
3969 return;
3970 }
3971 let snapshot = self.buffer.read(cx).snapshot(cx);
3972 let buffer_ids: HashSet<BufferId> = self
3973 .selections
3974 .disjoint_anchor_ranges()
3975 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3976 .collect();
3977 for buffer_id in buffer_ids {
3978 self.unfold_buffer(buffer_id, cx);
3979 }
3980 }
3981
3982 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3983 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3984 /// effects of selection change occur at the end of the transaction.
3985 pub fn change_selections<R>(
3986 &mut self,
3987 effects: SelectionEffects,
3988 window: &mut Window,
3989 cx: &mut Context<Self>,
3990 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3991 ) -> R {
3992 let snapshot = self.display_snapshot(cx);
3993 if let Some(state) = &mut self.deferred_selection_effects_state {
3994 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3995 state.effects.completions = effects.completions;
3996 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3997 let (changed, result) = self.selections.change_with(&snapshot, change);
3998 state.changed |= changed;
3999 return result;
4000 }
4001 let mut state = DeferredSelectionEffectsState {
4002 changed: false,
4003 effects,
4004 old_cursor_position: self.selections.newest_anchor().head(),
4005 history_entry: SelectionHistoryEntry {
4006 selections: self.selections.disjoint_anchors_arc(),
4007 select_next_state: self.select_next_state.clone(),
4008 select_prev_state: self.select_prev_state.clone(),
4009 add_selections_state: self.add_selections_state.clone(),
4010 },
4011 };
4012 let (changed, result) = self.selections.change_with(&snapshot, change);
4013 state.changed = state.changed || changed;
4014 if self.defer_selection_effects {
4015 self.deferred_selection_effects_state = Some(state);
4016 } else {
4017 self.apply_selection_effects(state, window, cx);
4018 }
4019 result
4020 }
4021
4022 /// Defers the effects of selection change, so that the effects of multiple calls to
4023 /// `change_selections` are applied at the end. This way these intermediate states aren't added
4024 /// to selection history and the state of popovers based on selection position aren't
4025 /// erroneously updated.
4026 pub fn with_selection_effects_deferred<R>(
4027 &mut self,
4028 window: &mut Window,
4029 cx: &mut Context<Self>,
4030 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
4031 ) -> R {
4032 let already_deferred = self.defer_selection_effects;
4033 self.defer_selection_effects = true;
4034 let result = update(self, window, cx);
4035 if !already_deferred {
4036 self.defer_selection_effects = false;
4037 if let Some(state) = self.deferred_selection_effects_state.take() {
4038 self.apply_selection_effects(state, window, cx);
4039 }
4040 }
4041 result
4042 }
4043
4044 fn apply_selection_effects(
4045 &mut self,
4046 state: DeferredSelectionEffectsState,
4047 window: &mut Window,
4048 cx: &mut Context<Self>,
4049 ) {
4050 if state.changed {
4051 self.selection_history.push(state.history_entry);
4052
4053 if let Some(autoscroll) = state.effects.scroll {
4054 self.request_autoscroll(autoscroll, cx);
4055 }
4056
4057 let old_cursor_position = &state.old_cursor_position;
4058
4059 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
4060
4061 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
4062 self.show_signature_help_auto(window, cx);
4063 }
4064 }
4065 }
4066
4067 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
4068 where
4069 I: IntoIterator<Item = (Range<S>, T)>,
4070 S: ToOffset,
4071 T: Into<Arc<str>>,
4072 {
4073 if self.read_only(cx) {
4074 return;
4075 }
4076
4077 self.buffer
4078 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
4079 }
4080
4081 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
4082 where
4083 I: IntoIterator<Item = (Range<S>, T)>,
4084 S: ToOffset,
4085 T: Into<Arc<str>>,
4086 {
4087 if self.read_only(cx) {
4088 return;
4089 }
4090
4091 self.buffer.update(cx, |buffer, cx| {
4092 buffer.edit(edits, self.autoindent_mode.clone(), cx)
4093 });
4094 }
4095
4096 pub fn edit_with_block_indent<I, S, T>(
4097 &mut self,
4098 edits: I,
4099 original_indent_columns: Vec<Option<u32>>,
4100 cx: &mut Context<Self>,
4101 ) where
4102 I: IntoIterator<Item = (Range<S>, T)>,
4103 S: ToOffset,
4104 T: Into<Arc<str>>,
4105 {
4106 if self.read_only(cx) {
4107 return;
4108 }
4109
4110 self.buffer.update(cx, |buffer, cx| {
4111 buffer.edit(
4112 edits,
4113 Some(AutoindentMode::Block {
4114 original_indent_columns,
4115 }),
4116 cx,
4117 )
4118 });
4119 }
4120
4121 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
4122 self.hide_context_menu(window, cx);
4123
4124 match phase {
4125 SelectPhase::Begin {
4126 position,
4127 add,
4128 click_count,
4129 } => self.begin_selection(position, add, click_count, window, cx),
4130 SelectPhase::BeginColumnar {
4131 position,
4132 goal_column,
4133 reset,
4134 mode,
4135 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
4136 SelectPhase::Extend {
4137 position,
4138 click_count,
4139 } => self.extend_selection(position, click_count, window, cx),
4140 SelectPhase::Update {
4141 position,
4142 goal_column,
4143 scroll_delta,
4144 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
4145 SelectPhase::End => self.end_selection(window, cx),
4146 }
4147 }
4148
4149 fn extend_selection(
4150 &mut self,
4151 position: DisplayPoint,
4152 click_count: usize,
4153 window: &mut Window,
4154 cx: &mut Context<Self>,
4155 ) {
4156 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4157 let tail = self
4158 .selections
4159 .newest::<MultiBufferOffset>(&display_map)
4160 .tail();
4161 let click_count = click_count.max(match self.selections.select_mode() {
4162 SelectMode::Character => 1,
4163 SelectMode::Word(_) => 2,
4164 SelectMode::Line(_) => 3,
4165 SelectMode::All => 4,
4166 });
4167 self.begin_selection(position, false, click_count, window, cx);
4168
4169 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
4170
4171 let current_selection = match self.selections.select_mode() {
4172 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
4173 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
4174 };
4175
4176 let mut pending_selection = self
4177 .selections
4178 .pending_anchor()
4179 .cloned()
4180 .expect("extend_selection not called with pending selection");
4181
4182 if pending_selection
4183 .start
4184 .cmp(¤t_selection.start, display_map.buffer_snapshot())
4185 == Ordering::Greater
4186 {
4187 pending_selection.start = current_selection.start;
4188 }
4189 if pending_selection
4190 .end
4191 .cmp(¤t_selection.end, display_map.buffer_snapshot())
4192 == Ordering::Less
4193 {
4194 pending_selection.end = current_selection.end;
4195 pending_selection.reversed = true;
4196 }
4197
4198 let mut pending_mode = self.selections.pending_mode().unwrap();
4199 match &mut pending_mode {
4200 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
4201 _ => {}
4202 }
4203
4204 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
4205 SelectionEffects::scroll(Autoscroll::fit())
4206 } else {
4207 SelectionEffects::no_scroll()
4208 };
4209
4210 self.change_selections(effects, window, cx, |s| {
4211 s.set_pending(pending_selection.clone(), pending_mode);
4212 s.set_is_extending(true);
4213 });
4214 }
4215
4216 fn begin_selection(
4217 &mut self,
4218 position: DisplayPoint,
4219 add: bool,
4220 click_count: usize,
4221 window: &mut Window,
4222 cx: &mut Context<Self>,
4223 ) {
4224 if !self.focus_handle.is_focused(window) {
4225 self.last_focused_descendant = None;
4226 window.focus(&self.focus_handle, cx);
4227 }
4228
4229 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4230 let buffer = display_map.buffer_snapshot();
4231 let position = display_map.clip_point(position, Bias::Left);
4232
4233 let start;
4234 let end;
4235 let mode;
4236 let mut auto_scroll;
4237 match click_count {
4238 1 => {
4239 start = buffer.anchor_before(position.to_point(&display_map));
4240 end = start;
4241 mode = SelectMode::Character;
4242 auto_scroll = true;
4243 }
4244 2 => {
4245 let position = display_map
4246 .clip_point(position, Bias::Left)
4247 .to_offset(&display_map, Bias::Left);
4248 let (range, _) = buffer.surrounding_word(position, None);
4249 start = buffer.anchor_before(range.start);
4250 end = buffer.anchor_before(range.end);
4251 mode = SelectMode::Word(start..end);
4252 auto_scroll = true;
4253 }
4254 3 => {
4255 let position = display_map
4256 .clip_point(position, Bias::Left)
4257 .to_point(&display_map);
4258 let line_start = display_map.prev_line_boundary(position).0;
4259 let next_line_start = buffer.clip_point(
4260 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4261 Bias::Left,
4262 );
4263 start = buffer.anchor_before(line_start);
4264 end = buffer.anchor_before(next_line_start);
4265 mode = SelectMode::Line(start..end);
4266 auto_scroll = true;
4267 }
4268 _ => {
4269 start = buffer.anchor_before(MultiBufferOffset(0));
4270 end = buffer.anchor_before(buffer.len());
4271 mode = SelectMode::All;
4272 auto_scroll = false;
4273 }
4274 }
4275 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
4276
4277 let point_to_delete: Option<usize> = {
4278 let selected_points: Vec<Selection<Point>> =
4279 self.selections.disjoint_in_range(start..end, &display_map);
4280
4281 if !add || click_count > 1 {
4282 None
4283 } else if !selected_points.is_empty() {
4284 Some(selected_points[0].id)
4285 } else {
4286 let clicked_point_already_selected =
4287 self.selections.disjoint_anchors().iter().find(|selection| {
4288 selection.start.to_point(buffer) == start.to_point(buffer)
4289 || selection.end.to_point(buffer) == end.to_point(buffer)
4290 });
4291
4292 clicked_point_already_selected.map(|selection| selection.id)
4293 }
4294 };
4295
4296 let selections_count = self.selections.count();
4297 let effects = if auto_scroll {
4298 SelectionEffects::default()
4299 } else {
4300 SelectionEffects::no_scroll()
4301 };
4302
4303 self.change_selections(effects, window, cx, |s| {
4304 if let Some(point_to_delete) = point_to_delete {
4305 s.delete(point_to_delete);
4306
4307 if selections_count == 1 {
4308 s.set_pending_anchor_range(start..end, mode);
4309 }
4310 } else {
4311 if !add {
4312 s.clear_disjoint();
4313 }
4314
4315 s.set_pending_anchor_range(start..end, mode);
4316 }
4317 });
4318 }
4319
4320 fn begin_columnar_selection(
4321 &mut self,
4322 position: DisplayPoint,
4323 goal_column: u32,
4324 reset: bool,
4325 mode: ColumnarMode,
4326 window: &mut Window,
4327 cx: &mut Context<Self>,
4328 ) {
4329 if !self.focus_handle.is_focused(window) {
4330 self.last_focused_descendant = None;
4331 window.focus(&self.focus_handle, cx);
4332 }
4333
4334 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4335
4336 if reset {
4337 let pointer_position = display_map
4338 .buffer_snapshot()
4339 .anchor_before(position.to_point(&display_map));
4340
4341 self.change_selections(
4342 SelectionEffects::scroll(Autoscroll::newest()),
4343 window,
4344 cx,
4345 |s| {
4346 s.clear_disjoint();
4347 s.set_pending_anchor_range(
4348 pointer_position..pointer_position,
4349 SelectMode::Character,
4350 );
4351 },
4352 );
4353 };
4354
4355 let tail = self.selections.newest::<Point>(&display_map).tail();
4356 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
4357 self.columnar_selection_state = match mode {
4358 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
4359 selection_tail: selection_anchor,
4360 display_point: if reset {
4361 if position.column() != goal_column {
4362 Some(DisplayPoint::new(position.row(), goal_column))
4363 } else {
4364 None
4365 }
4366 } else {
4367 None
4368 },
4369 }),
4370 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
4371 selection_tail: selection_anchor,
4372 }),
4373 };
4374
4375 if !reset {
4376 self.select_columns(position, goal_column, &display_map, window, cx);
4377 }
4378 }
4379
4380 fn update_selection(
4381 &mut self,
4382 position: DisplayPoint,
4383 goal_column: u32,
4384 scroll_delta: gpui::Point<f32>,
4385 window: &mut Window,
4386 cx: &mut Context<Self>,
4387 ) {
4388 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4389
4390 if self.columnar_selection_state.is_some() {
4391 self.select_columns(position, goal_column, &display_map, window, cx);
4392 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
4393 let buffer = display_map.buffer_snapshot();
4394 let head;
4395 let tail;
4396 let mode = self.selections.pending_mode().unwrap();
4397 match &mode {
4398 SelectMode::Character => {
4399 head = position.to_point(&display_map);
4400 tail = pending.tail().to_point(buffer);
4401 }
4402 SelectMode::Word(original_range) => {
4403 let offset = display_map
4404 .clip_point(position, Bias::Left)
4405 .to_offset(&display_map, Bias::Left);
4406 let original_range = original_range.to_offset(buffer);
4407
4408 let head_offset = if buffer.is_inside_word(offset, None)
4409 || original_range.contains(&offset)
4410 {
4411 let (word_range, _) = buffer.surrounding_word(offset, None);
4412 if word_range.start < original_range.start {
4413 word_range.start
4414 } else {
4415 word_range.end
4416 }
4417 } else {
4418 offset
4419 };
4420
4421 head = head_offset.to_point(buffer);
4422 if head_offset <= original_range.start {
4423 tail = original_range.end.to_point(buffer);
4424 } else {
4425 tail = original_range.start.to_point(buffer);
4426 }
4427 }
4428 SelectMode::Line(original_range) => {
4429 let original_range = original_range.to_point(display_map.buffer_snapshot());
4430
4431 let position = display_map
4432 .clip_point(position, Bias::Left)
4433 .to_point(&display_map);
4434 let line_start = display_map.prev_line_boundary(position).0;
4435 let next_line_start = buffer.clip_point(
4436 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4437 Bias::Left,
4438 );
4439
4440 if line_start < original_range.start {
4441 head = line_start
4442 } else {
4443 head = next_line_start
4444 }
4445
4446 if head <= original_range.start {
4447 tail = original_range.end;
4448 } else {
4449 tail = original_range.start;
4450 }
4451 }
4452 SelectMode::All => {
4453 return;
4454 }
4455 };
4456
4457 if head < tail {
4458 pending.start = buffer.anchor_before(head);
4459 pending.end = buffer.anchor_before(tail);
4460 pending.reversed = true;
4461 } else {
4462 pending.start = buffer.anchor_before(tail);
4463 pending.end = buffer.anchor_before(head);
4464 pending.reversed = false;
4465 }
4466
4467 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4468 s.set_pending(pending.clone(), mode);
4469 });
4470 } else {
4471 log::error!("update_selection dispatched with no pending selection");
4472 return;
4473 }
4474
4475 self.apply_scroll_delta(scroll_delta, window, cx);
4476 cx.notify();
4477 }
4478
4479 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4480 self.columnar_selection_state.take();
4481 if let Some(pending_mode) = self.selections.pending_mode() {
4482 let selections = self
4483 .selections
4484 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4485 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4486 s.select(selections);
4487 s.clear_pending();
4488 if s.is_extending() {
4489 s.set_is_extending(false);
4490 } else {
4491 s.set_select_mode(pending_mode);
4492 }
4493 });
4494 }
4495 }
4496
4497 fn select_columns(
4498 &mut self,
4499 head: DisplayPoint,
4500 goal_column: u32,
4501 display_map: &DisplaySnapshot,
4502 window: &mut Window,
4503 cx: &mut Context<Self>,
4504 ) {
4505 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4506 return;
4507 };
4508
4509 let tail = match columnar_state {
4510 ColumnarSelectionState::FromMouse {
4511 selection_tail,
4512 display_point,
4513 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4514 ColumnarSelectionState::FromSelection { selection_tail } => {
4515 selection_tail.to_display_point(display_map)
4516 }
4517 };
4518
4519 let start_row = cmp::min(tail.row(), head.row());
4520 let end_row = cmp::max(tail.row(), head.row());
4521 let start_column = cmp::min(tail.column(), goal_column);
4522 let end_column = cmp::max(tail.column(), goal_column);
4523 let reversed = start_column < tail.column();
4524
4525 let selection_ranges = (start_row.0..=end_row.0)
4526 .map(DisplayRow)
4527 .filter_map(|row| {
4528 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4529 || start_column <= display_map.line_len(row))
4530 && !display_map.is_block_line(row)
4531 {
4532 let start = display_map
4533 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4534 .to_point(display_map);
4535 let end = display_map
4536 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4537 .to_point(display_map);
4538 if reversed {
4539 Some(end..start)
4540 } else {
4541 Some(start..end)
4542 }
4543 } else {
4544 None
4545 }
4546 })
4547 .collect::<Vec<_>>();
4548 if selection_ranges.is_empty() {
4549 return;
4550 }
4551
4552 let ranges = match columnar_state {
4553 ColumnarSelectionState::FromMouse { .. } => {
4554 let mut non_empty_ranges = selection_ranges
4555 .iter()
4556 .filter(|selection_range| selection_range.start != selection_range.end)
4557 .peekable();
4558 if non_empty_ranges.peek().is_some() {
4559 non_empty_ranges.cloned().collect()
4560 } else {
4561 selection_ranges
4562 }
4563 }
4564 _ => selection_ranges,
4565 };
4566
4567 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4568 s.select_ranges(ranges);
4569 });
4570 cx.notify();
4571 }
4572
4573 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4574 self.selections
4575 .all_adjusted(snapshot)
4576 .iter()
4577 .any(|selection| !selection.is_empty())
4578 }
4579
4580 pub fn has_pending_nonempty_selection(&self) -> bool {
4581 let pending_nonempty_selection = match self.selections.pending_anchor() {
4582 Some(Selection { start, end, .. }) => start != end,
4583 None => false,
4584 };
4585
4586 pending_nonempty_selection
4587 || (self.columnar_selection_state.is_some()
4588 && self.selections.disjoint_anchors().len() > 1)
4589 }
4590
4591 pub fn has_pending_selection(&self) -> bool {
4592 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4593 }
4594
4595 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4596 self.selection_mark_mode = false;
4597 self.selection_drag_state = SelectionDragState::None;
4598
4599 if self.dismiss_menus_and_popups(true, window, cx) {
4600 cx.notify();
4601 return;
4602 }
4603 if self.clear_expanded_diff_hunks(cx) {
4604 cx.notify();
4605 return;
4606 }
4607 if self.show_git_blame_gutter {
4608 self.show_git_blame_gutter = false;
4609 cx.notify();
4610 return;
4611 }
4612
4613 if self.mode.is_full()
4614 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4615 {
4616 cx.notify();
4617 return;
4618 }
4619
4620 cx.propagate();
4621 }
4622
4623 pub fn dismiss_menus_and_popups(
4624 &mut self,
4625 is_user_requested: bool,
4626 window: &mut Window,
4627 cx: &mut Context<Self>,
4628 ) -> bool {
4629 let mut dismissed = false;
4630
4631 dismissed |= self.take_rename(false, window, cx).is_some();
4632 dismissed |= self.hide_blame_popover(true, cx);
4633 dismissed |= hide_hover(self, cx);
4634 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4635 dismissed |= self.hide_context_menu(window, cx).is_some();
4636 dismissed |= self.mouse_context_menu.take().is_some();
4637 dismissed |= is_user_requested
4638 && self.discard_edit_prediction(EditPredictionDiscardReason::Rejected, cx);
4639 dismissed |= self.snippet_stack.pop().is_some();
4640 if self.diff_review_drag_state.is_some() {
4641 self.cancel_diff_review_drag(cx);
4642 dismissed = true;
4643 }
4644 if !self.diff_review_overlays.is_empty() {
4645 self.dismiss_all_diff_review_overlays(cx);
4646 dismissed = true;
4647 }
4648
4649 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4650 self.dismiss_diagnostics(cx);
4651 dismissed = true;
4652 }
4653
4654 dismissed
4655 }
4656
4657 fn linked_editing_ranges_for(
4658 &self,
4659 selection: Range<text::Anchor>,
4660 cx: &App,
4661 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4662 if self.linked_edit_ranges.is_empty() {
4663 return None;
4664 }
4665 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4666 selection.end.buffer_id.and_then(|end_buffer_id| {
4667 if selection.start.buffer_id != Some(end_buffer_id) {
4668 return None;
4669 }
4670 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4671 let snapshot = buffer.read(cx).snapshot();
4672 self.linked_edit_ranges
4673 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4674 .map(|ranges| (ranges, snapshot, buffer))
4675 })?;
4676 use text::ToOffset as TO;
4677 // find offset from the start of current range to current cursor position
4678 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4679
4680 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4681 let start_difference = start_offset - start_byte_offset;
4682 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4683 let end_difference = end_offset - start_byte_offset;
4684
4685 // Current range has associated linked ranges.
4686 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4687 for range in linked_ranges.iter() {
4688 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4689 let end_offset = start_offset + end_difference;
4690 let start_offset = start_offset + start_difference;
4691 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4692 continue;
4693 }
4694 if self.selections.disjoint_anchor_ranges().any(|s| {
4695 if s.start.text_anchor.buffer_id != selection.start.buffer_id
4696 || s.end.text_anchor.buffer_id != selection.end.buffer_id
4697 {
4698 return false;
4699 }
4700 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4701 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4702 }) {
4703 continue;
4704 }
4705 let start = buffer_snapshot.anchor_after(start_offset);
4706 let end = buffer_snapshot.anchor_after(end_offset);
4707 linked_edits
4708 .entry(buffer.clone())
4709 .or_default()
4710 .push(start..end);
4711 }
4712 Some(linked_edits)
4713 }
4714
4715 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4716 let text: Arc<str> = text.into();
4717
4718 if self.read_only(cx) {
4719 return;
4720 }
4721
4722 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4723
4724 self.unfold_buffers_with_selections(cx);
4725
4726 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4727 let mut bracket_inserted = false;
4728 let mut edits = Vec::new();
4729 let mut linked_edits = LinkedEdits::new();
4730 let mut new_selections = Vec::with_capacity(selections.len());
4731 let mut new_autoclose_regions = Vec::new();
4732 let snapshot = self.buffer.read(cx).read(cx);
4733 let mut clear_linked_edit_ranges = false;
4734 let mut all_selections_read_only = true;
4735 let mut has_adjacent_edits = false;
4736 let mut in_adjacent_group = false;
4737
4738 let mut regions = self
4739 .selections_with_autoclose_regions(selections, &snapshot)
4740 .peekable();
4741
4742 while let Some((selection, autoclose_region)) = regions.next() {
4743 if snapshot
4744 .point_to_buffer_point(selection.head())
4745 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4746 {
4747 continue;
4748 }
4749 if snapshot
4750 .point_to_buffer_point(selection.tail())
4751 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4752 {
4753 // note, ideally we'd clip the tail to the closest writeable region towards the head
4754 continue;
4755 }
4756 all_selections_read_only = false;
4757
4758 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4759 // Determine if the inserted text matches the opening or closing
4760 // bracket of any of this language's bracket pairs.
4761 let mut bracket_pair = None;
4762 let mut is_bracket_pair_start = false;
4763 let mut is_bracket_pair_end = false;
4764 if !text.is_empty() {
4765 let mut bracket_pair_matching_end = None;
4766 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4767 // and they are removing the character that triggered IME popup.
4768 for (pair, enabled) in scope.brackets() {
4769 if !pair.close && !pair.surround {
4770 continue;
4771 }
4772
4773 if enabled && pair.start.ends_with(text.as_ref()) {
4774 let prefix_len = pair.start.len() - text.len();
4775 let preceding_text_matches_prefix = prefix_len == 0
4776 || (selection.start.column >= (prefix_len as u32)
4777 && snapshot.contains_str_at(
4778 Point::new(
4779 selection.start.row,
4780 selection.start.column - (prefix_len as u32),
4781 ),
4782 &pair.start[..prefix_len],
4783 ));
4784 if preceding_text_matches_prefix {
4785 bracket_pair = Some(pair.clone());
4786 is_bracket_pair_start = true;
4787 break;
4788 }
4789 }
4790 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4791 {
4792 // take first bracket pair matching end, but don't break in case a later bracket
4793 // pair matches start
4794 bracket_pair_matching_end = Some(pair.clone());
4795 }
4796 }
4797 if let Some(end) = bracket_pair_matching_end
4798 && bracket_pair.is_none()
4799 {
4800 bracket_pair = Some(end);
4801 is_bracket_pair_end = true;
4802 }
4803 }
4804
4805 if let Some(bracket_pair) = bracket_pair {
4806 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4807 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4808 let auto_surround =
4809 self.use_auto_surround && snapshot_settings.use_auto_surround;
4810 if selection.is_empty() {
4811 if is_bracket_pair_start {
4812 // If the inserted text is a suffix of an opening bracket and the
4813 // selection is preceded by the rest of the opening bracket, then
4814 // insert the closing bracket.
4815 let following_text_allows_autoclose = snapshot
4816 .chars_at(selection.start)
4817 .next()
4818 .is_none_or(|c| scope.should_autoclose_before(c));
4819
4820 let preceding_text_allows_autoclose = selection.start.column == 0
4821 || snapshot
4822 .reversed_chars_at(selection.start)
4823 .next()
4824 .is_none_or(|c| {
4825 bracket_pair.start != bracket_pair.end
4826 || !snapshot
4827 .char_classifier_at(selection.start)
4828 .is_word(c)
4829 });
4830
4831 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4832 && bracket_pair.start.len() == 1
4833 {
4834 let target = bracket_pair.start.chars().next().unwrap();
4835 let mut byte_offset = 0u32;
4836 let current_line_count = snapshot
4837 .reversed_chars_at(selection.start)
4838 .take_while(|&c| c != '\n')
4839 .filter(|c| {
4840 byte_offset += c.len_utf8() as u32;
4841 if *c != target {
4842 return false;
4843 }
4844
4845 let point = Point::new(
4846 selection.start.row,
4847 selection.start.column.saturating_sub(byte_offset),
4848 );
4849
4850 let is_enabled = snapshot
4851 .language_scope_at(point)
4852 .and_then(|scope| {
4853 scope
4854 .brackets()
4855 .find(|(pair, _)| {
4856 pair.start == bracket_pair.start
4857 })
4858 .map(|(_, enabled)| enabled)
4859 })
4860 .unwrap_or(true);
4861
4862 let is_delimiter = snapshot
4863 .language_scope_at(Point::new(
4864 point.row,
4865 point.column + 1,
4866 ))
4867 .and_then(|scope| {
4868 scope
4869 .brackets()
4870 .find(|(pair, _)| {
4871 pair.start == bracket_pair.start
4872 })
4873 .map(|(_, enabled)| !enabled)
4874 })
4875 .unwrap_or(false);
4876
4877 is_enabled && !is_delimiter
4878 })
4879 .count();
4880 current_line_count % 2 == 1
4881 } else {
4882 false
4883 };
4884
4885 if autoclose
4886 && bracket_pair.close
4887 && following_text_allows_autoclose
4888 && preceding_text_allows_autoclose
4889 && !is_closing_quote
4890 {
4891 let anchor = snapshot.anchor_before(selection.end);
4892 new_selections.push((selection.map(|_| anchor), text.len()));
4893 new_autoclose_regions.push((
4894 anchor,
4895 text.len(),
4896 selection.id,
4897 bracket_pair.clone(),
4898 ));
4899 edits.push((
4900 selection.range(),
4901 format!("{}{}", text, bracket_pair.end).into(),
4902 ));
4903 bracket_inserted = true;
4904 continue;
4905 }
4906 }
4907
4908 if let Some(region) = autoclose_region {
4909 // If the selection is followed by an auto-inserted closing bracket,
4910 // then don't insert that closing bracket again; just move the selection
4911 // past the closing bracket.
4912 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4913 && text.as_ref() == region.pair.end.as_str()
4914 && snapshot.contains_str_at(region.range.end, text.as_ref());
4915 if should_skip {
4916 let anchor = snapshot.anchor_after(selection.end);
4917 new_selections
4918 .push((selection.map(|_| anchor), region.pair.end.len()));
4919 continue;
4920 }
4921 }
4922
4923 let always_treat_brackets_as_autoclosed = snapshot
4924 .language_settings_at(selection.start, cx)
4925 .always_treat_brackets_as_autoclosed;
4926 if always_treat_brackets_as_autoclosed
4927 && is_bracket_pair_end
4928 && snapshot.contains_str_at(selection.end, text.as_ref())
4929 {
4930 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4931 // and the inserted text is a closing bracket and the selection is followed
4932 // by the closing bracket then move the selection past the closing bracket.
4933 let anchor = snapshot.anchor_after(selection.end);
4934 new_selections.push((selection.map(|_| anchor), text.len()));
4935 continue;
4936 }
4937 }
4938 // If an opening bracket is 1 character long and is typed while
4939 // text is selected, then surround that text with the bracket pair.
4940 else if auto_surround
4941 && bracket_pair.surround
4942 && is_bracket_pair_start
4943 && bracket_pair.start.chars().count() == 1
4944 {
4945 edits.push((selection.start..selection.start, text.clone()));
4946 edits.push((
4947 selection.end..selection.end,
4948 bracket_pair.end.as_str().into(),
4949 ));
4950 bracket_inserted = true;
4951 new_selections.push((
4952 Selection {
4953 id: selection.id,
4954 start: snapshot.anchor_after(selection.start),
4955 end: snapshot.anchor_before(selection.end),
4956 reversed: selection.reversed,
4957 goal: selection.goal,
4958 },
4959 0,
4960 ));
4961 continue;
4962 }
4963 }
4964 }
4965
4966 if self.auto_replace_emoji_shortcode
4967 && selection.is_empty()
4968 && text.as_ref().ends_with(':')
4969 && let Some(possible_emoji_short_code) =
4970 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4971 && !possible_emoji_short_code.is_empty()
4972 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4973 {
4974 let emoji_shortcode_start = Point::new(
4975 selection.start.row,
4976 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4977 );
4978
4979 // Remove shortcode from buffer
4980 edits.push((
4981 emoji_shortcode_start..selection.start,
4982 "".to_string().into(),
4983 ));
4984 new_selections.push((
4985 Selection {
4986 id: selection.id,
4987 start: snapshot.anchor_after(emoji_shortcode_start),
4988 end: snapshot.anchor_before(selection.start),
4989 reversed: selection.reversed,
4990 goal: selection.goal,
4991 },
4992 0,
4993 ));
4994
4995 // Insert emoji
4996 let selection_start_anchor = snapshot.anchor_after(selection.start);
4997 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4998 edits.push((selection.start..selection.end, emoji.to_string().into()));
4999
5000 continue;
5001 }
5002
5003 let next_is_adjacent = regions
5004 .peek()
5005 .is_some_and(|(next, _)| selection.end == next.start);
5006
5007 // If not handling any auto-close operation, then just replace the selected
5008 // text with the given input and move the selection to the end of the
5009 // newly inserted text.
5010 let anchor = if in_adjacent_group || next_is_adjacent {
5011 // After edits the right bias would shift those anchor to the next visible fragment
5012 // but we want to resolve to the previous one
5013 snapshot.anchor_before(selection.end)
5014 } else {
5015 snapshot.anchor_after(selection.end)
5016 };
5017
5018 if !self.linked_edit_ranges.is_empty() {
5019 let start_anchor = snapshot.anchor_before(selection.start);
5020
5021 let is_word_char = text.chars().next().is_none_or(|char| {
5022 let classifier = snapshot
5023 .char_classifier_at(start_anchor.to_offset(&snapshot))
5024 .scope_context(Some(CharScopeContext::LinkedEdit));
5025 classifier.is_word(char)
5026 });
5027 let is_dot = text.as_ref() == ".";
5028 let should_apply_linked_edit = is_word_char || is_dot;
5029
5030 if should_apply_linked_edit {
5031 let anchor_range = start_anchor.text_anchor..anchor.text_anchor;
5032 linked_edits.push(&self, anchor_range, text.clone(), cx);
5033 } else {
5034 clear_linked_edit_ranges = true;
5035 }
5036 }
5037
5038 new_selections.push((selection.map(|_| anchor), 0));
5039 edits.push((selection.start..selection.end, text.clone()));
5040
5041 has_adjacent_edits |= next_is_adjacent;
5042 in_adjacent_group = next_is_adjacent;
5043 }
5044
5045 if all_selections_read_only {
5046 return;
5047 }
5048
5049 drop(regions);
5050 drop(snapshot);
5051
5052 self.transact(window, cx, |this, window, cx| {
5053 if clear_linked_edit_ranges {
5054 this.linked_edit_ranges.clear();
5055 }
5056 let initial_buffer_versions =
5057 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
5058
5059 this.buffer.update(cx, |buffer, cx| {
5060 if has_adjacent_edits {
5061 buffer.edit_non_coalesce(edits, this.autoindent_mode.clone(), cx);
5062 } else {
5063 buffer.edit(edits, this.autoindent_mode.clone(), cx);
5064 }
5065 });
5066 linked_edits.apply(cx);
5067 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
5068 let new_selection_deltas = new_selections.iter().map(|e| e.1);
5069 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
5070 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
5071 new_anchor_selections,
5072 &map,
5073 )
5074 .zip(new_selection_deltas)
5075 .map(|(selection, delta)| Selection {
5076 id: selection.id,
5077 start: selection.start + delta,
5078 end: selection.end + delta,
5079 reversed: selection.reversed,
5080 goal: SelectionGoal::None,
5081 })
5082 .collect::<Vec<_>>();
5083
5084 let mut i = 0;
5085 for (position, delta, selection_id, pair) in new_autoclose_regions {
5086 let position = position.to_offset(map.buffer_snapshot()) + delta;
5087 let start = map.buffer_snapshot().anchor_before(position);
5088 let end = map.buffer_snapshot().anchor_after(position);
5089 while let Some(existing_state) = this.autoclose_regions.get(i) {
5090 match existing_state
5091 .range
5092 .start
5093 .cmp(&start, map.buffer_snapshot())
5094 {
5095 Ordering::Less => i += 1,
5096 Ordering::Greater => break,
5097 Ordering::Equal => {
5098 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
5099 Ordering::Less => i += 1,
5100 Ordering::Equal => break,
5101 Ordering::Greater => break,
5102 }
5103 }
5104 }
5105 }
5106 this.autoclose_regions.insert(
5107 i,
5108 AutocloseRegion {
5109 selection_id,
5110 range: start..end,
5111 pair,
5112 },
5113 );
5114 }
5115
5116 let had_active_edit_prediction = this.has_active_edit_prediction();
5117 this.change_selections(
5118 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
5119 window,
5120 cx,
5121 |s| s.select(new_selections),
5122 );
5123
5124 if !bracket_inserted
5125 && let Some(on_type_format_task) =
5126 this.trigger_on_type_formatting(text.to_string(), window, cx)
5127 {
5128 on_type_format_task.detach_and_log_err(cx);
5129 }
5130
5131 let editor_settings = EditorSettings::get_global(cx);
5132 if bracket_inserted
5133 && (editor_settings.auto_signature_help
5134 || editor_settings.show_signature_help_after_edits)
5135 {
5136 this.show_signature_help(&ShowSignatureHelp, window, cx);
5137 }
5138
5139 let trigger_in_words =
5140 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
5141 if this.hard_wrap.is_some() {
5142 let latest: Range<Point> = this.selections.newest(&map).range();
5143 if latest.is_empty()
5144 && this
5145 .buffer()
5146 .read(cx)
5147 .snapshot(cx)
5148 .line_len(MultiBufferRow(latest.start.row))
5149 == latest.start.column
5150 {
5151 this.rewrap_impl(
5152 RewrapOptions {
5153 override_language_settings: true,
5154 preserve_existing_whitespace: true,
5155 line_length: None,
5156 },
5157 cx,
5158 )
5159 }
5160 }
5161 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
5162 refresh_linked_ranges(this, window, cx);
5163 this.refresh_edit_prediction(true, false, window, cx);
5164 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
5165 });
5166 }
5167
5168 fn find_possible_emoji_shortcode_at_position(
5169 snapshot: &MultiBufferSnapshot,
5170 position: Point,
5171 ) -> Option<String> {
5172 let mut chars = Vec::new();
5173 let mut found_colon = false;
5174 for char in snapshot.reversed_chars_at(position).take(100) {
5175 // Found a possible emoji shortcode in the middle of the buffer
5176 if found_colon {
5177 if char.is_whitespace() {
5178 chars.reverse();
5179 return Some(chars.iter().collect());
5180 }
5181 // If the previous character is not a whitespace, we are in the middle of a word
5182 // and we only want to complete the shortcode if the word is made up of other emojis
5183 let mut containing_word = String::new();
5184 for ch in snapshot
5185 .reversed_chars_at(position)
5186 .skip(chars.len() + 1)
5187 .take(100)
5188 {
5189 if ch.is_whitespace() {
5190 break;
5191 }
5192 containing_word.push(ch);
5193 }
5194 let containing_word = containing_word.chars().rev().collect::<String>();
5195 if util::word_consists_of_emojis(containing_word.as_str()) {
5196 chars.reverse();
5197 return Some(chars.iter().collect());
5198 }
5199 }
5200
5201 if char.is_whitespace() || !char.is_ascii() {
5202 return None;
5203 }
5204 if char == ':' {
5205 found_colon = true;
5206 } else {
5207 chars.push(char);
5208 }
5209 }
5210 // Found a possible emoji shortcode at the beginning of the buffer
5211 chars.reverse();
5212 Some(chars.iter().collect())
5213 }
5214
5215 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
5216 if self.read_only(cx) {
5217 return;
5218 }
5219
5220 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5221 self.transact(window, cx, |this, window, cx| {
5222 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
5223 let selections = this
5224 .selections
5225 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
5226 let multi_buffer = this.buffer.read(cx);
5227 let buffer = multi_buffer.snapshot(cx);
5228 selections
5229 .iter()
5230 .map(|selection| {
5231 let start_point = selection.start.to_point(&buffer);
5232 let mut existing_indent =
5233 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
5234 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
5235 let start = selection.start;
5236 let end = selection.end;
5237 let selection_is_empty = start == end;
5238 let language_scope = buffer.language_scope_at(start);
5239 let (delimiter, newline_config) = if let Some(language) = &language_scope {
5240 let needs_extra_newline = NewlineConfig::insert_extra_newline_brackets(
5241 &buffer,
5242 start..end,
5243 language,
5244 )
5245 || NewlineConfig::insert_extra_newline_tree_sitter(
5246 &buffer,
5247 start..end,
5248 );
5249
5250 let mut newline_config = NewlineConfig::Newline {
5251 additional_indent: IndentSize::spaces(0),
5252 extra_line_additional_indent: if needs_extra_newline {
5253 Some(IndentSize::spaces(0))
5254 } else {
5255 None
5256 },
5257 prevent_auto_indent: false,
5258 };
5259
5260 let comment_delimiter = maybe!({
5261 if !selection_is_empty {
5262 return None;
5263 }
5264
5265 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5266 return None;
5267 }
5268
5269 return comment_delimiter_for_newline(
5270 &start_point,
5271 &buffer,
5272 language,
5273 );
5274 });
5275
5276 let doc_delimiter = maybe!({
5277 if !selection_is_empty {
5278 return None;
5279 }
5280
5281 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5282 return None;
5283 }
5284
5285 return documentation_delimiter_for_newline(
5286 &start_point,
5287 &buffer,
5288 language,
5289 &mut newline_config,
5290 );
5291 });
5292
5293 let list_delimiter = maybe!({
5294 if !selection_is_empty {
5295 return None;
5296 }
5297
5298 if !multi_buffer.language_settings(cx).extend_list_on_newline {
5299 return None;
5300 }
5301
5302 return list_delimiter_for_newline(
5303 &start_point,
5304 &buffer,
5305 language,
5306 &mut newline_config,
5307 );
5308 });
5309
5310 (
5311 comment_delimiter.or(doc_delimiter).or(list_delimiter),
5312 newline_config,
5313 )
5314 } else {
5315 (
5316 None,
5317 NewlineConfig::Newline {
5318 additional_indent: IndentSize::spaces(0),
5319 extra_line_additional_indent: None,
5320 prevent_auto_indent: false,
5321 },
5322 )
5323 };
5324
5325 let (edit_start, new_text, prevent_auto_indent) = match &newline_config {
5326 NewlineConfig::ClearCurrentLine => {
5327 let row_start =
5328 buffer.point_to_offset(Point::new(start_point.row, 0));
5329 (row_start, String::new(), false)
5330 }
5331 NewlineConfig::UnindentCurrentLine { continuation } => {
5332 let row_start =
5333 buffer.point_to_offset(Point::new(start_point.row, 0));
5334 let tab_size = buffer.language_settings_at(start, cx).tab_size;
5335 let tab_size_indent = IndentSize::spaces(tab_size.get());
5336 let reduced_indent =
5337 existing_indent.with_delta(Ordering::Less, tab_size_indent);
5338 let mut new_text = String::new();
5339 new_text.extend(reduced_indent.chars());
5340 new_text.push_str(continuation);
5341 (row_start, new_text, true)
5342 }
5343 NewlineConfig::Newline {
5344 additional_indent,
5345 extra_line_additional_indent,
5346 prevent_auto_indent,
5347 } => {
5348 let auto_indent_mode =
5349 buffer.language_settings_at(start, cx).auto_indent;
5350 let preserve_indent =
5351 auto_indent_mode != language::AutoIndentMode::None;
5352 let apply_syntax_indent =
5353 auto_indent_mode == language::AutoIndentMode::SyntaxAware;
5354 let capacity_for_delimiter =
5355 delimiter.as_deref().map(str::len).unwrap_or_default();
5356 let existing_indent_len = if preserve_indent {
5357 existing_indent.len as usize
5358 } else {
5359 0
5360 };
5361 let extra_line_len = extra_line_additional_indent
5362 .map(|i| 1 + existing_indent_len + i.len as usize)
5363 .unwrap_or(0);
5364 let mut new_text = String::with_capacity(
5365 1 + capacity_for_delimiter
5366 + existing_indent_len
5367 + additional_indent.len as usize
5368 + extra_line_len,
5369 );
5370 new_text.push('\n');
5371 if preserve_indent {
5372 new_text.extend(existing_indent.chars());
5373 }
5374 new_text.extend(additional_indent.chars());
5375 if let Some(delimiter) = &delimiter {
5376 new_text.push_str(delimiter);
5377 }
5378 if let Some(extra_indent) = extra_line_additional_indent {
5379 new_text.push('\n');
5380 if preserve_indent {
5381 new_text.extend(existing_indent.chars());
5382 }
5383 new_text.extend(extra_indent.chars());
5384 }
5385 (
5386 start,
5387 new_text,
5388 *prevent_auto_indent || !apply_syntax_indent,
5389 )
5390 }
5391 };
5392
5393 let anchor = buffer.anchor_after(end);
5394 let new_selection = selection.map(|_| anchor);
5395 (
5396 ((edit_start..end, new_text), prevent_auto_indent),
5397 (newline_config.has_extra_line(), new_selection),
5398 )
5399 })
5400 .unzip()
5401 };
5402
5403 let mut auto_indent_edits = Vec::new();
5404 let mut edits = Vec::new();
5405 for (edit, prevent_auto_indent) in edits_with_flags {
5406 if prevent_auto_indent {
5407 edits.push(edit);
5408 } else {
5409 auto_indent_edits.push(edit);
5410 }
5411 }
5412 if !edits.is_empty() {
5413 this.edit(edits, cx);
5414 }
5415 if !auto_indent_edits.is_empty() {
5416 this.edit_with_autoindent(auto_indent_edits, cx);
5417 }
5418
5419 let buffer = this.buffer.read(cx).snapshot(cx);
5420 let new_selections = selection_info
5421 .into_iter()
5422 .map(|(extra_newline_inserted, new_selection)| {
5423 let mut cursor = new_selection.end.to_point(&buffer);
5424 if extra_newline_inserted {
5425 cursor.row -= 1;
5426 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
5427 }
5428 new_selection.map(|_| cursor)
5429 })
5430 .collect();
5431
5432 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
5433 this.refresh_edit_prediction(true, false, window, cx);
5434 if let Some(task) = this.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5435 task.detach_and_log_err(cx);
5436 }
5437 });
5438 }
5439
5440 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
5441 if self.read_only(cx) {
5442 return;
5443 }
5444
5445 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5446
5447 let buffer = self.buffer.read(cx);
5448 let snapshot = buffer.snapshot(cx);
5449
5450 let mut edits = Vec::new();
5451 let mut rows = Vec::new();
5452
5453 for (rows_inserted, selection) in self
5454 .selections
5455 .all_adjusted(&self.display_snapshot(cx))
5456 .into_iter()
5457 .enumerate()
5458 {
5459 let cursor = selection.head();
5460 let row = cursor.row;
5461
5462 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
5463
5464 let newline = "\n".to_string();
5465 edits.push((start_of_line..start_of_line, newline));
5466
5467 rows.push(row + rows_inserted as u32);
5468 }
5469
5470 self.transact(window, cx, |editor, window, cx| {
5471 editor.edit(edits, cx);
5472
5473 editor.change_selections(Default::default(), window, cx, |s| {
5474 let mut index = 0;
5475 s.move_cursors_with(&mut |map, _, _| {
5476 let row = rows[index];
5477 index += 1;
5478
5479 let point = Point::new(row, 0);
5480 let boundary = map.next_line_boundary(point).1;
5481 let clipped = map.clip_point(boundary, Bias::Left);
5482
5483 (clipped, SelectionGoal::None)
5484 });
5485 });
5486
5487 let mut indent_edits = Vec::new();
5488 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5489 for row in rows {
5490 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5491 for (row, indent) in indents {
5492 if indent.len == 0 {
5493 continue;
5494 }
5495
5496 let text = match indent.kind {
5497 IndentKind::Space => " ".repeat(indent.len as usize),
5498 IndentKind::Tab => "\t".repeat(indent.len as usize),
5499 };
5500 let point = Point::new(row.0, 0);
5501 indent_edits.push((point..point, text));
5502 }
5503 }
5504 editor.edit(indent_edits, cx);
5505 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5506 format.detach_and_log_err(cx);
5507 }
5508 });
5509 }
5510
5511 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5512 if self.read_only(cx) {
5513 return;
5514 }
5515
5516 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5517
5518 let mut buffer_edits: HashMap<EntityId, (Entity<Buffer>, Vec<Point>)> = HashMap::default();
5519 let mut rows = Vec::new();
5520 let mut rows_inserted = 0;
5521
5522 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5523 let cursor = selection.head();
5524 let row = cursor.row;
5525
5526 let point = Point::new(row, 0);
5527 let Some((buffer_handle, buffer_point, _)) =
5528 self.buffer.read(cx).point_to_buffer_point(point, cx)
5529 else {
5530 continue;
5531 };
5532
5533 buffer_edits
5534 .entry(buffer_handle.entity_id())
5535 .or_insert_with(|| (buffer_handle, Vec::new()))
5536 .1
5537 .push(buffer_point);
5538
5539 rows_inserted += 1;
5540 rows.push(row + rows_inserted);
5541 }
5542
5543 self.transact(window, cx, |editor, window, cx| {
5544 for (_, (buffer_handle, points)) in &buffer_edits {
5545 buffer_handle.update(cx, |buffer, cx| {
5546 let edits: Vec<_> = points
5547 .iter()
5548 .map(|point| {
5549 let target = Point::new(point.row + 1, 0);
5550 let start_of_line = buffer.point_to_offset(target).min(buffer.len());
5551 (start_of_line..start_of_line, "\n")
5552 })
5553 .collect();
5554 buffer.edit(edits, None, cx);
5555 });
5556 }
5557
5558 editor.change_selections(Default::default(), window, cx, |s| {
5559 let mut index = 0;
5560 s.move_cursors_with(&mut |map, _, _| {
5561 let row = rows[index];
5562 index += 1;
5563
5564 let point = Point::new(row, 0);
5565 let boundary = map.next_line_boundary(point).1;
5566 let clipped = map.clip_point(boundary, Bias::Left);
5567
5568 (clipped, SelectionGoal::None)
5569 });
5570 });
5571
5572 let mut indent_edits = Vec::new();
5573 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5574 for row in rows {
5575 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5576 for (row, indent) in indents {
5577 if indent.len == 0 {
5578 continue;
5579 }
5580
5581 let text = match indent.kind {
5582 IndentKind::Space => " ".repeat(indent.len as usize),
5583 IndentKind::Tab => "\t".repeat(indent.len as usize),
5584 };
5585 let point = Point::new(row.0, 0);
5586 indent_edits.push((point..point, text));
5587 }
5588 }
5589 editor.edit(indent_edits, cx);
5590 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5591 format.detach_and_log_err(cx);
5592 }
5593 });
5594 }
5595
5596 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5597 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5598 original_indent_columns: Vec::new(),
5599 });
5600 self.replace_selections(text, autoindent, window, cx, false);
5601 }
5602
5603 /// Replaces the editor's selections with the provided `text`, applying the
5604 /// given `autoindent_mode` (`None` will skip autoindentation).
5605 ///
5606 /// Early returns if the editor is in read-only mode, without applying any
5607 /// edits.
5608 fn replace_selections(
5609 &mut self,
5610 text: &str,
5611 autoindent_mode: Option<AutoindentMode>,
5612 window: &mut Window,
5613 cx: &mut Context<Self>,
5614 apply_linked_edits: bool,
5615 ) {
5616 if self.read_only(cx) {
5617 return;
5618 }
5619
5620 let text: Arc<str> = text.into();
5621 self.transact(window, cx, |this, window, cx| {
5622 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5623 let linked_edits = if apply_linked_edits {
5624 this.linked_edits_for_selections(text.clone(), cx)
5625 } else {
5626 LinkedEdits::new()
5627 };
5628
5629 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5630 let anchors = {
5631 let snapshot = buffer.read(cx);
5632 old_selections
5633 .iter()
5634 .map(|s| {
5635 let anchor = snapshot.anchor_after(s.head());
5636 s.map(|_| anchor)
5637 })
5638 .collect::<Vec<_>>()
5639 };
5640 buffer.edit(
5641 old_selections
5642 .iter()
5643 .map(|s| (s.start..s.end, text.clone())),
5644 autoindent_mode,
5645 cx,
5646 );
5647 anchors
5648 });
5649
5650 linked_edits.apply(cx);
5651
5652 this.change_selections(Default::default(), window, cx, |s| {
5653 s.select_anchors(selection_anchors);
5654 });
5655
5656 if apply_linked_edits {
5657 refresh_linked_ranges(this, window, cx);
5658 }
5659
5660 cx.notify();
5661 });
5662 }
5663
5664 /// Collects linked edits for the current selections, pairing each linked
5665 /// range with `text`.
5666 pub fn linked_edits_for_selections(&self, text: Arc<str>, cx: &App) -> LinkedEdits {
5667 let mut linked_edits = LinkedEdits::new();
5668 if !self.linked_edit_ranges.is_empty() {
5669 for selection in self.selections.disjoint_anchors() {
5670 let start = selection.start.text_anchor;
5671 let end = selection.end.text_anchor;
5672 linked_edits.push(self, start..end, text.clone(), cx);
5673 }
5674 }
5675 linked_edits
5676 }
5677
5678 /// Deletes the content covered by the current selections and applies
5679 /// linked edits.
5680 pub fn delete_selections_with_linked_edits(
5681 &mut self,
5682 window: &mut Window,
5683 cx: &mut Context<Self>,
5684 ) {
5685 self.replace_selections("", None, window, cx, true);
5686 }
5687
5688 #[cfg(any(test, feature = "test-support"))]
5689 pub fn set_linked_edit_ranges_for_testing(
5690 &mut self,
5691 ranges: Vec<(Range<Point>, Vec<Range<Point>>)>,
5692 cx: &mut Context<Self>,
5693 ) -> Option<()> {
5694 let Some((buffer, _)) = self
5695 .buffer
5696 .read(cx)
5697 .text_anchor_for_position(self.selections.newest_anchor().start, cx)
5698 else {
5699 return None;
5700 };
5701 let buffer = buffer.read(cx);
5702 let buffer_id = buffer.remote_id();
5703 let mut linked_ranges = Vec::with_capacity(ranges.len());
5704 for (base_range, linked_ranges_points) in ranges {
5705 let base_anchor =
5706 buffer.anchor_before(base_range.start)..buffer.anchor_after(base_range.end);
5707 let linked_anchors = linked_ranges_points
5708 .into_iter()
5709 .map(|range| buffer.anchor_before(range.start)..buffer.anchor_after(range.end))
5710 .collect();
5711 linked_ranges.push((base_anchor, linked_anchors));
5712 }
5713 let mut map = HashMap::default();
5714 map.insert(buffer_id, linked_ranges);
5715 self.linked_edit_ranges = linked_editing_ranges::LinkedEditingRanges(map);
5716 Some(())
5717 }
5718
5719 fn trigger_completion_on_input(
5720 &mut self,
5721 text: &str,
5722 trigger_in_words: bool,
5723 window: &mut Window,
5724 cx: &mut Context<Self>,
5725 ) {
5726 let completions_source = self
5727 .context_menu
5728 .borrow()
5729 .as_ref()
5730 .and_then(|menu| match menu {
5731 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5732 CodeContextMenu::CodeActions(_) => None,
5733 });
5734
5735 match completions_source {
5736 Some(CompletionsMenuSource::Words { .. }) => {
5737 self.open_or_update_completions_menu(
5738 Some(CompletionsMenuSource::Words {
5739 ignore_threshold: false,
5740 }),
5741 None,
5742 trigger_in_words,
5743 window,
5744 cx,
5745 );
5746 }
5747 _ => self.open_or_update_completions_menu(
5748 None,
5749 Some(text.to_owned()).filter(|x| !x.is_empty()),
5750 true,
5751 window,
5752 cx,
5753 ),
5754 }
5755 }
5756
5757 /// If any empty selections is touching the start of its innermost containing autoclose
5758 /// region, expand it to select the brackets.
5759 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5760 let selections = self
5761 .selections
5762 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5763 let buffer = self.buffer.read(cx).read(cx);
5764 let new_selections = self
5765 .selections_with_autoclose_regions(selections, &buffer)
5766 .map(|(mut selection, region)| {
5767 if !selection.is_empty() {
5768 return selection;
5769 }
5770
5771 if let Some(region) = region {
5772 let mut range = region.range.to_offset(&buffer);
5773 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5774 range.start -= region.pair.start.len();
5775 if buffer.contains_str_at(range.start, ®ion.pair.start)
5776 && buffer.contains_str_at(range.end, ®ion.pair.end)
5777 {
5778 range.end += region.pair.end.len();
5779 selection.start = range.start;
5780 selection.end = range.end;
5781
5782 return selection;
5783 }
5784 }
5785 }
5786
5787 let always_treat_brackets_as_autoclosed = buffer
5788 .language_settings_at(selection.start, cx)
5789 .always_treat_brackets_as_autoclosed;
5790
5791 if !always_treat_brackets_as_autoclosed {
5792 return selection;
5793 }
5794
5795 if let Some(scope) = buffer.language_scope_at(selection.start) {
5796 for (pair, enabled) in scope.brackets() {
5797 if !enabled || !pair.close {
5798 continue;
5799 }
5800
5801 if buffer.contains_str_at(selection.start, &pair.end) {
5802 let pair_start_len = pair.start.len();
5803 if buffer.contains_str_at(
5804 selection.start.saturating_sub_usize(pair_start_len),
5805 &pair.start,
5806 ) {
5807 selection.start -= pair_start_len;
5808 selection.end += pair.end.len();
5809
5810 return selection;
5811 }
5812 }
5813 }
5814 }
5815
5816 selection
5817 })
5818 .collect();
5819
5820 drop(buffer);
5821 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5822 selections.select(new_selections)
5823 });
5824 }
5825
5826 /// Iterate the given selections, and for each one, find the smallest surrounding
5827 /// autoclose region. This uses the ordering of the selections and the autoclose
5828 /// regions to avoid repeated comparisons.
5829 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5830 &'a self,
5831 selections: impl IntoIterator<Item = Selection<D>>,
5832 buffer: &'a MultiBufferSnapshot,
5833 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5834 let mut i = 0;
5835 let mut regions = self.autoclose_regions.as_slice();
5836 selections.into_iter().map(move |selection| {
5837 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5838
5839 let mut enclosing = None;
5840 while let Some(pair_state) = regions.get(i) {
5841 if pair_state.range.end.to_offset(buffer) < range.start {
5842 regions = ®ions[i + 1..];
5843 i = 0;
5844 } else if pair_state.range.start.to_offset(buffer) > range.end {
5845 break;
5846 } else {
5847 if pair_state.selection_id == selection.id {
5848 enclosing = Some(pair_state);
5849 }
5850 i += 1;
5851 }
5852 }
5853
5854 (selection, enclosing)
5855 })
5856 }
5857
5858 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5859 fn invalidate_autoclose_regions(
5860 &mut self,
5861 mut selections: &[Selection<Anchor>],
5862 buffer: &MultiBufferSnapshot,
5863 ) {
5864 self.autoclose_regions.retain(|state| {
5865 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5866 return false;
5867 }
5868
5869 let mut i = 0;
5870 while let Some(selection) = selections.get(i) {
5871 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5872 selections = &selections[1..];
5873 continue;
5874 }
5875 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5876 break;
5877 }
5878 if selection.id == state.selection_id {
5879 return true;
5880 } else {
5881 i += 1;
5882 }
5883 }
5884 false
5885 });
5886 }
5887
5888 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5889 let offset = position.to_offset(buffer);
5890 let (word_range, kind) =
5891 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5892 if offset > word_range.start && kind == Some(CharKind::Word) {
5893 Some(
5894 buffer
5895 .text_for_range(word_range.start..offset)
5896 .collect::<String>(),
5897 )
5898 } else {
5899 None
5900 }
5901 }
5902
5903 pub fn visible_excerpts(
5904 &self,
5905 lsp_related_only: bool,
5906 cx: &mut Context<Editor>,
5907 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5908 let project = self.project().cloned();
5909 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
5910 let multi_buffer = self.buffer().read(cx);
5911 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5912 multi_buffer_snapshot
5913 .range_to_buffer_ranges(
5914 self.multi_buffer_visible_range(&display_snapshot, cx)
5915 .to_inclusive(),
5916 )
5917 .into_iter()
5918 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5919 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5920 if !lsp_related_only {
5921 return Some((
5922 excerpt_id,
5923 (
5924 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5925 buffer.version().clone(),
5926 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5927 ),
5928 ));
5929 }
5930
5931 let project = project.as_ref()?.read(cx);
5932 let buffer_file = project::File::from_dyn(buffer.file())?;
5933 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5934 let worktree_entry = buffer_worktree
5935 .read(cx)
5936 .entry_for_id(buffer_file.project_entry_id()?)?;
5937 if worktree_entry.is_ignored {
5938 None
5939 } else {
5940 Some((
5941 excerpt_id,
5942 (
5943 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5944 buffer.version().clone(),
5945 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5946 ),
5947 ))
5948 }
5949 })
5950 .collect()
5951 }
5952
5953 pub fn text_layout_details(&self, window: &mut Window, cx: &mut App) -> TextLayoutDetails {
5954 TextLayoutDetails {
5955 text_system: window.text_system().clone(),
5956 editor_style: self.style.clone().unwrap(),
5957 rem_size: window.rem_size(),
5958 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
5959 visible_rows: self.visible_line_count(),
5960 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5961 }
5962 }
5963
5964 fn trigger_on_type_formatting(
5965 &self,
5966 input: String,
5967 window: &mut Window,
5968 cx: &mut Context<Self>,
5969 ) -> Option<Task<Result<()>>> {
5970 if input.chars().count() != 1 {
5971 return None;
5972 }
5973
5974 let project = self.project()?;
5975 let position = self.selections.newest_anchor().head();
5976 let (buffer, buffer_position) = self
5977 .buffer
5978 .read(cx)
5979 .text_anchor_for_position(position, cx)?;
5980
5981 let settings = LanguageSettings::for_buffer_at(&buffer.read(cx), buffer_position, cx);
5982 if !settings.use_on_type_format {
5983 return None;
5984 }
5985
5986 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5987 // hence we do LSP request & edit on host side only — add formats to host's history.
5988 let push_to_lsp_host_history = true;
5989 // If this is not the host, append its history with new edits.
5990 let push_to_client_history = project.read(cx).is_via_collab();
5991
5992 let on_type_formatting = project.update(cx, |project, cx| {
5993 project.on_type_format(
5994 buffer.clone(),
5995 buffer_position,
5996 input,
5997 push_to_lsp_host_history,
5998 cx,
5999 )
6000 });
6001 Some(cx.spawn_in(window, async move |editor, cx| {
6002 if let Some(transaction) = on_type_formatting.await? {
6003 if push_to_client_history {
6004 buffer.update(cx, |buffer, _| {
6005 buffer.push_transaction(transaction, Instant::now());
6006 buffer.finalize_last_transaction();
6007 });
6008 }
6009 editor.update(cx, |editor, cx| {
6010 editor.refresh_document_highlights(cx);
6011 })?;
6012 }
6013 Ok(())
6014 }))
6015 }
6016
6017 pub fn show_word_completions(
6018 &mut self,
6019 _: &ShowWordCompletions,
6020 window: &mut Window,
6021 cx: &mut Context<Self>,
6022 ) {
6023 self.open_or_update_completions_menu(
6024 Some(CompletionsMenuSource::Words {
6025 ignore_threshold: true,
6026 }),
6027 None,
6028 false,
6029 window,
6030 cx,
6031 );
6032 }
6033
6034 pub fn show_completions(
6035 &mut self,
6036 _: &ShowCompletions,
6037 window: &mut Window,
6038 cx: &mut Context<Self>,
6039 ) {
6040 self.open_or_update_completions_menu(None, None, false, window, cx);
6041 }
6042
6043 fn open_or_update_completions_menu(
6044 &mut self,
6045 requested_source: Option<CompletionsMenuSource>,
6046 trigger: Option<String>,
6047 trigger_in_words: bool,
6048 window: &mut Window,
6049 cx: &mut Context<Self>,
6050 ) {
6051 if self.pending_rename.is_some() {
6052 return;
6053 }
6054
6055 let completions_source = self
6056 .context_menu
6057 .borrow()
6058 .as_ref()
6059 .and_then(|menu| match menu {
6060 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
6061 CodeContextMenu::CodeActions(_) => None,
6062 });
6063
6064 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
6065
6066 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
6067 // inserted and selected. To handle that case, the start of the selection is used so that
6068 // the menu starts with all choices.
6069 let position = self
6070 .selections
6071 .newest_anchor()
6072 .start
6073 .bias_right(&multibuffer_snapshot);
6074 if position.diff_base_anchor.is_some() {
6075 return;
6076 }
6077 let buffer_position = multibuffer_snapshot.anchor_before(position);
6078 let Some(buffer) = buffer_position
6079 .text_anchor
6080 .buffer_id
6081 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
6082 else {
6083 return;
6084 };
6085 let buffer_snapshot = buffer.read(cx).snapshot();
6086
6087 let menu_is_open = matches!(
6088 self.context_menu.borrow().as_ref(),
6089 Some(CodeContextMenu::Completions(_))
6090 );
6091
6092 let language = buffer_snapshot
6093 .language_at(buffer_position.text_anchor)
6094 .map(|language| language.name());
6095 let language_settings = multibuffer_snapshot.language_settings_at(buffer_position, cx);
6096 let completion_settings = language_settings.completions.clone();
6097
6098 let show_completions_on_input = self
6099 .show_completions_on_input_override
6100 .unwrap_or(language_settings.show_completions_on_input);
6101 if !menu_is_open && trigger.is_some() && !show_completions_on_input {
6102 return;
6103 }
6104
6105 let query: Option<Arc<String>> =
6106 Self::completion_query(&multibuffer_snapshot, buffer_position)
6107 .map(|query| query.into());
6108
6109 drop(multibuffer_snapshot);
6110
6111 // Hide the current completions menu when query is empty. Without this, cached
6112 // completions from before the trigger char may be reused (#32774).
6113 if query.is_none() && menu_is_open {
6114 self.hide_context_menu(window, cx);
6115 }
6116
6117 let mut ignore_word_threshold = false;
6118 let provider = match requested_source {
6119 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
6120 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
6121 ignore_word_threshold = ignore_threshold;
6122 None
6123 }
6124 Some(CompletionsMenuSource::SnippetChoices)
6125 | Some(CompletionsMenuSource::SnippetsOnly) => {
6126 log::error!("bug: SnippetChoices requested_source is not handled");
6127 None
6128 }
6129 };
6130
6131 let sort_completions = provider
6132 .as_ref()
6133 .is_some_and(|provider| provider.sort_completions());
6134
6135 let filter_completions = provider
6136 .as_ref()
6137 .is_none_or(|provider| provider.filter_completions());
6138
6139 let was_snippets_only = matches!(
6140 completions_source,
6141 Some(CompletionsMenuSource::SnippetsOnly)
6142 );
6143
6144 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
6145 if filter_completions {
6146 menu.filter(
6147 query.clone().unwrap_or_default(),
6148 buffer_position.text_anchor,
6149 &buffer,
6150 provider.clone(),
6151 window,
6152 cx,
6153 );
6154 }
6155 // When `is_incomplete` is false, no need to re-query completions when the current query
6156 // is a suffix of the initial query.
6157 let was_complete = !menu.is_incomplete;
6158 if was_complete && !was_snippets_only {
6159 // If the new query is a suffix of the old query (typing more characters) and
6160 // the previous result was complete, the existing completions can be filtered.
6161 //
6162 // Note that snippet completions are always complete.
6163 let query_matches = match (&menu.initial_query, &query) {
6164 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
6165 (None, _) => true,
6166 _ => false,
6167 };
6168 if query_matches {
6169 let position_matches = if menu.initial_position == position {
6170 true
6171 } else {
6172 let snapshot = self.buffer.read(cx).read(cx);
6173 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
6174 };
6175 if position_matches {
6176 return;
6177 }
6178 }
6179 }
6180 };
6181
6182 let Anchor {
6183 excerpt_id: buffer_excerpt_id,
6184 text_anchor: buffer_position,
6185 ..
6186 } = buffer_position;
6187
6188 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
6189 buffer_snapshot.surrounding_word(buffer_position, None)
6190 {
6191 let word_to_exclude = buffer_snapshot
6192 .text_for_range(word_range.clone())
6193 .collect::<String>();
6194 (
6195 buffer_snapshot.anchor_before(word_range.start)
6196 ..buffer_snapshot.anchor_after(buffer_position),
6197 Some(word_to_exclude),
6198 )
6199 } else {
6200 (buffer_position..buffer_position, None)
6201 };
6202
6203 let show_completion_documentation = buffer_snapshot
6204 .settings_at(buffer_position, cx)
6205 .show_completion_documentation;
6206
6207 // The document can be large, so stay in reasonable bounds when searching for words,
6208 // otherwise completion pop-up might be slow to appear.
6209 const WORD_LOOKUP_ROWS: u32 = 5_000;
6210 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
6211 let min_word_search = buffer_snapshot.clip_point(
6212 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
6213 Bias::Left,
6214 );
6215 let max_word_search = buffer_snapshot.clip_point(
6216 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
6217 Bias::Right,
6218 );
6219 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
6220 ..buffer_snapshot.point_to_offset(max_word_search);
6221
6222 let skip_digits = query
6223 .as_ref()
6224 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
6225
6226 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
6227 trigger.as_ref().is_none_or(|trigger| {
6228 provider.is_completion_trigger(
6229 &buffer,
6230 position.text_anchor,
6231 trigger,
6232 trigger_in_words,
6233 cx,
6234 )
6235 })
6236 });
6237
6238 let provider_responses = if let Some(provider) = &provider
6239 && load_provider_completions
6240 {
6241 let trigger_character =
6242 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
6243 let completion_context = CompletionContext {
6244 trigger_kind: match &trigger_character {
6245 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
6246 None => CompletionTriggerKind::INVOKED,
6247 },
6248 trigger_character,
6249 };
6250
6251 provider.completions(
6252 buffer_excerpt_id,
6253 &buffer,
6254 buffer_position,
6255 completion_context,
6256 window,
6257 cx,
6258 )
6259 } else {
6260 Task::ready(Ok(Vec::new()))
6261 };
6262
6263 let load_word_completions = if !self.word_completions_enabled {
6264 false
6265 } else if requested_source
6266 == Some(CompletionsMenuSource::Words {
6267 ignore_threshold: true,
6268 })
6269 {
6270 true
6271 } else {
6272 load_provider_completions
6273 && completion_settings.words != WordsCompletionMode::Disabled
6274 && (ignore_word_threshold || {
6275 let words_min_length = completion_settings.words_min_length;
6276 // check whether word has at least `words_min_length` characters
6277 let query_chars = query.iter().flat_map(|q| q.chars());
6278 query_chars.take(words_min_length).count() == words_min_length
6279 })
6280 };
6281
6282 let mut words = if load_word_completions {
6283 cx.background_spawn({
6284 let buffer_snapshot = buffer_snapshot.clone();
6285 async move {
6286 buffer_snapshot.words_in_range(WordsQuery {
6287 fuzzy_contents: None,
6288 range: word_search_range,
6289 skip_digits,
6290 })
6291 }
6292 })
6293 } else {
6294 Task::ready(BTreeMap::default())
6295 };
6296
6297 let snippets = if let Some(provider) = &provider
6298 && provider.show_snippets()
6299 && let Some(project) = self.project()
6300 {
6301 let char_classifier = buffer_snapshot
6302 .char_classifier_at(buffer_position)
6303 .scope_context(Some(CharScopeContext::Completion));
6304 project.update(cx, |project, cx| {
6305 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
6306 })
6307 } else {
6308 Task::ready(Ok(CompletionResponse {
6309 completions: Vec::new(),
6310 display_options: Default::default(),
6311 is_incomplete: false,
6312 }))
6313 };
6314
6315 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
6316
6317 let id = post_inc(&mut self.next_completion_id);
6318 let task = cx.spawn_in(window, async move |editor, cx| {
6319 let Ok(()) = editor.update(cx, |this, _| {
6320 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
6321 }) else {
6322 return;
6323 };
6324
6325 // TODO: Ideally completions from different sources would be selectively re-queried, so
6326 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
6327 let mut completions = Vec::new();
6328 let mut is_incomplete = false;
6329 let mut display_options: Option<CompletionDisplayOptions> = None;
6330 if let Some(provider_responses) = provider_responses.await.log_err()
6331 && !provider_responses.is_empty()
6332 {
6333 for response in provider_responses {
6334 completions.extend(response.completions);
6335 is_incomplete = is_incomplete || response.is_incomplete;
6336 match display_options.as_mut() {
6337 None => {
6338 display_options = Some(response.display_options);
6339 }
6340 Some(options) => options.merge(&response.display_options),
6341 }
6342 }
6343 if completion_settings.words == WordsCompletionMode::Fallback {
6344 words = Task::ready(BTreeMap::default());
6345 }
6346 }
6347 let display_options = display_options.unwrap_or_default();
6348
6349 let mut words = words.await;
6350 if let Some(word_to_exclude) = &word_to_exclude {
6351 words.remove(word_to_exclude);
6352 }
6353 for lsp_completion in &completions {
6354 words.remove(&lsp_completion.new_text);
6355 }
6356 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
6357 replace_range: word_replace_range.clone(),
6358 new_text: word.clone(),
6359 label: CodeLabel::plain(word, None),
6360 match_start: None,
6361 snippet_deduplication_key: None,
6362 icon_path: None,
6363 documentation: None,
6364 source: CompletionSource::BufferWord {
6365 word_range,
6366 resolved: false,
6367 },
6368 insert_text_mode: Some(InsertTextMode::AS_IS),
6369 confirm: None,
6370 }));
6371
6372 completions.extend(
6373 snippets
6374 .await
6375 .into_iter()
6376 .flat_map(|response| response.completions),
6377 );
6378
6379 let menu = if completions.is_empty() {
6380 None
6381 } else {
6382 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
6383 let languages = editor
6384 .workspace
6385 .as_ref()
6386 .and_then(|(workspace, _)| workspace.upgrade())
6387 .map(|workspace| workspace.read(cx).app_state().languages.clone());
6388 let menu = CompletionsMenu::new(
6389 id,
6390 requested_source.unwrap_or(if load_provider_completions {
6391 CompletionsMenuSource::Normal
6392 } else {
6393 CompletionsMenuSource::SnippetsOnly
6394 }),
6395 sort_completions,
6396 show_completion_documentation,
6397 position,
6398 query.clone(),
6399 is_incomplete,
6400 buffer.clone(),
6401 completions.into(),
6402 editor
6403 .context_menu()
6404 .borrow_mut()
6405 .as_ref()
6406 .map(|menu| menu.primary_scroll_handle()),
6407 display_options,
6408 snippet_sort_order,
6409 languages,
6410 language,
6411 cx,
6412 );
6413
6414 let query = if filter_completions { query } else { None };
6415 let matches_task = menu.do_async_filtering(
6416 query.unwrap_or_default(),
6417 buffer_position,
6418 &buffer,
6419 cx,
6420 );
6421 (menu, matches_task)
6422 }) else {
6423 return;
6424 };
6425
6426 let matches = matches_task.await;
6427
6428 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
6429 // Newer menu already set, so exit.
6430 if let Some(CodeContextMenu::Completions(prev_menu)) =
6431 editor.context_menu.borrow().as_ref()
6432 && prev_menu.id > id
6433 {
6434 return;
6435 };
6436
6437 // Only valid to take prev_menu because either the new menu is immediately set
6438 // below, or the menu is hidden.
6439 if let Some(CodeContextMenu::Completions(prev_menu)) =
6440 editor.context_menu.borrow_mut().take()
6441 {
6442 let position_matches =
6443 if prev_menu.initial_position == menu.initial_position {
6444 true
6445 } else {
6446 let snapshot = editor.buffer.read(cx).read(cx);
6447 prev_menu.initial_position.to_offset(&snapshot)
6448 == menu.initial_position.to_offset(&snapshot)
6449 };
6450 if position_matches {
6451 // Preserve markdown cache before `set_filter_results` because it will
6452 // try to populate the documentation cache.
6453 menu.preserve_markdown_cache(prev_menu);
6454 }
6455 };
6456
6457 menu.set_filter_results(matches, provider, window, cx);
6458 }) else {
6459 return;
6460 };
6461
6462 menu.visible().then_some(menu)
6463 };
6464
6465 editor
6466 .update_in(cx, |editor, window, cx| {
6467 if editor.focus_handle.is_focused(window)
6468 && let Some(menu) = menu
6469 {
6470 *editor.context_menu.borrow_mut() =
6471 Some(CodeContextMenu::Completions(menu));
6472
6473 crate::hover_popover::hide_hover(editor, cx);
6474 if editor.show_edit_predictions_in_menu() {
6475 editor.update_visible_edit_prediction(window, cx);
6476 } else {
6477 editor
6478 .discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
6479 }
6480
6481 cx.notify();
6482 return;
6483 }
6484
6485 if editor.completion_tasks.len() <= 1 {
6486 // If there are no more completion tasks and the last menu was empty, we should hide it.
6487 let was_hidden = editor.hide_context_menu(window, cx).is_none();
6488 // If it was already hidden and we don't show edit predictions in the menu,
6489 // we should also show the edit prediction when available.
6490 if was_hidden && editor.show_edit_predictions_in_menu() {
6491 editor.update_visible_edit_prediction(window, cx);
6492 }
6493 }
6494 })
6495 .ok();
6496 });
6497
6498 self.completion_tasks.push((id, task));
6499 }
6500
6501 #[cfg(any(test, feature = "test-support"))]
6502 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
6503 let menu = self.context_menu.borrow();
6504 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
6505 let completions = menu.completions.borrow();
6506 Some(completions.to_vec())
6507 } else {
6508 None
6509 }
6510 }
6511
6512 pub fn with_completions_menu_matching_id<R>(
6513 &self,
6514 id: CompletionId,
6515 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
6516 ) -> R {
6517 let mut context_menu = self.context_menu.borrow_mut();
6518 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
6519 return f(None);
6520 };
6521 if completions_menu.id != id {
6522 return f(None);
6523 }
6524 f(Some(completions_menu))
6525 }
6526
6527 pub fn confirm_completion(
6528 &mut self,
6529 action: &ConfirmCompletion,
6530 window: &mut Window,
6531 cx: &mut Context<Self>,
6532 ) -> Option<Task<Result<()>>> {
6533 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6534 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
6535 }
6536
6537 pub fn confirm_completion_insert(
6538 &mut self,
6539 _: &ConfirmCompletionInsert,
6540 window: &mut Window,
6541 cx: &mut Context<Self>,
6542 ) -> Option<Task<Result<()>>> {
6543 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6544 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
6545 }
6546
6547 pub fn confirm_completion_replace(
6548 &mut self,
6549 _: &ConfirmCompletionReplace,
6550 window: &mut Window,
6551 cx: &mut Context<Self>,
6552 ) -> Option<Task<Result<()>>> {
6553 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6554 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
6555 }
6556
6557 pub fn compose_completion(
6558 &mut self,
6559 action: &ComposeCompletion,
6560 window: &mut Window,
6561 cx: &mut Context<Self>,
6562 ) -> Option<Task<Result<()>>> {
6563 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6564 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
6565 }
6566
6567 fn do_completion(
6568 &mut self,
6569 item_ix: Option<usize>,
6570 intent: CompletionIntent,
6571 window: &mut Window,
6572 cx: &mut Context<Editor>,
6573 ) -> Option<Task<Result<()>>> {
6574 use language::ToOffset as _;
6575
6576 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
6577 else {
6578 return None;
6579 };
6580
6581 let candidate_id = {
6582 let entries = completions_menu.entries.borrow();
6583 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6584 if self.show_edit_predictions_in_menu() {
6585 self.discard_edit_prediction(EditPredictionDiscardReason::Rejected, cx);
6586 }
6587 mat.candidate_id
6588 };
6589
6590 let completion = completions_menu
6591 .completions
6592 .borrow()
6593 .get(candidate_id)?
6594 .clone();
6595 cx.stop_propagation();
6596
6597 let buffer_handle = completions_menu.buffer.clone();
6598
6599 let CompletionEdit {
6600 new_text,
6601 snippet,
6602 replace_range,
6603 } = process_completion_for_edit(
6604 &completion,
6605 intent,
6606 &buffer_handle,
6607 &completions_menu.initial_position.text_anchor,
6608 cx,
6609 );
6610
6611 let buffer = buffer_handle.read(cx);
6612 let snapshot = self.buffer.read(cx).snapshot(cx);
6613 let newest_anchor = self.selections.newest_anchor();
6614 let replace_range_multibuffer = {
6615 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6616 excerpt.map_range_from_buffer(replace_range.clone())
6617 };
6618 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6619 return None;
6620 }
6621
6622 let old_text = buffer
6623 .text_for_range(replace_range.clone())
6624 .collect::<String>();
6625 let lookbehind = newest_anchor
6626 .start
6627 .text_anchor
6628 .to_offset(buffer)
6629 .saturating_sub(replace_range.start.0);
6630 let lookahead = replace_range
6631 .end
6632 .0
6633 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6634 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6635 let suffix = &old_text[lookbehind.min(old_text.len())..];
6636
6637 let selections = self
6638 .selections
6639 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6640 let mut ranges = Vec::new();
6641 let mut all_commit_ranges = Vec::new();
6642 let mut linked_edits = LinkedEdits::new();
6643
6644 let text: Arc<str> = new_text.clone().into();
6645 for selection in &selections {
6646 let range = if selection.id == newest_anchor.id {
6647 replace_range_multibuffer.clone()
6648 } else {
6649 let mut range = selection.range();
6650
6651 // if prefix is present, don't duplicate it
6652 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6653 range.start = range.start.saturating_sub_usize(lookbehind);
6654
6655 // if suffix is also present, mimic the newest cursor and replace it
6656 if selection.id != newest_anchor.id
6657 && snapshot.contains_str_at(range.end, suffix)
6658 {
6659 range.end += lookahead;
6660 }
6661 }
6662 range
6663 };
6664
6665 ranges.push(range.clone());
6666
6667 let start_anchor = snapshot.anchor_before(range.start);
6668 let end_anchor = snapshot.anchor_after(range.end);
6669 let anchor_range = start_anchor.text_anchor..end_anchor.text_anchor;
6670 all_commit_ranges.push(anchor_range.clone());
6671
6672 if !self.linked_edit_ranges.is_empty() {
6673 linked_edits.push(&self, anchor_range, text.clone(), cx);
6674 }
6675 }
6676
6677 let common_prefix_len = old_text
6678 .chars()
6679 .zip(new_text.chars())
6680 .take_while(|(a, b)| a == b)
6681 .map(|(a, _)| a.len_utf8())
6682 .sum::<usize>();
6683
6684 cx.emit(EditorEvent::InputHandled {
6685 utf16_range_to_replace: None,
6686 text: new_text[common_prefix_len..].into(),
6687 });
6688
6689 self.transact(window, cx, |editor, window, cx| {
6690 if let Some(mut snippet) = snippet {
6691 snippet.text = new_text.to_string();
6692 editor
6693 .insert_snippet(&ranges, snippet, window, cx)
6694 .log_err();
6695 } else {
6696 editor.buffer.update(cx, |multi_buffer, cx| {
6697 let auto_indent = match completion.insert_text_mode {
6698 Some(InsertTextMode::AS_IS) => None,
6699 _ => editor.autoindent_mode.clone(),
6700 };
6701 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6702 multi_buffer.edit(edits, auto_indent, cx);
6703 });
6704 }
6705 linked_edits.apply(cx);
6706 editor.refresh_edit_prediction(true, false, window, cx);
6707 });
6708 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6709
6710 let show_new_completions_on_confirm = completion
6711 .confirm
6712 .as_ref()
6713 .is_some_and(|confirm| confirm(intent, window, cx));
6714 if show_new_completions_on_confirm {
6715 self.open_or_update_completions_menu(None, None, false, window, cx);
6716 }
6717
6718 let provider = self.completion_provider.as_ref()?;
6719
6720 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6721 let command = lsp_store.as_ref().and_then(|lsp_store| {
6722 let CompletionSource::Lsp {
6723 lsp_completion,
6724 server_id,
6725 ..
6726 } = &completion.source
6727 else {
6728 return None;
6729 };
6730 let lsp_command = lsp_completion.command.as_ref()?;
6731 let available_commands = lsp_store
6732 .read(cx)
6733 .lsp_server_capabilities
6734 .get(server_id)
6735 .and_then(|server_capabilities| {
6736 server_capabilities
6737 .execute_command_provider
6738 .as_ref()
6739 .map(|options| options.commands.as_slice())
6740 })?;
6741 if available_commands.contains(&lsp_command.command) {
6742 Some(CodeAction {
6743 server_id: *server_id,
6744 range: language::Anchor::MIN..language::Anchor::MIN,
6745 lsp_action: LspAction::Command(lsp_command.clone()),
6746 resolved: false,
6747 })
6748 } else {
6749 None
6750 }
6751 });
6752
6753 drop(completion);
6754 let apply_edits = provider.apply_additional_edits_for_completion(
6755 buffer_handle.clone(),
6756 completions_menu.completions.clone(),
6757 candidate_id,
6758 true,
6759 all_commit_ranges,
6760 cx,
6761 );
6762
6763 let editor_settings = EditorSettings::get_global(cx);
6764 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6765 // After the code completion is finished, users often want to know what signatures are needed.
6766 // so we should automatically call signature_help
6767 self.show_signature_help(&ShowSignatureHelp, window, cx);
6768 }
6769
6770 Some(cx.spawn_in(window, async move |editor, cx| {
6771 apply_edits.await?;
6772
6773 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6774 let title = command.lsp_action.title().to_owned();
6775 let project_transaction = lsp_store
6776 .update(cx, |lsp_store, cx| {
6777 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6778 })
6779 .await
6780 .context("applying post-completion command")?;
6781 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6782 Self::open_project_transaction(
6783 &editor,
6784 workspace.downgrade(),
6785 project_transaction,
6786 title,
6787 cx,
6788 )
6789 .await?;
6790 }
6791 }
6792
6793 Ok(())
6794 }))
6795 }
6796
6797 pub fn toggle_code_actions(
6798 &mut self,
6799 action: &ToggleCodeActions,
6800 window: &mut Window,
6801 cx: &mut Context<Self>,
6802 ) {
6803 let quick_launch = action.quick_launch;
6804 let mut context_menu = self.context_menu.borrow_mut();
6805 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6806 if code_actions.deployed_from == action.deployed_from {
6807 // Toggle if we're selecting the same one
6808 *context_menu = None;
6809 cx.notify();
6810 return;
6811 } else {
6812 // Otherwise, clear it and start a new one
6813 *context_menu = None;
6814 cx.notify();
6815 }
6816 }
6817 drop(context_menu);
6818 let snapshot = self.snapshot(window, cx);
6819 let deployed_from = action.deployed_from.clone();
6820 let action = action.clone();
6821 self.completion_tasks.clear();
6822 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
6823
6824 let multibuffer_point = match &action.deployed_from {
6825 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6826 DisplayPoint::new(*row, 0).to_point(&snapshot)
6827 }
6828 _ => self
6829 .selections
6830 .newest::<Point>(&snapshot.display_snapshot)
6831 .head(),
6832 };
6833 let Some((buffer, buffer_row)) = snapshot
6834 .buffer_snapshot()
6835 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6836 .and_then(|(buffer_snapshot, range)| {
6837 self.buffer()
6838 .read(cx)
6839 .buffer(buffer_snapshot.remote_id())
6840 .map(|buffer| (buffer, range.start.row))
6841 })
6842 else {
6843 return;
6844 };
6845 let buffer_id = buffer.read(cx).remote_id();
6846 let tasks = self
6847 .runnables
6848 .runnables((buffer_id, buffer_row))
6849 .map(|t| Arc::new(t.to_owned()));
6850
6851 if !self.focus_handle.is_focused(window) {
6852 return;
6853 }
6854 let project = self.project.clone();
6855
6856 let code_actions_task = match deployed_from {
6857 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6858 _ => self.code_actions(buffer_row, window, cx),
6859 };
6860
6861 let runnable_task = match deployed_from {
6862 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6863 _ => {
6864 let mut task_context_task = Task::ready(None);
6865 if let Some(tasks) = &tasks
6866 && let Some(project) = project
6867 {
6868 task_context_task =
6869 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6870 }
6871
6872 cx.spawn_in(window, {
6873 let buffer = buffer.clone();
6874 async move |editor, cx| {
6875 let task_context = task_context_task.await;
6876
6877 let resolved_tasks =
6878 tasks
6879 .zip(task_context.clone())
6880 .map(|(tasks, task_context)| ResolvedTasks {
6881 templates: tasks.resolve(&task_context).collect(),
6882 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6883 multibuffer_point.row,
6884 tasks.column,
6885 )),
6886 });
6887 let debug_scenarios = editor
6888 .update(cx, |editor, cx| {
6889 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6890 })?
6891 .await;
6892 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6893 }
6894 })
6895 }
6896 };
6897
6898 cx.spawn_in(window, async move |editor, cx| {
6899 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6900 let code_actions = code_actions_task.await;
6901 let spawn_straight_away = quick_launch
6902 && resolved_tasks
6903 .as_ref()
6904 .is_some_and(|tasks| tasks.templates.len() == 1)
6905 && code_actions
6906 .as_ref()
6907 .is_none_or(|actions| actions.is_empty())
6908 && debug_scenarios.is_empty();
6909
6910 editor.update_in(cx, |editor, window, cx| {
6911 crate::hover_popover::hide_hover(editor, cx);
6912 let actions = CodeActionContents::new(
6913 resolved_tasks,
6914 code_actions,
6915 debug_scenarios,
6916 task_context.unwrap_or_default(),
6917 );
6918
6919 // Don't show the menu if there are no actions available
6920 if actions.is_empty() {
6921 cx.notify();
6922 return Task::ready(Ok(()));
6923 }
6924
6925 *editor.context_menu.borrow_mut() =
6926 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6927 buffer,
6928 actions,
6929 selected_item: Default::default(),
6930 scroll_handle: UniformListScrollHandle::default(),
6931 deployed_from,
6932 }));
6933 cx.notify();
6934 if spawn_straight_away
6935 && let Some(task) = editor.confirm_code_action(
6936 &ConfirmCodeAction { item_ix: Some(0) },
6937 window,
6938 cx,
6939 )
6940 {
6941 return task;
6942 }
6943
6944 Task::ready(Ok(()))
6945 })
6946 })
6947 .detach_and_log_err(cx);
6948 }
6949
6950 fn debug_scenarios(
6951 &mut self,
6952 resolved_tasks: &Option<ResolvedTasks>,
6953 buffer: &Entity<Buffer>,
6954 cx: &mut App,
6955 ) -> Task<Vec<task::DebugScenario>> {
6956 maybe!({
6957 let project = self.project()?;
6958 let dap_store = project.read(cx).dap_store();
6959 let mut scenarios = vec![];
6960 let resolved_tasks = resolved_tasks.as_ref()?;
6961 let buffer = buffer.read(cx);
6962 let language = buffer.language()?;
6963 let debug_adapter = LanguageSettings::for_buffer(&buffer, cx)
6964 .debuggers
6965 .first()
6966 .map(SharedString::from)
6967 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6968
6969 dap_store.update(cx, |dap_store, cx| {
6970 for (_, task) in &resolved_tasks.templates {
6971 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6972 task.original_task().clone(),
6973 debug_adapter.clone().into(),
6974 task.display_label().to_owned().into(),
6975 cx,
6976 );
6977 scenarios.push(maybe_scenario);
6978 }
6979 });
6980 Some(cx.background_spawn(async move {
6981 futures::future::join_all(scenarios)
6982 .await
6983 .into_iter()
6984 .flatten()
6985 .collect::<Vec<_>>()
6986 }))
6987 })
6988 .unwrap_or_else(|| Task::ready(vec![]))
6989 }
6990
6991 fn code_actions(
6992 &mut self,
6993 buffer_row: u32,
6994 window: &mut Window,
6995 cx: &mut Context<Self>,
6996 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6997 let mut task = self.code_actions_task.take();
6998 cx.spawn_in(window, async move |editor, cx| {
6999 while let Some(prev_task) = task {
7000 prev_task.await.log_err();
7001 task = editor
7002 .update(cx, |this, _| this.code_actions_task.take())
7003 .ok()?;
7004 }
7005
7006 editor
7007 .update(cx, |editor, cx| {
7008 editor
7009 .available_code_actions
7010 .clone()
7011 .and_then(|(location, code_actions)| {
7012 let snapshot = location.buffer.read(cx).snapshot();
7013 let point_range = location.range.to_point(&snapshot);
7014 let point_range = point_range.start.row..=point_range.end.row;
7015 if point_range.contains(&buffer_row) {
7016 Some(code_actions)
7017 } else {
7018 None
7019 }
7020 })
7021 })
7022 .ok()
7023 .flatten()
7024 })
7025 }
7026
7027 pub fn confirm_code_action(
7028 &mut self,
7029 action: &ConfirmCodeAction,
7030 window: &mut Window,
7031 cx: &mut Context<Self>,
7032 ) -> Option<Task<Result<()>>> {
7033 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
7034
7035 let actions_menu =
7036 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
7037 menu
7038 } else {
7039 return None;
7040 };
7041
7042 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
7043 let action = actions_menu.actions.get(action_ix)?;
7044 let title = action.label();
7045 let buffer = actions_menu.buffer;
7046 let workspace = self.workspace()?;
7047
7048 match action {
7049 CodeActionsItem::Task(task_source_kind, resolved_task) => {
7050 workspace.update(cx, |workspace, cx| {
7051 workspace.schedule_resolved_task(
7052 task_source_kind,
7053 resolved_task,
7054 false,
7055 window,
7056 cx,
7057 );
7058
7059 Some(Task::ready(Ok(())))
7060 })
7061 }
7062 CodeActionsItem::CodeAction {
7063 excerpt_id,
7064 action,
7065 provider,
7066 } => {
7067 let apply_code_action =
7068 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
7069 let workspace = workspace.downgrade();
7070 Some(cx.spawn_in(window, async move |editor, cx| {
7071 let project_transaction = apply_code_action.await?;
7072 Self::open_project_transaction(
7073 &editor,
7074 workspace,
7075 project_transaction,
7076 title,
7077 cx,
7078 )
7079 .await
7080 }))
7081 }
7082 CodeActionsItem::DebugScenario(scenario) => {
7083 let context = actions_menu.actions.context.into();
7084
7085 workspace.update(cx, |workspace, cx| {
7086 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
7087 workspace.start_debug_session(
7088 scenario,
7089 context,
7090 Some(buffer),
7091 None,
7092 window,
7093 cx,
7094 );
7095 });
7096 Some(Task::ready(Ok(())))
7097 }
7098 }
7099 }
7100
7101 fn open_transaction_for_hidden_buffers(
7102 workspace: Entity<Workspace>,
7103 transaction: ProjectTransaction,
7104 title: String,
7105 window: &mut Window,
7106 cx: &mut Context<Self>,
7107 ) {
7108 if transaction.0.is_empty() {
7109 return;
7110 }
7111
7112 let edited_buffers_already_open = {
7113 let other_editors: Vec<Entity<Editor>> = workspace
7114 .read(cx)
7115 .panes()
7116 .iter()
7117 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
7118 .filter(|editor| editor.entity_id() != cx.entity_id())
7119 .collect();
7120
7121 transaction.0.keys().all(|buffer| {
7122 other_editors.iter().any(|editor| {
7123 let multi_buffer = editor.read(cx).buffer();
7124 multi_buffer.read(cx).is_singleton()
7125 && multi_buffer
7126 .read(cx)
7127 .as_singleton()
7128 .map_or(false, |singleton| {
7129 singleton.entity_id() == buffer.entity_id()
7130 })
7131 })
7132 })
7133 };
7134 if !edited_buffers_already_open {
7135 let workspace = workspace.downgrade();
7136 cx.defer_in(window, move |_, window, cx| {
7137 cx.spawn_in(window, async move |editor, cx| {
7138 Self::open_project_transaction(&editor, workspace, transaction, title, cx)
7139 .await
7140 .ok()
7141 })
7142 .detach();
7143 });
7144 }
7145 }
7146
7147 pub async fn open_project_transaction(
7148 editor: &WeakEntity<Editor>,
7149 workspace: WeakEntity<Workspace>,
7150 transaction: ProjectTransaction,
7151 title: String,
7152 cx: &mut AsyncWindowContext,
7153 ) -> Result<()> {
7154 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
7155 cx.update(|_, cx| {
7156 entries.sort_unstable_by_key(|(buffer, _)| {
7157 buffer.read(cx).file().map(|f| f.path().clone())
7158 });
7159 })?;
7160 if entries.is_empty() {
7161 return Ok(());
7162 }
7163
7164 // If the project transaction's edits are all contained within this editor, then
7165 // avoid opening a new editor to display them.
7166
7167 if let [(buffer, transaction)] = &*entries {
7168 let excerpt = editor.update(cx, |editor, cx| {
7169 editor
7170 .buffer()
7171 .read(cx)
7172 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
7173 })?;
7174 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
7175 && excerpted_buffer == *buffer
7176 {
7177 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
7178 let excerpt_range = excerpt_range.to_offset(buffer);
7179 buffer
7180 .edited_ranges_for_transaction::<usize>(transaction)
7181 .all(|range| {
7182 excerpt_range.start <= range.start && excerpt_range.end >= range.end
7183 })
7184 });
7185
7186 if all_edits_within_excerpt {
7187 return Ok(());
7188 }
7189 }
7190 }
7191
7192 let mut ranges_to_highlight = Vec::new();
7193 let excerpt_buffer = cx.new(|cx| {
7194 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
7195 for (buffer_handle, transaction) in &entries {
7196 let edited_ranges = buffer_handle
7197 .read(cx)
7198 .edited_ranges_for_transaction::<Point>(transaction)
7199 .collect::<Vec<_>>();
7200 let (ranges, _) = multibuffer.set_excerpts_for_path(
7201 PathKey::for_buffer(buffer_handle, cx),
7202 buffer_handle.clone(),
7203 edited_ranges,
7204 multibuffer_context_lines(cx),
7205 cx,
7206 );
7207
7208 ranges_to_highlight.extend(ranges);
7209 }
7210 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
7211 multibuffer
7212 });
7213
7214 workspace.update_in(cx, |workspace, window, cx| {
7215 let project = workspace.project().clone();
7216 let editor =
7217 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
7218 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
7219 editor.update(cx, |editor, cx| {
7220 editor.highlight_background(
7221 HighlightKey::Editor,
7222 &ranges_to_highlight,
7223 |_, theme| theme.colors().editor_highlighted_line_background,
7224 cx,
7225 );
7226 });
7227 })?;
7228
7229 Ok(())
7230 }
7231
7232 pub fn clear_code_action_providers(&mut self) {
7233 self.code_action_providers.clear();
7234 self.available_code_actions.take();
7235 }
7236
7237 pub fn add_code_action_provider(
7238 &mut self,
7239 provider: Rc<dyn CodeActionProvider>,
7240 window: &mut Window,
7241 cx: &mut Context<Self>,
7242 ) {
7243 if self
7244 .code_action_providers
7245 .iter()
7246 .any(|existing_provider| existing_provider.id() == provider.id())
7247 {
7248 return;
7249 }
7250
7251 self.code_action_providers.push(provider);
7252 self.refresh_code_actions(window, cx);
7253 }
7254
7255 pub fn remove_code_action_provider(
7256 &mut self,
7257 id: Arc<str>,
7258 window: &mut Window,
7259 cx: &mut Context<Self>,
7260 ) {
7261 self.code_action_providers
7262 .retain(|provider| provider.id() != id);
7263 self.refresh_code_actions(window, cx);
7264 }
7265
7266 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
7267 !self.code_action_providers.is_empty()
7268 && EditorSettings::get_global(cx).toolbar.code_actions
7269 }
7270
7271 pub fn has_available_code_actions(&self) -> bool {
7272 self.available_code_actions
7273 .as_ref()
7274 .is_some_and(|(_, actions)| !actions.is_empty())
7275 }
7276
7277 fn render_inline_code_actions(
7278 &self,
7279 icon_size: ui::IconSize,
7280 display_row: DisplayRow,
7281 is_active: bool,
7282 cx: &mut Context<Self>,
7283 ) -> AnyElement {
7284 let show_tooltip = !self.context_menu_visible();
7285 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
7286 .icon_size(icon_size)
7287 .shape(ui::IconButtonShape::Square)
7288 .icon_color(ui::Color::Hidden)
7289 .toggle_state(is_active)
7290 .when(show_tooltip, |this| {
7291 this.tooltip({
7292 let focus_handle = self.focus_handle.clone();
7293 move |_window, cx| {
7294 Tooltip::for_action_in(
7295 "Toggle Code Actions",
7296 &ToggleCodeActions {
7297 deployed_from: None,
7298 quick_launch: false,
7299 },
7300 &focus_handle,
7301 cx,
7302 )
7303 }
7304 })
7305 })
7306 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
7307 window.focus(&editor.focus_handle(cx), cx);
7308 editor.toggle_code_actions(
7309 &crate::actions::ToggleCodeActions {
7310 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
7311 display_row,
7312 )),
7313 quick_launch: false,
7314 },
7315 window,
7316 cx,
7317 );
7318 }))
7319 .into_any_element()
7320 }
7321
7322 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
7323 &self.context_menu
7324 }
7325
7326 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7327 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
7328 cx.background_executor()
7329 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
7330 .await;
7331
7332 let (start_buffer, start, _, end, newest_selection) = this
7333 .update(cx, |this, cx| {
7334 let newest_selection = this.selections.newest_anchor().clone();
7335 if newest_selection.head().diff_base_anchor.is_some() {
7336 return None;
7337 }
7338 let display_snapshot = this.display_snapshot(cx);
7339 let newest_selection_adjusted =
7340 this.selections.newest_adjusted(&display_snapshot);
7341 let buffer = this.buffer.read(cx);
7342
7343 let (start_buffer, start) =
7344 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
7345 let (end_buffer, end) =
7346 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
7347
7348 Some((start_buffer, start, end_buffer, end, newest_selection))
7349 })?
7350 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
7351 .context(
7352 "Expected selection to lie in a single buffer when refreshing code actions",
7353 )?;
7354 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
7355 let providers = this.code_action_providers.clone();
7356 let tasks = this
7357 .code_action_providers
7358 .iter()
7359 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
7360 .collect::<Vec<_>>();
7361 (providers, tasks)
7362 })?;
7363
7364 let mut actions = Vec::new();
7365 for (provider, provider_actions) in
7366 providers.into_iter().zip(future::join_all(tasks).await)
7367 {
7368 if let Some(provider_actions) = provider_actions.log_err() {
7369 actions.extend(provider_actions.into_iter().map(|action| {
7370 AvailableCodeAction {
7371 excerpt_id: newest_selection.start.excerpt_id,
7372 action,
7373 provider: provider.clone(),
7374 }
7375 }));
7376 }
7377 }
7378
7379 this.update(cx, |this, cx| {
7380 this.available_code_actions = if actions.is_empty() {
7381 None
7382 } else {
7383 Some((
7384 Location {
7385 buffer: start_buffer,
7386 range: start..end,
7387 },
7388 actions.into(),
7389 ))
7390 };
7391 cx.notify();
7392 })
7393 }));
7394 }
7395
7396 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7397 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
7398 self.show_git_blame_inline = false;
7399
7400 self.show_git_blame_inline_delay_task =
7401 Some(cx.spawn_in(window, async move |this, cx| {
7402 cx.background_executor().timer(delay).await;
7403
7404 this.update(cx, |this, cx| {
7405 this.show_git_blame_inline = true;
7406 cx.notify();
7407 })
7408 .log_err();
7409 }));
7410 }
7411 }
7412
7413 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
7414 let snapshot = self.snapshot(window, cx);
7415 let cursor = self
7416 .selections
7417 .newest::<Point>(&snapshot.display_snapshot)
7418 .head();
7419 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
7420 else {
7421 return;
7422 };
7423
7424 if self.blame.is_none() {
7425 self.start_git_blame(true, window, cx);
7426 }
7427 let Some(blame) = self.blame.as_ref() else {
7428 return;
7429 };
7430
7431 let row_info = RowInfo {
7432 buffer_id: Some(buffer.remote_id()),
7433 buffer_row: Some(point.row),
7434 ..Default::default()
7435 };
7436 let Some((buffer, blame_entry)) = blame
7437 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
7438 .flatten()
7439 else {
7440 return;
7441 };
7442
7443 let anchor = self.selections.newest_anchor().head();
7444 let position = self.to_pixel_point(anchor, &snapshot, window, cx);
7445 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
7446 self.show_blame_popover(
7447 buffer,
7448 &blame_entry,
7449 position + last_bounds.origin,
7450 true,
7451 cx,
7452 );
7453 };
7454 }
7455
7456 fn show_blame_popover(
7457 &mut self,
7458 buffer: BufferId,
7459 blame_entry: &BlameEntry,
7460 position: gpui::Point<Pixels>,
7461 ignore_timeout: bool,
7462 cx: &mut Context<Self>,
7463 ) {
7464 if let Some(state) = &mut self.inline_blame_popover {
7465 state.hide_task.take();
7466 } else {
7467 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
7468 let blame_entry = blame_entry.clone();
7469 let show_task = cx.spawn(async move |editor, cx| {
7470 if !ignore_timeout {
7471 cx.background_executor()
7472 .timer(std::time::Duration::from_millis(blame_popover_delay))
7473 .await;
7474 }
7475 editor
7476 .update(cx, |editor, cx| {
7477 editor.inline_blame_popover_show_task.take();
7478 let Some(blame) = editor.blame.as_ref() else {
7479 return;
7480 };
7481 let blame = blame.read(cx);
7482 let details = blame.details_for_entry(buffer, &blame_entry);
7483 let markdown = cx.new(|cx| {
7484 Markdown::new(
7485 details
7486 .as_ref()
7487 .map(|message| message.message.clone())
7488 .unwrap_or_default(),
7489 None,
7490 None,
7491 cx,
7492 )
7493 });
7494 editor.inline_blame_popover = Some(InlineBlamePopover {
7495 position,
7496 hide_task: None,
7497 popover_bounds: None,
7498 popover_state: InlineBlamePopoverState {
7499 scroll_handle: ScrollHandle::new(),
7500 commit_message: details,
7501 markdown,
7502 },
7503 keyboard_grace: ignore_timeout,
7504 });
7505 cx.notify();
7506 })
7507 .ok();
7508 });
7509 self.inline_blame_popover_show_task = Some(show_task);
7510 }
7511 }
7512
7513 pub fn has_mouse_context_menu(&self) -> bool {
7514 self.mouse_context_menu.is_some()
7515 }
7516
7517 pub fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
7518 self.inline_blame_popover_show_task.take();
7519 if let Some(state) = &mut self.inline_blame_popover {
7520 let hide_task = cx.spawn(async move |editor, cx| {
7521 if !ignore_timeout {
7522 cx.background_executor()
7523 .timer(std::time::Duration::from_millis(100))
7524 .await;
7525 }
7526 editor
7527 .update(cx, |editor, cx| {
7528 editor.inline_blame_popover.take();
7529 cx.notify();
7530 })
7531 .ok();
7532 });
7533 state.hide_task = Some(hide_task);
7534 true
7535 } else {
7536 false
7537 }
7538 }
7539
7540 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
7541 if self.pending_rename.is_some() {
7542 return None;
7543 }
7544
7545 let provider = self.semantics_provider.clone()?;
7546 let buffer = self.buffer.read(cx);
7547 let newest_selection = self.selections.newest_anchor().clone();
7548 let cursor_position = newest_selection.head();
7549 let (cursor_buffer, cursor_buffer_position) =
7550 buffer.text_anchor_for_position(cursor_position, cx)?;
7551 let (tail_buffer, tail_buffer_position) =
7552 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
7553 if cursor_buffer != tail_buffer {
7554 return None;
7555 }
7556
7557 let snapshot = cursor_buffer.read(cx).snapshot();
7558 let word_ranges = cx.background_spawn(async move {
7559 // this might look odd to put on the background thread, but
7560 // `surrounding_word` can be quite expensive as it calls into
7561 // tree-sitter language scopes
7562 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
7563 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
7564 (start_word_range, end_word_range)
7565 });
7566
7567 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
7568 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
7569 let (start_word_range, end_word_range) = word_ranges.await;
7570 if start_word_range != end_word_range {
7571 this.update(cx, |this, cx| {
7572 this.document_highlights_task.take();
7573 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
7574 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
7575 })
7576 .ok();
7577 return;
7578 }
7579 cx.background_executor()
7580 .timer(Duration::from_millis(debounce))
7581 .await;
7582
7583 let highlights = if let Some(highlights) = cx.update(|cx| {
7584 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
7585 }) {
7586 highlights.await.log_err()
7587 } else {
7588 None
7589 };
7590
7591 if let Some(highlights) = highlights {
7592 this.update(cx, |this, cx| {
7593 if this.pending_rename.is_some() {
7594 return;
7595 }
7596
7597 let buffer = this.buffer.read(cx);
7598 if buffer
7599 .text_anchor_for_position(cursor_position, cx)
7600 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
7601 {
7602 return;
7603 }
7604
7605 let cursor_buffer_snapshot = cursor_buffer.read(cx);
7606 let mut write_ranges = Vec::new();
7607 let mut read_ranges = Vec::new();
7608 for highlight in highlights {
7609 let buffer_id = cursor_buffer.read(cx).remote_id();
7610 for (excerpt_id, _, excerpt_range) in
7611 buffer.excerpts_for_buffer(buffer_id, cx)
7612 {
7613 let start = highlight
7614 .range
7615 .start
7616 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
7617 let end = highlight
7618 .range
7619 .end
7620 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
7621 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
7622 continue;
7623 }
7624
7625 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
7626 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7627 write_ranges.push(range);
7628 } else {
7629 read_ranges.push(range);
7630 }
7631 }
7632 }
7633
7634 this.highlight_background(
7635 HighlightKey::DocumentHighlightRead,
7636 &read_ranges,
7637 |_, theme| theme.colors().editor_document_highlight_read_background,
7638 cx,
7639 );
7640 this.highlight_background(
7641 HighlightKey::DocumentHighlightWrite,
7642 &write_ranges,
7643 |_, theme| theme.colors().editor_document_highlight_write_background,
7644 cx,
7645 );
7646 cx.notify();
7647 })
7648 .log_err();
7649 }
7650 }));
7651 None
7652 }
7653
7654 fn prepare_highlight_query_from_selection(
7655 &mut self,
7656 snapshot: &DisplaySnapshot,
7657 cx: &mut Context<Editor>,
7658 ) -> Option<(String, Range<Anchor>)> {
7659 if matches!(self.mode, EditorMode::SingleLine) {
7660 return None;
7661 }
7662 if !EditorSettings::get_global(cx).selection_highlight {
7663 return None;
7664 }
7665 if self.selections.count() != 1 || self.selections.line_mode() {
7666 return None;
7667 }
7668 let selection = self.selections.newest::<Point>(&snapshot);
7669 // If the selection spans multiple rows OR it is empty
7670 if selection.start.row != selection.end.row
7671 || selection.start.column == selection.end.column
7672 {
7673 return None;
7674 }
7675 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7676 let query = snapshot
7677 .buffer_snapshot()
7678 .text_for_range(selection_anchor_range.clone())
7679 .collect::<String>();
7680 if query.trim().is_empty() {
7681 return None;
7682 }
7683 Some((query, selection_anchor_range))
7684 }
7685
7686 #[ztracing::instrument(skip_all)]
7687 fn update_selection_occurrence_highlights(
7688 &mut self,
7689 multi_buffer_snapshot: MultiBufferSnapshot,
7690 query_text: String,
7691 query_range: Range<Anchor>,
7692 multi_buffer_range_to_query: Range<Point>,
7693 use_debounce: bool,
7694 window: &mut Window,
7695 cx: &mut Context<Editor>,
7696 ) -> Task<()> {
7697 cx.spawn_in(window, async move |editor, cx| {
7698 if use_debounce {
7699 cx.background_executor()
7700 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7701 .await;
7702 }
7703 let match_task = cx.background_spawn(async move {
7704 let buffer_ranges = multi_buffer_snapshot
7705 .range_to_buffer_ranges(
7706 multi_buffer_range_to_query.start..=multi_buffer_range_to_query.end,
7707 )
7708 .into_iter()
7709 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7710 let mut match_ranges = Vec::new();
7711 let Ok(regex) = project::search::SearchQuery::text(
7712 query_text,
7713 false,
7714 false,
7715 false,
7716 Default::default(),
7717 Default::default(),
7718 false,
7719 None,
7720 ) else {
7721 return Vec::default();
7722 };
7723 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7724 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7725 match_ranges.extend(
7726 regex
7727 .search(
7728 buffer_snapshot,
7729 Some(search_range.start.0..search_range.end.0),
7730 )
7731 .await
7732 .into_iter()
7733 .filter_map(|match_range| {
7734 let match_start = buffer_snapshot
7735 .anchor_after(search_range.start + match_range.start);
7736 let match_end = buffer_snapshot
7737 .anchor_before(search_range.start + match_range.end);
7738 let match_anchor_range =
7739 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7740 (match_anchor_range != query_range).then_some(match_anchor_range)
7741 }),
7742 );
7743 }
7744 match_ranges
7745 });
7746 let match_ranges = match_task.await;
7747 editor
7748 .update_in(cx, |editor, _, cx| {
7749 if use_debounce {
7750 editor.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
7751 editor.debounced_selection_highlight_complete = true;
7752 } else if editor.debounced_selection_highlight_complete {
7753 return;
7754 }
7755 if !match_ranges.is_empty() {
7756 editor.highlight_background(
7757 HighlightKey::SelectedTextHighlight,
7758 &match_ranges,
7759 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7760 cx,
7761 )
7762 }
7763 })
7764 .log_err();
7765 })
7766 }
7767
7768 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7769 struct NewlineFold;
7770 let type_id = std::any::TypeId::of::<NewlineFold>();
7771 if !self.mode.is_single_line() {
7772 return;
7773 }
7774 let snapshot = self.snapshot(window, cx);
7775 if snapshot.buffer_snapshot().max_point().row == 0 {
7776 return;
7777 }
7778 let task = cx.background_spawn(async move {
7779 let new_newlines = snapshot
7780 .buffer_chars_at(MultiBufferOffset(0))
7781 .filter_map(|(c, i)| {
7782 if c == '\n' {
7783 Some(
7784 snapshot.buffer_snapshot().anchor_after(i)
7785 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7786 )
7787 } else {
7788 None
7789 }
7790 })
7791 .collect::<Vec<_>>();
7792 let existing_newlines = snapshot
7793 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7794 .filter_map(|fold| {
7795 if fold.placeholder.type_tag == Some(type_id) {
7796 Some(fold.range.start..fold.range.end)
7797 } else {
7798 None
7799 }
7800 })
7801 .collect::<Vec<_>>();
7802
7803 (new_newlines, existing_newlines)
7804 });
7805 self.folding_newlines = cx.spawn(async move |this, cx| {
7806 let (new_newlines, existing_newlines) = task.await;
7807 if new_newlines == existing_newlines {
7808 return;
7809 }
7810 let placeholder = FoldPlaceholder {
7811 render: Arc::new(move |_, _, cx| {
7812 div()
7813 .bg(cx.theme().status().hint_background)
7814 .border_b_1()
7815 .size_full()
7816 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7817 .border_color(cx.theme().status().hint)
7818 .child("\\n")
7819 .into_any()
7820 }),
7821 constrain_width: false,
7822 merge_adjacent: false,
7823 type_tag: Some(type_id),
7824 collapsed_text: None,
7825 };
7826 let creases = new_newlines
7827 .into_iter()
7828 .map(|range| Crease::simple(range, placeholder.clone()))
7829 .collect();
7830 this.update(cx, |this, cx| {
7831 this.display_map.update(cx, |display_map, cx| {
7832 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7833 display_map.fold(creases, cx);
7834 });
7835 })
7836 .ok();
7837 });
7838 }
7839
7840 #[ztracing::instrument(skip_all)]
7841 fn refresh_outline_symbols_at_cursor(&mut self, cx: &mut Context<Editor>) {
7842 if !self.lsp_data_enabled() {
7843 return;
7844 }
7845 let cursor = self.selections.newest_anchor().head();
7846 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7847
7848 if self.uses_lsp_document_symbols(cursor, &multi_buffer_snapshot, cx) {
7849 self.outline_symbols_at_cursor =
7850 self.lsp_symbols_at_cursor(cursor, &multi_buffer_snapshot, cx);
7851 cx.emit(EditorEvent::OutlineSymbolsChanged);
7852 cx.notify();
7853 } else {
7854 let syntax = cx.theme().syntax().clone();
7855 let background_task = cx.background_spawn(async move {
7856 multi_buffer_snapshot.symbols_containing(cursor, Some(&syntax))
7857 });
7858 self.refresh_outline_symbols_at_cursor_at_cursor_task =
7859 cx.spawn(async move |this, cx| {
7860 let symbols = background_task.await;
7861 this.update(cx, |this, cx| {
7862 this.outline_symbols_at_cursor = symbols;
7863 cx.emit(EditorEvent::OutlineSymbolsChanged);
7864 cx.notify();
7865 })
7866 .ok();
7867 });
7868 }
7869 }
7870
7871 #[ztracing::instrument(skip_all)]
7872 fn refresh_selected_text_highlights(
7873 &mut self,
7874 snapshot: &DisplaySnapshot,
7875 on_buffer_edit: bool,
7876 window: &mut Window,
7877 cx: &mut Context<Editor>,
7878 ) {
7879 let Some((query_text, query_range)) =
7880 self.prepare_highlight_query_from_selection(snapshot, cx)
7881 else {
7882 self.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
7883 self.quick_selection_highlight_task.take();
7884 self.debounced_selection_highlight_task.take();
7885 self.debounced_selection_highlight_complete = false;
7886 return;
7887 };
7888 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7889 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7890 let query_changed = self
7891 .quick_selection_highlight_task
7892 .as_ref()
7893 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range);
7894 if query_changed {
7895 self.debounced_selection_highlight_complete = false;
7896 }
7897 if on_buffer_edit || query_changed {
7898 self.quick_selection_highlight_task = Some((
7899 query_range.clone(),
7900 self.update_selection_occurrence_highlights(
7901 snapshot.buffer.clone(),
7902 query_text.clone(),
7903 query_range.clone(),
7904 self.multi_buffer_visible_range(&display_snapshot, cx),
7905 false,
7906 window,
7907 cx,
7908 ),
7909 ));
7910 }
7911 if on_buffer_edit
7912 || self
7913 .debounced_selection_highlight_task
7914 .as_ref()
7915 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7916 {
7917 let multi_buffer_start = multi_buffer_snapshot
7918 .anchor_before(MultiBufferOffset(0))
7919 .to_point(&multi_buffer_snapshot);
7920 let multi_buffer_end = multi_buffer_snapshot
7921 .anchor_after(multi_buffer_snapshot.len())
7922 .to_point(&multi_buffer_snapshot);
7923 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7924 self.debounced_selection_highlight_task = Some((
7925 query_range.clone(),
7926 self.update_selection_occurrence_highlights(
7927 snapshot.buffer.clone(),
7928 query_text,
7929 query_range,
7930 multi_buffer_full_range,
7931 true,
7932 window,
7933 cx,
7934 ),
7935 ));
7936 }
7937 }
7938
7939 pub fn multi_buffer_visible_range(
7940 &self,
7941 display_snapshot: &DisplaySnapshot,
7942 cx: &App,
7943 ) -> Range<Point> {
7944 let visible_start = self
7945 .scroll_manager
7946 .native_anchor(display_snapshot, cx)
7947 .anchor
7948 .to_point(display_snapshot.buffer_snapshot())
7949 .to_display_point(display_snapshot);
7950
7951 let mut target_end = visible_start;
7952 *target_end.row_mut() += self.visible_line_count().unwrap_or(0.).ceil() as u32;
7953
7954 visible_start.to_point(display_snapshot)
7955 ..display_snapshot
7956 .clip_point(target_end, Bias::Right)
7957 .to_point(display_snapshot)
7958 }
7959
7960 pub fn refresh_edit_prediction(
7961 &mut self,
7962 debounce: bool,
7963 user_requested: bool,
7964 window: &mut Window,
7965 cx: &mut Context<Self>,
7966 ) -> Option<()> {
7967 if self.leader_id.is_some() {
7968 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7969 return None;
7970 }
7971
7972 let cursor = self.selections.newest_anchor().head();
7973 let (buffer, cursor_buffer_position) =
7974 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7975
7976 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
7977 return None;
7978 }
7979
7980 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7981 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7982 return None;
7983 }
7984
7985 self.update_visible_edit_prediction(window, cx);
7986
7987 if !user_requested
7988 && (!self.should_show_edit_predictions()
7989 || !self.is_focused(window)
7990 || buffer.read(cx).is_empty())
7991 {
7992 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7993 return None;
7994 }
7995
7996 self.edit_prediction_provider()?
7997 .refresh(buffer, cursor_buffer_position, debounce, cx);
7998 Some(())
7999 }
8000
8001 fn show_edit_predictions_in_menu(&self) -> bool {
8002 match self.edit_prediction_settings {
8003 EditPredictionSettings::Disabled => false,
8004 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
8005 }
8006 }
8007
8008 pub fn edit_predictions_enabled(&self) -> bool {
8009 match self.edit_prediction_settings {
8010 EditPredictionSettings::Disabled => false,
8011 EditPredictionSettings::Enabled { .. } => true,
8012 }
8013 }
8014
8015 fn edit_prediction_requires_modifier(&self) -> bool {
8016 match self.edit_prediction_settings {
8017 EditPredictionSettings::Disabled => false,
8018 EditPredictionSettings::Enabled {
8019 preview_requires_modifier,
8020 ..
8021 } => preview_requires_modifier,
8022 }
8023 }
8024
8025 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
8026 if self.edit_prediction_provider.is_none() {
8027 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8028 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8029 return;
8030 }
8031
8032 let selection = self.selections.newest_anchor();
8033 let cursor = selection.head();
8034
8035 if let Some((buffer, cursor_buffer_position)) =
8036 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
8037 {
8038 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
8039 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8040 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8041 return;
8042 }
8043 self.edit_prediction_settings =
8044 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8045 }
8046 }
8047
8048 fn edit_prediction_settings_at_position(
8049 &self,
8050 buffer: &Entity<Buffer>,
8051 buffer_position: language::Anchor,
8052 cx: &App,
8053 ) -> EditPredictionSettings {
8054 if !self.mode.is_full()
8055 || !self.show_edit_predictions_override.unwrap_or(true)
8056 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
8057 {
8058 return EditPredictionSettings::Disabled;
8059 }
8060
8061 if !LanguageSettings::for_buffer(&buffer.read(cx), cx).show_edit_predictions {
8062 return EditPredictionSettings::Disabled;
8063 };
8064
8065 let by_provider = matches!(
8066 self.menu_edit_predictions_policy,
8067 MenuEditPredictionsPolicy::ByProvider
8068 );
8069
8070 let show_in_menu = by_provider
8071 && self
8072 .edit_prediction_provider
8073 .as_ref()
8074 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
8075
8076 let file = buffer.read(cx).file();
8077 let preview_requires_modifier =
8078 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
8079
8080 EditPredictionSettings::Enabled {
8081 show_in_menu,
8082 preview_requires_modifier,
8083 }
8084 }
8085
8086 fn should_show_edit_predictions(&self) -> bool {
8087 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
8088 }
8089
8090 pub fn edit_prediction_preview_is_active(&self) -> bool {
8091 matches!(
8092 self.edit_prediction_preview,
8093 EditPredictionPreview::Active { .. }
8094 )
8095 }
8096
8097 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
8098 let cursor = self.selections.newest_anchor().head();
8099 if let Some((buffer, cursor_position)) =
8100 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
8101 {
8102 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
8103 } else {
8104 false
8105 }
8106 }
8107
8108 pub fn supports_minimap(&self, cx: &App) -> bool {
8109 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
8110 }
8111
8112 fn edit_predictions_enabled_in_buffer(
8113 &self,
8114 buffer: &Entity<Buffer>,
8115 buffer_position: language::Anchor,
8116 cx: &App,
8117 ) -> bool {
8118 maybe!({
8119 if self.read_only(cx) || self.leader_id.is_some() {
8120 return Some(false);
8121 }
8122 let provider = self.edit_prediction_provider()?;
8123 if !provider.is_enabled(buffer, buffer_position, cx) {
8124 return Some(false);
8125 }
8126 let buffer = buffer.read(cx);
8127 let Some(file) = buffer.file() else {
8128 return Some(true);
8129 };
8130 let settings = all_language_settings(Some(file), cx);
8131 Some(settings.edit_predictions_enabled_for_file(file, cx))
8132 })
8133 .unwrap_or(false)
8134 }
8135
8136 pub fn show_edit_prediction(
8137 &mut self,
8138 _: &ShowEditPrediction,
8139 window: &mut Window,
8140 cx: &mut Context<Self>,
8141 ) {
8142 if !self.has_active_edit_prediction() {
8143 self.refresh_edit_prediction(false, true, window, cx);
8144 return;
8145 }
8146
8147 self.update_visible_edit_prediction(window, cx);
8148 }
8149
8150 pub fn display_cursor_names(
8151 &mut self,
8152 _: &DisplayCursorNames,
8153 window: &mut Window,
8154 cx: &mut Context<Self>,
8155 ) {
8156 self.show_cursor_names(window, cx);
8157 }
8158
8159 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8160 self.show_cursor_names = true;
8161 cx.notify();
8162 cx.spawn_in(window, async move |this, cx| {
8163 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
8164 this.update(cx, |this, cx| {
8165 this.show_cursor_names = false;
8166 cx.notify()
8167 })
8168 .ok()
8169 })
8170 .detach();
8171 }
8172
8173 pub fn accept_partial_edit_prediction(
8174 &mut self,
8175 granularity: EditPredictionGranularity,
8176 window: &mut Window,
8177 cx: &mut Context<Self>,
8178 ) {
8179 if self.show_edit_predictions_in_menu() {
8180 self.hide_context_menu(window, cx);
8181 }
8182
8183 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
8184 return;
8185 };
8186
8187 if !matches!(granularity, EditPredictionGranularity::Full) && self.selections.count() != 1 {
8188 return;
8189 }
8190
8191 match &active_edit_prediction.completion {
8192 EditPrediction::MoveWithin { target, .. } => {
8193 let target = *target;
8194
8195 if matches!(granularity, EditPredictionGranularity::Full) {
8196 if let Some(position_map) = &self.last_position_map {
8197 let target_row = target.to_display_point(&position_map.snapshot).row();
8198 let is_visible = position_map.visible_row_range.contains(&target_row);
8199
8200 if is_visible || !self.edit_prediction_requires_modifier() {
8201 self.unfold_ranges(&[target..target], true, false, cx);
8202 self.change_selections(
8203 SelectionEffects::scroll(Autoscroll::newest()),
8204 window,
8205 cx,
8206 |selections| {
8207 selections.select_anchor_ranges([target..target]);
8208 },
8209 );
8210 self.clear_row_highlights::<EditPredictionPreview>();
8211 self.edit_prediction_preview
8212 .set_previous_scroll_position(None);
8213 } else {
8214 // Highlight and request scroll
8215 self.edit_prediction_preview
8216 .set_previous_scroll_position(Some(
8217 position_map.snapshot.scroll_anchor,
8218 ));
8219 self.highlight_rows::<EditPredictionPreview>(
8220 target..target,
8221 cx.theme().colors().editor_highlighted_line_background,
8222 RowHighlightOptions {
8223 autoscroll: true,
8224 ..Default::default()
8225 },
8226 cx,
8227 );
8228 self.request_autoscroll(Autoscroll::fit(), cx);
8229 }
8230 }
8231 } else {
8232 self.change_selections(
8233 SelectionEffects::scroll(Autoscroll::newest()),
8234 window,
8235 cx,
8236 |selections| {
8237 selections.select_anchor_ranges([target..target]);
8238 },
8239 );
8240 }
8241 }
8242 EditPrediction::MoveOutside { snapshot, target } => {
8243 if let Some(workspace) = self.workspace() {
8244 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
8245 .detach_and_log_err(cx);
8246 }
8247 }
8248 EditPrediction::Edit {
8249 edits,
8250 cursor_position,
8251 ..
8252 } => {
8253 self.report_edit_prediction_event(
8254 active_edit_prediction.completion_id.clone(),
8255 true,
8256 cx,
8257 );
8258
8259 match granularity {
8260 EditPredictionGranularity::Full => {
8261 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
8262
8263 // Compute fallback cursor position BEFORE applying the edit,
8264 // so the anchor tracks through the edit correctly
8265 let fallback_cursor_target = {
8266 let snapshot = self.buffer.read(cx).snapshot(cx);
8267 edits.last().unwrap().0.end.bias_right(&snapshot)
8268 };
8269
8270 self.buffer.update(cx, |buffer, cx| {
8271 buffer.edit(edits.iter().cloned(), None, cx)
8272 });
8273
8274 if let Some(provider) = self.edit_prediction_provider() {
8275 provider.accept(cx);
8276 }
8277
8278 // Resolve cursor position after the edit is applied
8279 let cursor_target = if let Some((anchor, offset)) = cursor_position {
8280 // The anchor tracks through the edit, then we add the offset
8281 let snapshot = self.buffer.read(cx).snapshot(cx);
8282 let base_offset = anchor.to_offset(&snapshot).0;
8283 let target_offset =
8284 MultiBufferOffset((base_offset + offset).min(snapshot.len().0));
8285 snapshot.anchor_after(target_offset)
8286 } else {
8287 fallback_cursor_target
8288 };
8289
8290 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
8291 s.select_anchor_ranges([cursor_target..cursor_target]);
8292 });
8293
8294 let selections = self.selections.disjoint_anchors_arc();
8295 if let Some(transaction_id_now) =
8296 self.buffer.read(cx).last_transaction_id(cx)
8297 {
8298 if transaction_id_prev != Some(transaction_id_now) {
8299 self.selection_history
8300 .insert_transaction(transaction_id_now, selections);
8301 }
8302 }
8303
8304 self.update_visible_edit_prediction(window, cx);
8305 if self.active_edit_prediction.is_none() {
8306 self.refresh_edit_prediction(true, true, window, cx);
8307 }
8308 cx.notify();
8309 }
8310 _ => {
8311 let snapshot = self.buffer.read(cx).snapshot(cx);
8312 let cursor_offset = self
8313 .selections
8314 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8315 .head();
8316
8317 let insertion = edits.iter().find_map(|(range, text)| {
8318 let range = range.to_offset(&snapshot);
8319 if range.is_empty() && range.start == cursor_offset {
8320 Some(text)
8321 } else {
8322 None
8323 }
8324 });
8325
8326 if let Some(text) = insertion {
8327 let text_to_insert = match granularity {
8328 EditPredictionGranularity::Word => {
8329 let mut partial = text
8330 .chars()
8331 .by_ref()
8332 .take_while(|c| c.is_alphabetic())
8333 .collect::<String>();
8334 if partial.is_empty() {
8335 partial = text
8336 .chars()
8337 .by_ref()
8338 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
8339 .collect::<String>();
8340 }
8341 partial
8342 }
8343 EditPredictionGranularity::Line => {
8344 if let Some(line) = text.split_inclusive('\n').next() {
8345 line.to_string()
8346 } else {
8347 text.to_string()
8348 }
8349 }
8350 EditPredictionGranularity::Full => unreachable!(),
8351 };
8352
8353 cx.emit(EditorEvent::InputHandled {
8354 utf16_range_to_replace: None,
8355 text: text_to_insert.clone().into(),
8356 });
8357
8358 self.replace_selections(&text_to_insert, None, window, cx, false);
8359 self.refresh_edit_prediction(true, true, window, cx);
8360 cx.notify();
8361 } else {
8362 self.accept_partial_edit_prediction(
8363 EditPredictionGranularity::Full,
8364 window,
8365 cx,
8366 );
8367 }
8368 }
8369 }
8370 }
8371 }
8372 }
8373
8374 pub fn accept_next_word_edit_prediction(
8375 &mut self,
8376 _: &AcceptNextWordEditPrediction,
8377 window: &mut Window,
8378 cx: &mut Context<Self>,
8379 ) {
8380 self.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
8381 }
8382
8383 pub fn accept_next_line_edit_prediction(
8384 &mut self,
8385 _: &AcceptNextLineEditPrediction,
8386 window: &mut Window,
8387 cx: &mut Context<Self>,
8388 ) {
8389 self.accept_partial_edit_prediction(EditPredictionGranularity::Line, window, cx);
8390 }
8391
8392 pub fn accept_edit_prediction(
8393 &mut self,
8394 _: &AcceptEditPrediction,
8395 window: &mut Window,
8396 cx: &mut Context<Self>,
8397 ) {
8398 self.accept_partial_edit_prediction(EditPredictionGranularity::Full, window, cx);
8399 }
8400
8401 fn discard_edit_prediction(
8402 &mut self,
8403 reason: EditPredictionDiscardReason,
8404 cx: &mut Context<Self>,
8405 ) -> bool {
8406 if reason == EditPredictionDiscardReason::Rejected {
8407 let completion_id = self
8408 .active_edit_prediction
8409 .as_ref()
8410 .and_then(|active_completion| active_completion.completion_id.clone());
8411
8412 self.report_edit_prediction_event(completion_id, false, cx);
8413 }
8414
8415 if let Some(provider) = self.edit_prediction_provider() {
8416 provider.discard(reason, cx);
8417 }
8418
8419 self.take_active_edit_prediction(reason == EditPredictionDiscardReason::Ignored, cx)
8420 }
8421
8422 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
8423 let Some(provider) = self.edit_prediction_provider() else {
8424 return;
8425 };
8426
8427 let Some((_, buffer, _)) = self
8428 .buffer
8429 .read(cx)
8430 .excerpt_containing(self.selections.newest_anchor().head(), cx)
8431 else {
8432 return;
8433 };
8434
8435 let extension = buffer
8436 .read(cx)
8437 .file()
8438 .and_then(|file| Some(file.path().extension()?.to_string()));
8439
8440 let event_type = match accepted {
8441 true => "Edit Prediction Accepted",
8442 false => "Edit Prediction Discarded",
8443 };
8444 telemetry::event!(
8445 event_type,
8446 provider = provider.name(),
8447 prediction_id = id,
8448 suggestion_accepted = accepted,
8449 file_extension = extension,
8450 );
8451 }
8452
8453 fn open_editor_at_anchor(
8454 snapshot: &language::BufferSnapshot,
8455 target: language::Anchor,
8456 workspace: &Entity<Workspace>,
8457 window: &mut Window,
8458 cx: &mut App,
8459 ) -> Task<Result<()>> {
8460 workspace.update(cx, |workspace, cx| {
8461 let path = snapshot.file().map(|file| file.full_path(cx));
8462 let Some(path) =
8463 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
8464 else {
8465 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
8466 };
8467 let target = text::ToPoint::to_point(&target, snapshot);
8468 let item = workspace.open_path(path, None, true, window, cx);
8469 window.spawn(cx, async move |cx| {
8470 let Some(editor) = item.await?.downcast::<Editor>() else {
8471 return Ok(());
8472 };
8473 editor
8474 .update_in(cx, |editor, window, cx| {
8475 editor.go_to_singleton_buffer_point(target, window, cx);
8476 })
8477 .ok();
8478 anyhow::Ok(())
8479 })
8480 })
8481 }
8482
8483 pub fn has_active_edit_prediction(&self) -> bool {
8484 self.active_edit_prediction.is_some()
8485 }
8486
8487 fn take_active_edit_prediction(
8488 &mut self,
8489 preserve_stale_in_menu: bool,
8490 cx: &mut Context<Self>,
8491 ) -> bool {
8492 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
8493 if !preserve_stale_in_menu {
8494 self.stale_edit_prediction_in_menu = None;
8495 }
8496 return false;
8497 };
8498
8499 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
8500 self.clear_highlights(HighlightKey::EditPredictionHighlight, cx);
8501 self.stale_edit_prediction_in_menu =
8502 preserve_stale_in_menu.then_some(active_edit_prediction);
8503 true
8504 }
8505
8506 /// Returns true when we're displaying the edit prediction popover below the cursor
8507 /// like we are not previewing and the LSP autocomplete menu is visible
8508 /// or we are in `when_holding_modifier` mode.
8509 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
8510 if self.edit_prediction_preview_is_active()
8511 || !self.show_edit_predictions_in_menu()
8512 || !self.edit_predictions_enabled()
8513 {
8514 return false;
8515 }
8516
8517 if self.has_visible_completions_menu() {
8518 return true;
8519 }
8520
8521 has_completion && self.edit_prediction_requires_modifier()
8522 }
8523
8524 fn handle_modifiers_changed(
8525 &mut self,
8526 modifiers: Modifiers,
8527 position_map: &PositionMap,
8528 window: &mut Window,
8529 cx: &mut Context<Self>,
8530 ) {
8531 self.update_edit_prediction_settings(cx);
8532
8533 // Ensure that the edit prediction preview is updated, even when not
8534 // enabled, if there's an active edit prediction preview.
8535 if self.show_edit_predictions_in_menu()
8536 || self.edit_prediction_requires_modifier()
8537 || matches!(
8538 self.edit_prediction_preview,
8539 EditPredictionPreview::Active { .. }
8540 )
8541 {
8542 self.update_edit_prediction_preview(&modifiers, window, cx);
8543 }
8544
8545 self.update_selection_mode(&modifiers, position_map, window, cx);
8546
8547 let mouse_position = window.mouse_position();
8548 if !position_map.text_hitbox.is_hovered(window) {
8549 return;
8550 }
8551
8552 self.update_hovered_link(
8553 position_map.point_for_position(mouse_position),
8554 Some(mouse_position),
8555 &position_map.snapshot,
8556 modifiers,
8557 window,
8558 cx,
8559 )
8560 }
8561
8562 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8563 match EditorSettings::get_global(cx).multi_cursor_modifier {
8564 MultiCursorModifier::Alt => modifiers.secondary(),
8565 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
8566 }
8567 }
8568
8569 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8570 match EditorSettings::get_global(cx).multi_cursor_modifier {
8571 MultiCursorModifier::Alt => modifiers.alt,
8572 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
8573 }
8574 }
8575
8576 fn columnar_selection_mode(
8577 modifiers: &Modifiers,
8578 cx: &mut Context<Self>,
8579 ) -> Option<ColumnarMode> {
8580 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
8581 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
8582 Some(ColumnarMode::FromMouse)
8583 } else if Self::is_alt_pressed(modifiers, cx) {
8584 Some(ColumnarMode::FromSelection)
8585 } else {
8586 None
8587 }
8588 } else {
8589 None
8590 }
8591 }
8592
8593 fn update_selection_mode(
8594 &mut self,
8595 modifiers: &Modifiers,
8596 position_map: &PositionMap,
8597 window: &mut Window,
8598 cx: &mut Context<Self>,
8599 ) {
8600 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
8601 return;
8602 };
8603 if self.selections.pending_anchor().is_none() {
8604 return;
8605 }
8606
8607 let mouse_position = window.mouse_position();
8608 let point_for_position = position_map.point_for_position(mouse_position);
8609 let position = point_for_position.previous_valid;
8610
8611 self.select(
8612 SelectPhase::BeginColumnar {
8613 position,
8614 reset: false,
8615 mode,
8616 goal_column: point_for_position.exact_unclipped.column(),
8617 },
8618 window,
8619 cx,
8620 );
8621 }
8622
8623 fn update_edit_prediction_preview(
8624 &mut self,
8625 modifiers: &Modifiers,
8626 window: &mut Window,
8627 cx: &mut Context<Self>,
8628 ) {
8629 let modifiers_held = self.edit_prediction_preview_modifiers_held(modifiers, window, cx);
8630
8631 if modifiers_held {
8632 if matches!(
8633 self.edit_prediction_preview,
8634 EditPredictionPreview::Inactive { .. }
8635 ) {
8636 self.edit_prediction_preview = EditPredictionPreview::Active {
8637 previous_scroll_position: None,
8638 since: Instant::now(),
8639 };
8640
8641 self.update_visible_edit_prediction(window, cx);
8642 cx.notify();
8643 }
8644 } else if let EditPredictionPreview::Active {
8645 previous_scroll_position,
8646 since,
8647 } = self.edit_prediction_preview
8648 {
8649 if let (Some(previous_scroll_position), Some(position_map)) =
8650 (previous_scroll_position, self.last_position_map.as_ref())
8651 {
8652 self.set_scroll_position(
8653 previous_scroll_position
8654 .scroll_position(&position_map.snapshot.display_snapshot),
8655 window,
8656 cx,
8657 );
8658 }
8659
8660 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8661 released_too_fast: since.elapsed() < Duration::from_millis(200),
8662 };
8663 self.clear_row_highlights::<EditPredictionPreview>();
8664 self.update_visible_edit_prediction(window, cx);
8665 cx.notify();
8666 }
8667 }
8668
8669 fn update_visible_edit_prediction(
8670 &mut self,
8671 _window: &mut Window,
8672 cx: &mut Context<Self>,
8673 ) -> Option<()> {
8674 if self.ime_transaction.is_some() {
8675 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8676 return None;
8677 }
8678
8679 let selection = self.selections.newest_anchor();
8680 let cursor = selection.head();
8681 let multibuffer = self.buffer.read(cx).snapshot(cx);
8682
8683 // Check project-level disable_ai setting for the current buffer
8684 if let Some((buffer, _)) = self.buffer.read(cx).text_anchor_for_position(cursor, cx) {
8685 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
8686 return None;
8687 }
8688 }
8689 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8690 let excerpt_id = cursor.excerpt_id;
8691
8692 let show_in_menu = self.show_edit_predictions_in_menu();
8693 let completions_menu_has_precedence = !show_in_menu
8694 && (self.context_menu.borrow().is_some()
8695 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8696
8697 if completions_menu_has_precedence
8698 || !offset_selection.is_empty()
8699 || self
8700 .active_edit_prediction
8701 .as_ref()
8702 .is_some_and(|completion| {
8703 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8704 return false;
8705 };
8706 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8707 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8708 !invalidation_range.contains(&offset_selection.head())
8709 })
8710 {
8711 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8712 return None;
8713 }
8714
8715 self.take_active_edit_prediction(true, cx);
8716 let Some(provider) = self.edit_prediction_provider() else {
8717 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8718 return None;
8719 };
8720
8721 let (buffer, cursor_buffer_position) =
8722 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8723
8724 self.edit_prediction_settings =
8725 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8726
8727 self.in_leading_whitespace = multibuffer.is_line_whitespace_upto(cursor);
8728
8729 if self.in_leading_whitespace {
8730 let cursor_point = cursor.to_point(&multibuffer);
8731 let mut suggested_indent = None;
8732 multibuffer.suggested_indents_callback(
8733 cursor_point.row..cursor_point.row + 1,
8734 &mut |_, indent| {
8735 suggested_indent = Some(indent);
8736 ControlFlow::Break(())
8737 },
8738 cx,
8739 );
8740
8741 if let Some(indent) = suggested_indent
8742 && indent.len == cursor_point.column
8743 {
8744 self.in_leading_whitespace = false;
8745 }
8746 }
8747
8748 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8749
8750 let (completion_id, edits, predicted_cursor_position, edit_preview) = match edit_prediction
8751 {
8752 edit_prediction_types::EditPrediction::Local {
8753 id,
8754 edits,
8755 cursor_position,
8756 edit_preview,
8757 } => (id, edits, cursor_position, edit_preview),
8758 edit_prediction_types::EditPrediction::Jump {
8759 id,
8760 snapshot,
8761 target,
8762 } => {
8763 if let Some(provider) = &self.edit_prediction_provider {
8764 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8765 }
8766 self.stale_edit_prediction_in_menu = None;
8767 self.active_edit_prediction = Some(EditPredictionState {
8768 inlay_ids: vec![],
8769 completion: EditPrediction::MoveOutside { snapshot, target },
8770 completion_id: id,
8771 invalidation_range: None,
8772 });
8773 cx.notify();
8774 return Some(());
8775 }
8776 };
8777
8778 let edits = edits
8779 .into_iter()
8780 .flat_map(|(range, new_text)| {
8781 Some((
8782 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8783 new_text,
8784 ))
8785 })
8786 .collect::<Vec<_>>();
8787 if edits.is_empty() {
8788 return None;
8789 }
8790
8791 let cursor_position = predicted_cursor_position.and_then(|predicted| {
8792 let anchor = multibuffer.anchor_in_excerpt(excerpt_id, predicted.anchor)?;
8793 Some((anchor, predicted.offset))
8794 });
8795
8796 let first_edit_start = edits.first().unwrap().0.start;
8797 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8798 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8799
8800 let last_edit_end = edits.last().unwrap().0.end;
8801 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8802 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8803
8804 let cursor_row = cursor.to_point(&multibuffer).row;
8805
8806 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8807
8808 let mut inlay_ids = Vec::new();
8809 let invalidation_row_range;
8810 let move_invalidation_row_range = if cursor_row < edit_start_row {
8811 Some(cursor_row..edit_end_row)
8812 } else if cursor_row > edit_end_row {
8813 Some(edit_start_row..cursor_row)
8814 } else {
8815 None
8816 };
8817 let supports_jump = self
8818 .edit_prediction_provider
8819 .as_ref()
8820 .map(|provider| provider.provider.supports_jump_to_edit())
8821 .unwrap_or(true);
8822
8823 let is_move = supports_jump
8824 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8825 let completion = if is_move {
8826 if let Some(provider) = &self.edit_prediction_provider {
8827 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8828 }
8829 invalidation_row_range =
8830 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8831 let target = first_edit_start;
8832 EditPrediction::MoveWithin { target, snapshot }
8833 } else {
8834 let show_completions_in_menu = self.has_visible_completions_menu();
8835 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8836 && !self.edit_predictions_hidden_for_vim_mode;
8837
8838 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8839 if provider.show_tab_accept_marker() {
8840 EditDisplayMode::TabAccept
8841 } else {
8842 EditDisplayMode::Inline
8843 }
8844 } else {
8845 EditDisplayMode::DiffPopover
8846 };
8847
8848 let report_shown = match display_mode {
8849 EditDisplayMode::DiffPopover | EditDisplayMode::Inline => {
8850 show_completions_in_buffer || show_completions_in_menu
8851 }
8852 EditDisplayMode::TabAccept => {
8853 show_completions_in_menu || self.edit_prediction_preview_is_active()
8854 }
8855 };
8856
8857 if report_shown && let Some(provider) = &self.edit_prediction_provider {
8858 let suggestion_display_type = match display_mode {
8859 EditDisplayMode::DiffPopover => SuggestionDisplayType::DiffPopover,
8860 EditDisplayMode::Inline | EditDisplayMode::TabAccept => {
8861 SuggestionDisplayType::GhostText
8862 }
8863 };
8864 provider.provider.did_show(suggestion_display_type, cx);
8865 }
8866
8867 if show_completions_in_buffer {
8868 if edits
8869 .iter()
8870 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8871 {
8872 let mut inlays = Vec::new();
8873 for (range, new_text) in &edits {
8874 let inlay = Inlay::edit_prediction(
8875 post_inc(&mut self.next_inlay_id),
8876 range.start,
8877 new_text.as_ref(),
8878 );
8879 inlay_ids.push(inlay.id);
8880 inlays.push(inlay);
8881 }
8882
8883 self.splice_inlays(&[], inlays, cx);
8884 } else {
8885 let background_color = cx.theme().status().deleted_background;
8886 self.highlight_text(
8887 HighlightKey::EditPredictionHighlight,
8888 edits.iter().map(|(range, _)| range.clone()).collect(),
8889 HighlightStyle {
8890 background_color: Some(background_color),
8891 ..Default::default()
8892 },
8893 cx,
8894 );
8895 }
8896 }
8897
8898 invalidation_row_range = edit_start_row..edit_end_row;
8899
8900 EditPrediction::Edit {
8901 edits,
8902 cursor_position,
8903 edit_preview,
8904 display_mode,
8905 snapshot,
8906 }
8907 };
8908
8909 let invalidation_range = multibuffer
8910 .anchor_before(Point::new(invalidation_row_range.start, 0))
8911 ..multibuffer.anchor_after(Point::new(
8912 invalidation_row_range.end,
8913 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8914 ));
8915
8916 self.stale_edit_prediction_in_menu = None;
8917 self.active_edit_prediction = Some(EditPredictionState {
8918 inlay_ids,
8919 completion,
8920 completion_id,
8921 invalidation_range: Some(invalidation_range),
8922 });
8923
8924 cx.notify();
8925
8926 Some(())
8927 }
8928
8929 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8930 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8931 }
8932
8933 /// Get all display points of breakpoints that will be rendered within editor
8934 ///
8935 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8936 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8937 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8938 fn active_breakpoints(
8939 &self,
8940 range: Range<DisplayRow>,
8941 window: &mut Window,
8942 cx: &mut Context<Self>,
8943 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8944 let mut breakpoint_display_points = HashMap::default();
8945
8946 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8947 return breakpoint_display_points;
8948 };
8949
8950 let snapshot = self.snapshot(window, cx);
8951
8952 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8953 let Some(project) = self.project() else {
8954 return breakpoint_display_points;
8955 };
8956
8957 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8958 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8959
8960 for (buffer_snapshot, range, excerpt_id) in
8961 multi_buffer_snapshot.range_to_buffer_ranges(range.start..=range.end)
8962 {
8963 let Some(buffer) = project
8964 .read(cx)
8965 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8966 else {
8967 continue;
8968 };
8969 let breakpoints = breakpoint_store.read(cx).breakpoints(
8970 &buffer,
8971 Some(
8972 buffer_snapshot.anchor_before(range.start)
8973 ..buffer_snapshot.anchor_after(range.end),
8974 ),
8975 buffer_snapshot,
8976 cx,
8977 );
8978 for (breakpoint, state) in breakpoints {
8979 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8980 let position = multi_buffer_anchor
8981 .to_point(&multi_buffer_snapshot)
8982 .to_display_point(&snapshot);
8983
8984 breakpoint_display_points.insert(
8985 position.row(),
8986 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8987 );
8988 }
8989 }
8990
8991 breakpoint_display_points
8992 }
8993
8994 fn breakpoint_context_menu(
8995 &self,
8996 anchor: Anchor,
8997 window: &mut Window,
8998 cx: &mut Context<Self>,
8999 ) -> Entity<ui::ContextMenu> {
9000 let weak_editor = cx.weak_entity();
9001 let focus_handle = self.focus_handle(cx);
9002
9003 let row = self
9004 .buffer
9005 .read(cx)
9006 .snapshot(cx)
9007 .summary_for_anchor::<Point>(&anchor)
9008 .row;
9009
9010 let breakpoint = self
9011 .breakpoint_at_row(row, window, cx)
9012 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
9013
9014 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
9015 "Edit Log Breakpoint"
9016 } else {
9017 "Set Log Breakpoint"
9018 };
9019
9020 let condition_breakpoint_msg = if breakpoint
9021 .as_ref()
9022 .is_some_and(|bp| bp.1.condition.is_some())
9023 {
9024 "Edit Condition Breakpoint"
9025 } else {
9026 "Set Condition Breakpoint"
9027 };
9028
9029 let hit_condition_breakpoint_msg = if breakpoint
9030 .as_ref()
9031 .is_some_and(|bp| bp.1.hit_condition.is_some())
9032 {
9033 "Edit Hit Condition Breakpoint"
9034 } else {
9035 "Set Hit Condition Breakpoint"
9036 };
9037
9038 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
9039 "Unset Breakpoint"
9040 } else {
9041 "Set Breakpoint"
9042 };
9043
9044 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
9045
9046 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
9047 BreakpointState::Enabled => Some("Disable"),
9048 BreakpointState::Disabled => Some("Enable"),
9049 });
9050
9051 let (anchor, breakpoint) =
9052 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
9053
9054 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
9055 menu.on_blur_subscription(Subscription::new(|| {}))
9056 .context(focus_handle)
9057 .when(run_to_cursor, |this| {
9058 let weak_editor = weak_editor.clone();
9059 this.entry("Run to Cursor", None, move |window, cx| {
9060 weak_editor
9061 .update(cx, |editor, cx| {
9062 editor.change_selections(
9063 SelectionEffects::no_scroll(),
9064 window,
9065 cx,
9066 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
9067 );
9068 })
9069 .ok();
9070
9071 window.dispatch_action(Box::new(RunToCursor), cx);
9072 })
9073 .separator()
9074 })
9075 .when_some(toggle_state_msg, |this, msg| {
9076 this.entry(msg, None, {
9077 let weak_editor = weak_editor.clone();
9078 let breakpoint = breakpoint.clone();
9079 move |_window, cx| {
9080 weak_editor
9081 .update(cx, |this, cx| {
9082 this.edit_breakpoint_at_anchor(
9083 anchor,
9084 breakpoint.as_ref().clone(),
9085 BreakpointEditAction::InvertState,
9086 cx,
9087 );
9088 })
9089 .log_err();
9090 }
9091 })
9092 })
9093 .entry(set_breakpoint_msg, None, {
9094 let weak_editor = weak_editor.clone();
9095 let breakpoint = breakpoint.clone();
9096 move |_window, cx| {
9097 weak_editor
9098 .update(cx, |this, cx| {
9099 this.edit_breakpoint_at_anchor(
9100 anchor,
9101 breakpoint.as_ref().clone(),
9102 BreakpointEditAction::Toggle,
9103 cx,
9104 );
9105 })
9106 .log_err();
9107 }
9108 })
9109 .entry(log_breakpoint_msg, None, {
9110 let breakpoint = breakpoint.clone();
9111 let weak_editor = weak_editor.clone();
9112 move |window, cx| {
9113 weak_editor
9114 .update(cx, |this, cx| {
9115 this.add_edit_breakpoint_block(
9116 anchor,
9117 breakpoint.as_ref(),
9118 BreakpointPromptEditAction::Log,
9119 window,
9120 cx,
9121 );
9122 })
9123 .log_err();
9124 }
9125 })
9126 .entry(condition_breakpoint_msg, None, {
9127 let breakpoint = breakpoint.clone();
9128 let weak_editor = weak_editor.clone();
9129 move |window, cx| {
9130 weak_editor
9131 .update(cx, |this, cx| {
9132 this.add_edit_breakpoint_block(
9133 anchor,
9134 breakpoint.as_ref(),
9135 BreakpointPromptEditAction::Condition,
9136 window,
9137 cx,
9138 );
9139 })
9140 .log_err();
9141 }
9142 })
9143 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
9144 weak_editor
9145 .update(cx, |this, cx| {
9146 this.add_edit_breakpoint_block(
9147 anchor,
9148 breakpoint.as_ref(),
9149 BreakpointPromptEditAction::HitCondition,
9150 window,
9151 cx,
9152 );
9153 })
9154 .log_err();
9155 })
9156 })
9157 }
9158
9159 fn render_breakpoint(
9160 &self,
9161 position: Anchor,
9162 row: DisplayRow,
9163 breakpoint: &Breakpoint,
9164 state: Option<BreakpointSessionState>,
9165 cx: &mut Context<Self>,
9166 ) -> IconButton {
9167 let is_rejected = state.is_some_and(|s| !s.verified);
9168 // Is it a breakpoint that shows up when hovering over gutter?
9169 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
9170 (false, false),
9171 |PhantomBreakpointIndicator {
9172 is_active,
9173 display_row,
9174 collides_with_existing_breakpoint,
9175 }| {
9176 (
9177 is_active && display_row == row,
9178 collides_with_existing_breakpoint,
9179 )
9180 },
9181 );
9182
9183 let (color, icon) = {
9184 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
9185 (false, false) => ui::IconName::DebugBreakpoint,
9186 (true, false) => ui::IconName::DebugLogBreakpoint,
9187 (false, true) => ui::IconName::DebugDisabledBreakpoint,
9188 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
9189 };
9190
9191 let theme_colors = cx.theme().colors();
9192
9193 let color = if is_phantom {
9194 if collides_with_existing {
9195 Color::Custom(
9196 theme_colors
9197 .debugger_accent
9198 .blend(theme_colors.text.opacity(0.6)),
9199 )
9200 } else {
9201 Color::Hint
9202 }
9203 } else if is_rejected {
9204 Color::Disabled
9205 } else {
9206 Color::Debugger
9207 };
9208
9209 (color, icon)
9210 };
9211
9212 let breakpoint = Arc::from(breakpoint.clone());
9213
9214 let alt_as_text = gpui::Keystroke {
9215 modifiers: Modifiers::secondary_key(),
9216 ..Default::default()
9217 };
9218 let primary_action_text = if breakpoint.is_disabled() {
9219 "Enable breakpoint"
9220 } else if is_phantom && !collides_with_existing {
9221 "Set breakpoint"
9222 } else {
9223 "Unset breakpoint"
9224 };
9225 let focus_handle = self.focus_handle.clone();
9226
9227 let meta = if is_rejected {
9228 SharedString::from("No executable code is associated with this line.")
9229 } else if collides_with_existing && !breakpoint.is_disabled() {
9230 SharedString::from(format!(
9231 "{alt_as_text}-click to disable,\nright-click for more options."
9232 ))
9233 } else {
9234 SharedString::from("Right-click for more options.")
9235 };
9236 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
9237 .icon_size(IconSize::XSmall)
9238 .size(ui::ButtonSize::None)
9239 .when(is_rejected, |this| {
9240 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
9241 })
9242 .icon_color(color)
9243 .style(ButtonStyle::Transparent)
9244 .on_click(cx.listener({
9245 move |editor, event: &ClickEvent, window, cx| {
9246 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
9247 BreakpointEditAction::InvertState
9248 } else {
9249 BreakpointEditAction::Toggle
9250 };
9251
9252 window.focus(&editor.focus_handle(cx), cx);
9253 editor.update_breakpoint_collision_on_toggle(row, &edit_action);
9254 editor.edit_breakpoint_at_anchor(
9255 position,
9256 breakpoint.as_ref().clone(),
9257 edit_action,
9258 cx,
9259 );
9260 }
9261 }))
9262 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
9263 editor.set_breakpoint_context_menu(
9264 row,
9265 Some(position),
9266 event.position(),
9267 window,
9268 cx,
9269 );
9270 }))
9271 .tooltip(move |_window, cx| {
9272 Tooltip::with_meta_in(
9273 primary_action_text,
9274 Some(&ToggleBreakpoint),
9275 meta.clone(),
9276 &focus_handle,
9277 cx,
9278 )
9279 })
9280 }
9281
9282 fn build_tasks_context(
9283 project: &Entity<Project>,
9284 buffer: &Entity<Buffer>,
9285 buffer_row: u32,
9286 tasks: &Arc<RunnableTasks>,
9287 cx: &mut Context<Self>,
9288 ) -> Task<Option<task::TaskContext>> {
9289 let position = Point::new(buffer_row, tasks.column);
9290 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
9291 let location = Location {
9292 buffer: buffer.clone(),
9293 range: range_start..range_start,
9294 };
9295 // Fill in the environmental variables from the tree-sitter captures
9296 let mut captured_task_variables = TaskVariables::default();
9297 for (capture_name, value) in tasks.extra_variables.clone() {
9298 captured_task_variables.insert(
9299 task::VariableName::Custom(capture_name.into()),
9300 value.clone(),
9301 );
9302 }
9303 project.update(cx, |project, cx| {
9304 project.task_store().update(cx, |task_store, cx| {
9305 task_store.task_context_for_location(captured_task_variables, location, cx)
9306 })
9307 })
9308 }
9309
9310 pub fn context_menu_visible(&self) -> bool {
9311 !self.edit_prediction_preview_is_active()
9312 && self
9313 .context_menu
9314 .borrow()
9315 .as_ref()
9316 .is_some_and(|menu| menu.visible())
9317 }
9318
9319 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
9320 self.context_menu
9321 .borrow()
9322 .as_ref()
9323 .map(|menu| menu.origin())
9324 }
9325
9326 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
9327 self.context_menu_options = Some(options);
9328 }
9329
9330 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
9331 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
9332
9333 fn render_edit_prediction_popover(
9334 &mut self,
9335 text_bounds: &Bounds<Pixels>,
9336 content_origin: gpui::Point<Pixels>,
9337 right_margin: Pixels,
9338 editor_snapshot: &EditorSnapshot,
9339 visible_row_range: Range<DisplayRow>,
9340 scroll_top: ScrollOffset,
9341 scroll_bottom: ScrollOffset,
9342 line_layouts: &[LineWithInvisibles],
9343 line_height: Pixels,
9344 scroll_position: gpui::Point<ScrollOffset>,
9345 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9346 newest_selection_head: Option<DisplayPoint>,
9347 editor_width: Pixels,
9348 style: &EditorStyle,
9349 window: &mut Window,
9350 cx: &mut App,
9351 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9352 if self.mode().is_minimap() {
9353 return None;
9354 }
9355 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
9356
9357 if self.edit_prediction_visible_in_cursor_popover(true) {
9358 return None;
9359 }
9360
9361 match &active_edit_prediction.completion {
9362 EditPrediction::MoveWithin { target, .. } => {
9363 let target_display_point = target.to_display_point(editor_snapshot);
9364
9365 if self.edit_prediction_requires_modifier() {
9366 if !self.edit_prediction_preview_is_active() {
9367 return None;
9368 }
9369
9370 self.render_edit_prediction_modifier_jump_popover(
9371 text_bounds,
9372 content_origin,
9373 visible_row_range,
9374 line_layouts,
9375 line_height,
9376 scroll_pixel_position,
9377 newest_selection_head,
9378 target_display_point,
9379 window,
9380 cx,
9381 )
9382 } else {
9383 self.render_edit_prediction_eager_jump_popover(
9384 text_bounds,
9385 content_origin,
9386 editor_snapshot,
9387 visible_row_range,
9388 scroll_top,
9389 scroll_bottom,
9390 line_height,
9391 scroll_pixel_position,
9392 target_display_point,
9393 editor_width,
9394 window,
9395 cx,
9396 )
9397 }
9398 }
9399 EditPrediction::Edit {
9400 display_mode: EditDisplayMode::Inline,
9401 ..
9402 } => None,
9403 EditPrediction::Edit {
9404 display_mode: EditDisplayMode::TabAccept,
9405 edits,
9406 ..
9407 } => {
9408 let range = &edits.first()?.0;
9409 let target_display_point = range.end.to_display_point(editor_snapshot);
9410
9411 self.render_edit_prediction_end_of_line_popover(
9412 "Accept",
9413 editor_snapshot,
9414 visible_row_range,
9415 target_display_point,
9416 line_height,
9417 scroll_pixel_position,
9418 content_origin,
9419 editor_width,
9420 window,
9421 cx,
9422 )
9423 }
9424 EditPrediction::Edit {
9425 edits,
9426 edit_preview,
9427 display_mode: EditDisplayMode::DiffPopover,
9428 snapshot,
9429 ..
9430 } => self.render_edit_prediction_diff_popover(
9431 text_bounds,
9432 content_origin,
9433 right_margin,
9434 editor_snapshot,
9435 visible_row_range,
9436 line_layouts,
9437 line_height,
9438 scroll_position,
9439 scroll_pixel_position,
9440 newest_selection_head,
9441 editor_width,
9442 style,
9443 edits,
9444 edit_preview,
9445 snapshot,
9446 window,
9447 cx,
9448 ),
9449 EditPrediction::MoveOutside { snapshot, .. } => {
9450 let mut element = self
9451 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
9452 .into_any();
9453
9454 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9455 let origin_x = text_bounds.size.width - size.width - px(30.);
9456 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
9457 element.prepaint_at(origin, window, cx);
9458
9459 Some((element, origin))
9460 }
9461 }
9462 }
9463
9464 fn render_edit_prediction_modifier_jump_popover(
9465 &mut self,
9466 text_bounds: &Bounds<Pixels>,
9467 content_origin: gpui::Point<Pixels>,
9468 visible_row_range: Range<DisplayRow>,
9469 line_layouts: &[LineWithInvisibles],
9470 line_height: Pixels,
9471 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9472 newest_selection_head: Option<DisplayPoint>,
9473 target_display_point: DisplayPoint,
9474 window: &mut Window,
9475 cx: &mut App,
9476 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9477 let scrolled_content_origin =
9478 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
9479
9480 const SCROLL_PADDING_Y: Pixels = px(12.);
9481
9482 if target_display_point.row() < visible_row_range.start {
9483 return self.render_edit_prediction_scroll_popover(
9484 &|_| SCROLL_PADDING_Y,
9485 IconName::ArrowUp,
9486 visible_row_range,
9487 line_layouts,
9488 newest_selection_head,
9489 scrolled_content_origin,
9490 window,
9491 cx,
9492 );
9493 } else if target_display_point.row() >= visible_row_range.end {
9494 return self.render_edit_prediction_scroll_popover(
9495 &|size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
9496 IconName::ArrowDown,
9497 visible_row_range,
9498 line_layouts,
9499 newest_selection_head,
9500 scrolled_content_origin,
9501 window,
9502 cx,
9503 );
9504 }
9505
9506 const POLE_WIDTH: Pixels = px(2.);
9507
9508 let line_layout =
9509 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
9510 let target_column = target_display_point.column() as usize;
9511
9512 let target_x = line_layout.x_for_index(target_column);
9513 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
9514 - scroll_pixel_position.y;
9515
9516 let flag_on_right = target_x < text_bounds.size.width / 2.;
9517
9518 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
9519 border_color.l += 0.001;
9520
9521 let mut element = v_flex()
9522 .items_end()
9523 .when(flag_on_right, |el| el.items_start())
9524 .child(if flag_on_right {
9525 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9526 .rounded_bl(px(0.))
9527 .rounded_tl(px(0.))
9528 .border_l_2()
9529 .border_color(border_color)
9530 } else {
9531 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9532 .rounded_br(px(0.))
9533 .rounded_tr(px(0.))
9534 .border_r_2()
9535 .border_color(border_color)
9536 })
9537 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9538 .into_any();
9539
9540 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9541
9542 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9543 - point(
9544 if flag_on_right {
9545 POLE_WIDTH
9546 } else {
9547 size.width - POLE_WIDTH
9548 },
9549 size.height - line_height,
9550 );
9551
9552 origin.x = origin.x.max(content_origin.x);
9553
9554 element.prepaint_at(origin, window, cx);
9555
9556 Some((element, origin))
9557 }
9558
9559 fn render_edit_prediction_scroll_popover(
9560 &mut self,
9561 to_y: &dyn Fn(Size<Pixels>) -> Pixels,
9562 scroll_icon: IconName,
9563 visible_row_range: Range<DisplayRow>,
9564 line_layouts: &[LineWithInvisibles],
9565 newest_selection_head: Option<DisplayPoint>,
9566 scrolled_content_origin: gpui::Point<Pixels>,
9567 window: &mut Window,
9568 cx: &mut App,
9569 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9570 let mut element = self
9571 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9572 .into_any();
9573
9574 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9575
9576 let cursor = newest_selection_head?;
9577 let cursor_row_layout =
9578 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9579 let cursor_column = cursor.column() as usize;
9580
9581 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9582
9583 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9584
9585 element.prepaint_at(origin, window, cx);
9586 Some((element, origin))
9587 }
9588
9589 fn render_edit_prediction_eager_jump_popover(
9590 &mut self,
9591 text_bounds: &Bounds<Pixels>,
9592 content_origin: gpui::Point<Pixels>,
9593 editor_snapshot: &EditorSnapshot,
9594 visible_row_range: Range<DisplayRow>,
9595 scroll_top: ScrollOffset,
9596 scroll_bottom: ScrollOffset,
9597 line_height: Pixels,
9598 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9599 target_display_point: DisplayPoint,
9600 editor_width: Pixels,
9601 window: &mut Window,
9602 cx: &mut App,
9603 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9604 if target_display_point.row().as_f64() < scroll_top {
9605 let mut element = self
9606 .render_edit_prediction_line_popover(
9607 "Jump to Edit",
9608 Some(IconName::ArrowUp),
9609 window,
9610 cx,
9611 )
9612 .into_any();
9613
9614 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9615 let offset = point(
9616 (text_bounds.size.width - size.width) / 2.,
9617 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9618 );
9619
9620 let origin = text_bounds.origin + offset;
9621 element.prepaint_at(origin, window, cx);
9622 Some((element, origin))
9623 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9624 let mut element = self
9625 .render_edit_prediction_line_popover(
9626 "Jump to Edit",
9627 Some(IconName::ArrowDown),
9628 window,
9629 cx,
9630 )
9631 .into_any();
9632
9633 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9634 let offset = point(
9635 (text_bounds.size.width - size.width) / 2.,
9636 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9637 );
9638
9639 let origin = text_bounds.origin + offset;
9640 element.prepaint_at(origin, window, cx);
9641 Some((element, origin))
9642 } else {
9643 self.render_edit_prediction_end_of_line_popover(
9644 "Jump to Edit",
9645 editor_snapshot,
9646 visible_row_range,
9647 target_display_point,
9648 line_height,
9649 scroll_pixel_position,
9650 content_origin,
9651 editor_width,
9652 window,
9653 cx,
9654 )
9655 }
9656 }
9657
9658 fn render_edit_prediction_end_of_line_popover(
9659 self: &mut Editor,
9660 label: &'static str,
9661 editor_snapshot: &EditorSnapshot,
9662 visible_row_range: Range<DisplayRow>,
9663 target_display_point: DisplayPoint,
9664 line_height: Pixels,
9665 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9666 content_origin: gpui::Point<Pixels>,
9667 editor_width: Pixels,
9668 window: &mut Window,
9669 cx: &mut App,
9670 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9671 let target_line_end = DisplayPoint::new(
9672 target_display_point.row(),
9673 editor_snapshot.line_len(target_display_point.row()),
9674 );
9675
9676 let mut element = self
9677 .render_edit_prediction_line_popover(label, None, window, cx)
9678 .into_any();
9679
9680 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9681
9682 let line_origin =
9683 self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
9684
9685 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9686 let mut origin = start_point
9687 + line_origin
9688 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9689 origin.x = origin.x.max(content_origin.x);
9690
9691 let max_x = content_origin.x + editor_width - size.width;
9692
9693 if origin.x > max_x {
9694 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9695
9696 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9697 origin.y += offset;
9698 IconName::ArrowUp
9699 } else {
9700 origin.y -= offset;
9701 IconName::ArrowDown
9702 };
9703
9704 element = self
9705 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9706 .into_any();
9707
9708 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9709
9710 origin.x = content_origin.x + editor_width - size.width - px(2.);
9711 }
9712
9713 element.prepaint_at(origin, window, cx);
9714 Some((element, origin))
9715 }
9716
9717 fn render_edit_prediction_diff_popover(
9718 self: &Editor,
9719 text_bounds: &Bounds<Pixels>,
9720 content_origin: gpui::Point<Pixels>,
9721 right_margin: Pixels,
9722 editor_snapshot: &EditorSnapshot,
9723 visible_row_range: Range<DisplayRow>,
9724 line_layouts: &[LineWithInvisibles],
9725 line_height: Pixels,
9726 scroll_position: gpui::Point<ScrollOffset>,
9727 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9728 newest_selection_head: Option<DisplayPoint>,
9729 editor_width: Pixels,
9730 style: &EditorStyle,
9731 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9732 edit_preview: &Option<language::EditPreview>,
9733 snapshot: &language::BufferSnapshot,
9734 window: &mut Window,
9735 cx: &mut App,
9736 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9737 let edit_start = edits
9738 .first()
9739 .unwrap()
9740 .0
9741 .start
9742 .to_display_point(editor_snapshot);
9743 let edit_end = edits
9744 .last()
9745 .unwrap()
9746 .0
9747 .end
9748 .to_display_point(editor_snapshot);
9749
9750 let is_visible = visible_row_range.contains(&edit_start.row())
9751 || visible_row_range.contains(&edit_end.row());
9752 if !is_visible {
9753 return None;
9754 }
9755
9756 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9757 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9758 } else {
9759 // Fallback for providers without edit_preview
9760 crate::edit_prediction_fallback_text(edits, cx)
9761 };
9762
9763 let styled_text = highlighted_edits.to_styled_text(&style.text);
9764 let line_count = highlighted_edits.text.lines().count();
9765
9766 const BORDER_WIDTH: Pixels = px(1.);
9767
9768 let keybind = self.render_edit_prediction_keybind(window, cx);
9769 let has_keybind = keybind.is_some();
9770
9771 let mut element = h_flex()
9772 .items_start()
9773 .child(
9774 h_flex()
9775 .bg(cx.theme().colors().editor_background)
9776 .border(BORDER_WIDTH)
9777 .shadow_xs()
9778 .border_color(cx.theme().colors().border)
9779 .rounded_l_lg()
9780 .when(line_count > 1, |el| el.rounded_br_lg())
9781 .pr_1()
9782 .child(styled_text),
9783 )
9784 .child(
9785 h_flex()
9786 .h(line_height + BORDER_WIDTH * 2.)
9787 .px_1p5()
9788 .gap_1()
9789 // Workaround: For some reason, there's a gap if we don't do this
9790 .ml(-BORDER_WIDTH)
9791 .shadow(vec![gpui::BoxShadow {
9792 color: gpui::black().opacity(0.05),
9793 offset: point(px(1.), px(1.)),
9794 blur_radius: px(2.),
9795 spread_radius: px(0.),
9796 }])
9797 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9798 .border(BORDER_WIDTH)
9799 .border_color(cx.theme().colors().border)
9800 .rounded_r_lg()
9801 .id("edit_prediction_diff_popover_keybind")
9802 .when(!has_keybind, |el| {
9803 let status_colors = cx.theme().status();
9804
9805 el.bg(status_colors.error_background)
9806 .border_color(status_colors.error.opacity(0.6))
9807 .child(Icon::new(IconName::Info).color(Color::Error))
9808 .cursor_default()
9809 .hoverable_tooltip(move |_window, cx| {
9810 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9811 })
9812 })
9813 .children(keybind),
9814 )
9815 .into_any();
9816
9817 let longest_row =
9818 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9819 let longest_line_width = if visible_row_range.contains(&longest_row) {
9820 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9821 } else {
9822 layout_line(
9823 longest_row,
9824 editor_snapshot,
9825 style,
9826 editor_width,
9827 |_| false,
9828 window,
9829 cx,
9830 )
9831 .width
9832 };
9833
9834 let viewport_bounds =
9835 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9836 right: -right_margin,
9837 ..Default::default()
9838 });
9839
9840 let x_after_longest = Pixels::from(
9841 ScrollPixelOffset::from(
9842 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9843 ) - scroll_pixel_position.x,
9844 );
9845
9846 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9847
9848 // Fully visible if it can be displayed within the window (allow overlapping other
9849 // panes). However, this is only allowed if the popover starts within text_bounds.
9850 let can_position_to_the_right = x_after_longest < text_bounds.right()
9851 && x_after_longest + element_bounds.width < viewport_bounds.right();
9852
9853 let mut origin = if can_position_to_the_right {
9854 point(
9855 x_after_longest,
9856 text_bounds.origin.y
9857 + Pixels::from(
9858 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9859 - scroll_pixel_position.y,
9860 ),
9861 )
9862 } else {
9863 let cursor_row = newest_selection_head.map(|head| head.row());
9864 let above_edit = edit_start
9865 .row()
9866 .0
9867 .checked_sub(line_count as u32)
9868 .map(DisplayRow);
9869 let below_edit = Some(edit_end.row() + 1);
9870 let above_cursor =
9871 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9872 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9873
9874 // Place the edit popover adjacent to the edit if there is a location
9875 // available that is onscreen and does not obscure the cursor. Otherwise,
9876 // place it adjacent to the cursor.
9877 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9878 .into_iter()
9879 .flatten()
9880 .find(|&start_row| {
9881 let end_row = start_row + line_count as u32;
9882 visible_row_range.contains(&start_row)
9883 && visible_row_range.contains(&end_row)
9884 && cursor_row
9885 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9886 })?;
9887
9888 content_origin
9889 + point(
9890 Pixels::from(-scroll_pixel_position.x),
9891 Pixels::from(
9892 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9893 ),
9894 )
9895 };
9896
9897 origin.x -= BORDER_WIDTH;
9898
9899 window.with_content_mask(
9900 Some(gpui::ContentMask {
9901 bounds: *text_bounds,
9902 }),
9903 |window| {
9904 window.defer_draw(element, origin, 1, Some(window.content_mask()));
9905 },
9906 );
9907
9908 // Do not return an element, since it will already be drawn due to defer_draw.
9909 None
9910 }
9911
9912 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9913 px(30.)
9914 }
9915
9916 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9917 if self.read_only(cx) {
9918 cx.theme().players().read_only()
9919 } else {
9920 self.style.as_ref().unwrap().local_player
9921 }
9922 }
9923
9924 fn render_edit_prediction_inline_keystroke(
9925 &self,
9926 keystroke: &gpui::KeybindingKeystroke,
9927 modifiers_color: Color,
9928 cx: &App,
9929 ) -> AnyElement {
9930 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9931
9932 h_flex()
9933 .px_0p5()
9934 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9935 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9936 .text_size(TextSize::XSmall.rems(cx))
9937 .child(h_flex().children(ui::render_modifiers(
9938 keystroke.modifiers(),
9939 PlatformStyle::platform(),
9940 Some(modifiers_color),
9941 Some(IconSize::XSmall.rems().into()),
9942 true,
9943 )))
9944 .when(is_platform_style_mac, |parent| {
9945 parent.child(keystroke.key().to_string())
9946 })
9947 .when(!is_platform_style_mac, |parent| {
9948 parent.child(
9949 Key::new(util::capitalize(keystroke.key()), Some(Color::Default))
9950 .size(Some(IconSize::XSmall.rems().into())),
9951 )
9952 })
9953 .into_any()
9954 }
9955
9956 fn render_edit_prediction_popover_keystroke(
9957 &self,
9958 keystroke: &gpui::KeybindingKeystroke,
9959 color: Color,
9960 cx: &App,
9961 ) -> AnyElement {
9962 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9963
9964 if keystroke.modifiers().modified() {
9965 h_flex()
9966 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9967 .when(is_platform_style_mac, |parent| parent.gap_1())
9968 .child(h_flex().children(ui::render_modifiers(
9969 keystroke.modifiers(),
9970 PlatformStyle::platform(),
9971 Some(color),
9972 None,
9973 false,
9974 )))
9975 .into_any()
9976 } else {
9977 Key::new(util::capitalize(keystroke.key()), Some(color))
9978 .size(Some(IconSize::XSmall.rems().into()))
9979 .into_any_element()
9980 }
9981 }
9982
9983 fn render_edit_prediction_keybind(
9984 &self,
9985 window: &mut Window,
9986 cx: &mut App,
9987 ) -> Option<AnyElement> {
9988 let keybind_display =
9989 self.edit_prediction_keybind_display(EditPredictionKeybindSurface::Inline, window, cx);
9990 let keystroke = keybind_display.displayed_keystroke.as_ref()?;
9991
9992 let modifiers_color = if *keystroke.modifiers() == window.modifiers() {
9993 Color::Accent
9994 } else {
9995 Color::Muted
9996 };
9997
9998 Some(self.render_edit_prediction_inline_keystroke(keystroke, modifiers_color, cx))
9999 }
10000
10001 fn render_edit_prediction_line_popover(
10002 &self,
10003 label: impl Into<SharedString>,
10004 icon: Option<IconName>,
10005 window: &mut Window,
10006 cx: &mut App,
10007 ) -> Stateful<Div> {
10008 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
10009
10010 let keybind = self.render_edit_prediction_keybind(window, cx);
10011 let has_keybind = keybind.is_some();
10012 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10013
10014 h_flex()
10015 .id("ep-line-popover")
10016 .py_0p5()
10017 .pl_1()
10018 .pr(padding_right)
10019 .gap_1()
10020 .rounded_md()
10021 .border_1()
10022 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10023 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
10024 .shadow_xs()
10025 .when(!has_keybind, |el| {
10026 let status_colors = cx.theme().status();
10027
10028 el.bg(status_colors.error_background)
10029 .border_color(status_colors.error.opacity(0.6))
10030 .pl_2()
10031 .child(Icon::new(icons.error).color(Color::Error))
10032 .cursor_default()
10033 .hoverable_tooltip(move |_window, cx| {
10034 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
10035 })
10036 })
10037 .children(keybind)
10038 .child(
10039 Label::new(label)
10040 .size(LabelSize::Small)
10041 .when(!has_keybind, |el| {
10042 el.color(cx.theme().status().error.into()).strikethrough()
10043 }),
10044 )
10045 .when(!has_keybind, |el| {
10046 el.child(
10047 h_flex().ml_1().child(
10048 Icon::new(IconName::Info)
10049 .size(IconSize::Small)
10050 .color(cx.theme().status().error.into()),
10051 ),
10052 )
10053 })
10054 .when_some(icon, |element, icon| {
10055 element.child(
10056 div()
10057 .mt(px(1.5))
10058 .child(Icon::new(icon).size(IconSize::Small)),
10059 )
10060 })
10061 }
10062
10063 fn render_edit_prediction_jump_outside_popover(
10064 &self,
10065 snapshot: &BufferSnapshot,
10066 window: &mut Window,
10067 cx: &mut App,
10068 ) -> Stateful<Div> {
10069 let keybind = self.render_edit_prediction_keybind(window, cx);
10070 let has_keybind = keybind.is_some();
10071 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10072
10073 let file_name = snapshot
10074 .file()
10075 .map(|file| SharedString::new(file.file_name(cx)))
10076 .unwrap_or(SharedString::new_static("untitled"));
10077
10078 h_flex()
10079 .id("ep-jump-outside-popover")
10080 .py_1()
10081 .px_2()
10082 .gap_1()
10083 .rounded_md()
10084 .border_1()
10085 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10086 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
10087 .shadow_xs()
10088 .when(!has_keybind, |el| {
10089 let status_colors = cx.theme().status();
10090
10091 el.bg(status_colors.error_background)
10092 .border_color(status_colors.error.opacity(0.6))
10093 .pl_2()
10094 .child(Icon::new(icons.error).color(Color::Error))
10095 .cursor_default()
10096 .hoverable_tooltip(move |_window, cx| {
10097 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
10098 })
10099 })
10100 .children(keybind)
10101 .child(
10102 Label::new(file_name)
10103 .size(LabelSize::Small)
10104 .buffer_font(cx)
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 .child(
10119 div()
10120 .mt(px(1.5))
10121 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
10122 )
10123 }
10124
10125 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
10126 let accent_color = cx.theme().colors().text_accent;
10127 let editor_bg_color = cx.theme().colors().editor_background;
10128 editor_bg_color.blend(accent_color.opacity(0.1))
10129 }
10130
10131 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
10132 let accent_color = cx.theme().colors().text_accent;
10133 let editor_bg_color = cx.theme().colors().editor_background;
10134 editor_bg_color.blend(accent_color.opacity(0.6))
10135 }
10136 fn get_prediction_provider_icons(
10137 provider: &Option<RegisteredEditPredictionDelegate>,
10138 cx: &App,
10139 ) -> edit_prediction_types::EditPredictionIconSet {
10140 match provider {
10141 Some(provider) => provider.provider.icons(cx),
10142 None => edit_prediction_types::EditPredictionIconSet::new(IconName::ZedPredict),
10143 }
10144 }
10145
10146 fn render_edit_prediction_cursor_popover(
10147 &self,
10148 min_width: Pixels,
10149 max_width: Pixels,
10150 cursor_point: Point,
10151 style: &EditorStyle,
10152 window: &mut Window,
10153 cx: &mut Context<Editor>,
10154 ) -> Option<AnyElement> {
10155 let provider = self.edit_prediction_provider.as_ref()?;
10156 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10157
10158 let is_refreshing = provider.provider.is_refreshing(cx);
10159
10160 fn pending_completion_container(icon: IconName) -> Div {
10161 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
10162 }
10163
10164 let completion = match &self.active_edit_prediction {
10165 Some(prediction) => {
10166 if !self.has_visible_completions_menu() {
10167 const RADIUS: Pixels = px(6.);
10168 const BORDER_WIDTH: Pixels = px(1.);
10169 let keybind_display = self.edit_prediction_keybind_display(
10170 EditPredictionKeybindSurface::CursorPopoverCompact,
10171 window,
10172 cx,
10173 );
10174
10175 return Some(
10176 h_flex()
10177 .elevation_2(cx)
10178 .border(BORDER_WIDTH)
10179 .border_color(cx.theme().colors().border)
10180 .when(keybind_display.missing_accept_keystroke, |el| {
10181 el.border_color(cx.theme().status().error)
10182 })
10183 .rounded(RADIUS)
10184 .rounded_tl(px(0.))
10185 .overflow_hidden()
10186 .child(div().px_1p5().child(match &prediction.completion {
10187 EditPrediction::MoveWithin { target, snapshot } => {
10188 use text::ToPoint as _;
10189 if target.text_anchor.to_point(snapshot).row > cursor_point.row
10190 {
10191 Icon::new(icons.down)
10192 } else {
10193 Icon::new(icons.up)
10194 }
10195 }
10196 EditPrediction::MoveOutside { .. } => {
10197 // TODO [zeta2] custom icon for external jump?
10198 Icon::new(icons.base)
10199 }
10200 EditPrediction::Edit { .. } => Icon::new(icons.base),
10201 }))
10202 .child(
10203 h_flex()
10204 .gap_1()
10205 .py_1()
10206 .px_2()
10207 .rounded_r(RADIUS - BORDER_WIDTH)
10208 .border_l_1()
10209 .border_color(cx.theme().colors().border)
10210 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10211 .when(keybind_display.show_hold_label, |el| {
10212 el.child(
10213 Label::new("Hold")
10214 .size(LabelSize::Small)
10215 .when(
10216 keybind_display.missing_accept_keystroke,
10217 |el| el.strikethrough(),
10218 )
10219 .line_height_style(LineHeightStyle::UiLabel),
10220 )
10221 })
10222 .id("edit_prediction_cursor_popover_keybind")
10223 .when(keybind_display.missing_accept_keystroke, |el| {
10224 let status_colors = cx.theme().status();
10225
10226 el.bg(status_colors.error_background)
10227 .border_color(status_colors.error.opacity(0.6))
10228 .child(Icon::new(IconName::Info).color(Color::Error))
10229 .cursor_default()
10230 .hoverable_tooltip(move |_window, cx| {
10231 cx.new(|_| MissingEditPredictionKeybindingTooltip)
10232 .into()
10233 })
10234 })
10235 .when_some(
10236 keybind_display.displayed_keystroke.as_ref(),
10237 |el, compact_keystroke| {
10238 el.child(self.render_edit_prediction_popover_keystroke(
10239 compact_keystroke,
10240 Color::Default,
10241 cx,
10242 ))
10243 },
10244 ),
10245 )
10246 .into_any(),
10247 );
10248 }
10249
10250 self.render_edit_prediction_cursor_popover_preview(
10251 prediction,
10252 cursor_point,
10253 style,
10254 cx,
10255 )?
10256 }
10257
10258 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
10259 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
10260 stale_completion,
10261 cursor_point,
10262 style,
10263 cx,
10264 )?,
10265
10266 None => pending_completion_container(icons.base)
10267 .child(Label::new("...").size(LabelSize::Small)),
10268 },
10269
10270 None => pending_completion_container(icons.base)
10271 .child(Label::new("...").size(LabelSize::Small)),
10272 };
10273
10274 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
10275 completion
10276 .with_animation(
10277 "loading-completion",
10278 Animation::new(Duration::from_secs(2))
10279 .repeat()
10280 .with_easing(pulsating_between(0.4, 0.8)),
10281 |label, delta| label.opacity(delta),
10282 )
10283 .into_any_element()
10284 } else {
10285 completion.into_any_element()
10286 };
10287
10288 let has_completion = self.active_edit_prediction.is_some();
10289 let keybind_display = self.edit_prediction_keybind_display(
10290 EditPredictionKeybindSurface::CursorPopoverExpanded,
10291 window,
10292 cx,
10293 );
10294
10295 Some(
10296 h_flex()
10297 .min_w(min_width)
10298 .max_w(max_width)
10299 .flex_1()
10300 .elevation_2(cx)
10301 .border_color(cx.theme().colors().border)
10302 .child(
10303 div()
10304 .flex_1()
10305 .py_1()
10306 .px_2()
10307 .overflow_hidden()
10308 .child(completion),
10309 )
10310 .when_some(
10311 keybind_display.displayed_keystroke.as_ref(),
10312 |el, keystroke| {
10313 let key_color = if !has_completion {
10314 Color::Muted
10315 } else {
10316 Color::Default
10317 };
10318
10319 if keybind_display.action == EditPredictionKeybindAction::Preview {
10320 el.child(
10321 h_flex()
10322 .h_full()
10323 .border_l_1()
10324 .rounded_r_lg()
10325 .border_color(cx.theme().colors().border)
10326 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10327 .gap_1()
10328 .py_1()
10329 .px_2()
10330 .child(self.render_edit_prediction_popover_keystroke(
10331 keystroke, key_color, cx,
10332 ))
10333 .child(Label::new("Preview").into_any_element())
10334 .opacity(if has_completion { 1.0 } else { 0.4 }),
10335 )
10336 } else {
10337 el.child(
10338 h_flex()
10339 .h_full()
10340 .border_l_1()
10341 .rounded_r_lg()
10342 .border_color(cx.theme().colors().border)
10343 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10344 .gap_1()
10345 .py_1()
10346 .px_2()
10347 .child(self.render_edit_prediction_popover_keystroke(
10348 keystroke, key_color, cx,
10349 ))
10350 .opacity(if has_completion { 1.0 } else { 0.4 }),
10351 )
10352 }
10353 },
10354 )
10355 .into_any(),
10356 )
10357 }
10358
10359 fn render_edit_prediction_cursor_popover_preview(
10360 &self,
10361 completion: &EditPredictionState,
10362 cursor_point: Point,
10363 style: &EditorStyle,
10364 cx: &mut Context<Editor>,
10365 ) -> Option<Div> {
10366 use text::ToPoint as _;
10367
10368 fn render_relative_row_jump(
10369 prefix: impl Into<String>,
10370 current_row: u32,
10371 target_row: u32,
10372 ) -> Div {
10373 let (row_diff, arrow) = if target_row < current_row {
10374 (current_row - target_row, IconName::ArrowUp)
10375 } else {
10376 (target_row - current_row, IconName::ArrowDown)
10377 };
10378
10379 h_flex()
10380 .child(
10381 Label::new(format!("{}{}", prefix.into(), row_diff))
10382 .color(Color::Muted)
10383 .size(LabelSize::Small),
10384 )
10385 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
10386 }
10387
10388 let supports_jump = self
10389 .edit_prediction_provider
10390 .as_ref()
10391 .map(|provider| provider.provider.supports_jump_to_edit())
10392 .unwrap_or(true);
10393
10394 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10395
10396 match &completion.completion {
10397 EditPrediction::MoveWithin {
10398 target, snapshot, ..
10399 } => {
10400 if !supports_jump {
10401 return None;
10402 }
10403
10404 Some(
10405 h_flex()
10406 .px_2()
10407 .gap_2()
10408 .flex_1()
10409 .child(
10410 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
10411 Icon::new(icons.down)
10412 } else {
10413 Icon::new(icons.up)
10414 },
10415 )
10416 .child(Label::new("Jump to Edit")),
10417 )
10418 }
10419 EditPrediction::MoveOutside { snapshot, .. } => {
10420 let file_name = snapshot
10421 .file()
10422 .map(|file| file.file_name(cx))
10423 .unwrap_or("untitled");
10424 Some(
10425 h_flex()
10426 .px_2()
10427 .gap_2()
10428 .flex_1()
10429 .child(Icon::new(icons.base))
10430 .child(Label::new(format!("Jump to {file_name}"))),
10431 )
10432 }
10433 EditPrediction::Edit {
10434 edits,
10435 edit_preview,
10436 snapshot,
10437 ..
10438 } => {
10439 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
10440
10441 let (highlighted_edits, has_more_lines) =
10442 if let Some(edit_preview) = edit_preview.as_ref() {
10443 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
10444 .first_line_preview()
10445 } else {
10446 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
10447 };
10448
10449 let styled_text = gpui::StyledText::new(highlighted_edits.text)
10450 .with_default_highlights(&style.text, highlighted_edits.highlights);
10451
10452 let preview = h_flex()
10453 .gap_1()
10454 .min_w_16()
10455 .child(styled_text)
10456 .when(has_more_lines, |parent| parent.child("…"));
10457
10458 let left = if supports_jump && first_edit_row != cursor_point.row {
10459 render_relative_row_jump("", cursor_point.row, first_edit_row)
10460 .into_any_element()
10461 } else {
10462 Icon::new(icons.base).into_any_element()
10463 };
10464
10465 Some(
10466 h_flex()
10467 .h_full()
10468 .flex_1()
10469 .gap_2()
10470 .pr_1()
10471 .overflow_x_hidden()
10472 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
10473 .child(left)
10474 .child(preview),
10475 )
10476 }
10477 }
10478 }
10479
10480 pub fn render_context_menu(
10481 &mut self,
10482 max_height_in_lines: u32,
10483 window: &mut Window,
10484 cx: &mut Context<Editor>,
10485 ) -> Option<AnyElement> {
10486 let menu = self.context_menu.borrow();
10487 let menu = menu.as_ref()?;
10488 if !menu.visible() {
10489 return None;
10490 };
10491 self.style
10492 .as_ref()
10493 .map(|style| menu.render(style, max_height_in_lines, window, cx))
10494 }
10495
10496 fn render_context_menu_aside(
10497 &mut self,
10498 max_size: Size<Pixels>,
10499 window: &mut Window,
10500 cx: &mut Context<Editor>,
10501 ) -> Option<AnyElement> {
10502 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
10503 if menu.visible() {
10504 menu.render_aside(max_size, window, cx)
10505 } else {
10506 None
10507 }
10508 })
10509 }
10510
10511 fn hide_context_menu(
10512 &mut self,
10513 window: &mut Window,
10514 cx: &mut Context<Self>,
10515 ) -> Option<CodeContextMenu> {
10516 cx.notify();
10517 self.completion_tasks.clear();
10518 let context_menu = self.context_menu.borrow_mut().take();
10519 self.stale_edit_prediction_in_menu.take();
10520 self.update_visible_edit_prediction(window, cx);
10521 if let Some(CodeContextMenu::Completions(_)) = &context_menu
10522 && let Some(completion_provider) = &self.completion_provider
10523 {
10524 completion_provider.selection_changed(None, window, cx);
10525 }
10526 context_menu
10527 }
10528
10529 fn show_snippet_choices(
10530 &mut self,
10531 choices: &Vec<String>,
10532 selection: Range<Anchor>,
10533 cx: &mut Context<Self>,
10534 ) {
10535 let Some((_, buffer, _)) = self
10536 .buffer()
10537 .read(cx)
10538 .excerpt_containing(selection.start, cx)
10539 else {
10540 return;
10541 };
10542 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
10543 else {
10544 return;
10545 };
10546 if buffer != end_buffer {
10547 log::error!("expected anchor range to have matching buffer IDs");
10548 return;
10549 }
10550
10551 let id = post_inc(&mut self.next_completion_id);
10552 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
10553 let mut context_menu = self.context_menu.borrow_mut();
10554 let old_menu = context_menu.take();
10555 *context_menu = Some(CodeContextMenu::Completions(
10556 CompletionsMenu::new_snippet_choices(
10557 id,
10558 true,
10559 choices,
10560 selection,
10561 buffer,
10562 old_menu.map(|menu| menu.primary_scroll_handle()),
10563 snippet_sort_order,
10564 ),
10565 ));
10566 }
10567
10568 pub fn insert_snippet(
10569 &mut self,
10570 insertion_ranges: &[Range<MultiBufferOffset>],
10571 snippet: Snippet,
10572 window: &mut Window,
10573 cx: &mut Context<Self>,
10574 ) -> Result<()> {
10575 struct Tabstop<T> {
10576 is_end_tabstop: bool,
10577 ranges: Vec<Range<T>>,
10578 choices: Option<Vec<String>>,
10579 }
10580
10581 let tabstops = self.buffer.update(cx, |buffer, cx| {
10582 let snippet_text: Arc<str> = snippet.text.clone().into();
10583 let edits = insertion_ranges
10584 .iter()
10585 .cloned()
10586 .map(|range| (range, snippet_text.clone()));
10587 let autoindent_mode = AutoindentMode::Block {
10588 original_indent_columns: Vec::new(),
10589 };
10590 buffer.edit(edits, Some(autoindent_mode), cx);
10591
10592 let snapshot = &*buffer.read(cx);
10593 let snippet = &snippet;
10594 snippet
10595 .tabstops
10596 .iter()
10597 .map(|tabstop| {
10598 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10599 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10600 });
10601 let mut tabstop_ranges = tabstop
10602 .ranges
10603 .iter()
10604 .flat_map(|tabstop_range| {
10605 let mut delta = 0_isize;
10606 insertion_ranges.iter().map(move |insertion_range| {
10607 let insertion_start = insertion_range.start + delta;
10608 delta += snippet.text.len() as isize
10609 - (insertion_range.end - insertion_range.start) as isize;
10610
10611 let start =
10612 (insertion_start + tabstop_range.start).min(snapshot.len());
10613 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10614 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10615 })
10616 })
10617 .collect::<Vec<_>>();
10618 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10619
10620 Tabstop {
10621 is_end_tabstop,
10622 ranges: tabstop_ranges,
10623 choices: tabstop.choices.clone(),
10624 }
10625 })
10626 .collect::<Vec<_>>()
10627 });
10628 if let Some(tabstop) = tabstops.first() {
10629 self.change_selections(Default::default(), window, cx, |s| {
10630 // Reverse order so that the first range is the newest created selection.
10631 // Completions will use it and autoscroll will prioritize it.
10632 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10633 });
10634
10635 if let Some(choices) = &tabstop.choices
10636 && let Some(selection) = tabstop.ranges.first()
10637 {
10638 self.show_snippet_choices(choices, selection.clone(), cx)
10639 }
10640
10641 // If we're already at the last tabstop and it's at the end of the snippet,
10642 // we're done, we don't need to keep the state around.
10643 if !tabstop.is_end_tabstop {
10644 let choices = tabstops
10645 .iter()
10646 .map(|tabstop| tabstop.choices.clone())
10647 .collect();
10648
10649 let ranges = tabstops
10650 .into_iter()
10651 .map(|tabstop| tabstop.ranges)
10652 .collect::<Vec<_>>();
10653
10654 self.snippet_stack.push(SnippetState {
10655 active_index: 0,
10656 ranges,
10657 choices,
10658 });
10659 }
10660
10661 // Check whether the just-entered snippet ends with an auto-closable bracket.
10662 if self.autoclose_regions.is_empty() {
10663 let snapshot = self.buffer.read(cx).snapshot(cx);
10664 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10665 let selection_head = selection.head();
10666 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10667 continue;
10668 };
10669
10670 let mut bracket_pair = None;
10671 let max_lookup_length = scope
10672 .brackets()
10673 .map(|(pair, _)| {
10674 pair.start
10675 .as_str()
10676 .chars()
10677 .count()
10678 .max(pair.end.as_str().chars().count())
10679 })
10680 .max();
10681 if let Some(max_lookup_length) = max_lookup_length {
10682 let next_text = snapshot
10683 .chars_at(selection_head)
10684 .take(max_lookup_length)
10685 .collect::<String>();
10686 let prev_text = snapshot
10687 .reversed_chars_at(selection_head)
10688 .take(max_lookup_length)
10689 .collect::<String>();
10690
10691 for (pair, enabled) in scope.brackets() {
10692 if enabled
10693 && pair.close
10694 && prev_text.starts_with(pair.start.as_str())
10695 && next_text.starts_with(pair.end.as_str())
10696 {
10697 bracket_pair = Some(pair.clone());
10698 break;
10699 }
10700 }
10701 }
10702
10703 if let Some(pair) = bracket_pair {
10704 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10705 let autoclose_enabled =
10706 self.use_autoclose && snapshot_settings.use_autoclose;
10707 if autoclose_enabled {
10708 let start = snapshot.anchor_after(selection_head);
10709 let end = snapshot.anchor_after(selection_head);
10710 self.autoclose_regions.push(AutocloseRegion {
10711 selection_id: selection.id,
10712 range: start..end,
10713 pair,
10714 });
10715 }
10716 }
10717 }
10718 }
10719 }
10720 Ok(())
10721 }
10722
10723 pub fn move_to_next_snippet_tabstop(
10724 &mut self,
10725 window: &mut Window,
10726 cx: &mut Context<Self>,
10727 ) -> bool {
10728 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10729 }
10730
10731 pub fn move_to_prev_snippet_tabstop(
10732 &mut self,
10733 window: &mut Window,
10734 cx: &mut Context<Self>,
10735 ) -> bool {
10736 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10737 }
10738
10739 pub fn move_to_snippet_tabstop(
10740 &mut self,
10741 bias: Bias,
10742 window: &mut Window,
10743 cx: &mut Context<Self>,
10744 ) -> bool {
10745 if let Some(mut snippet) = self.snippet_stack.pop() {
10746 match bias {
10747 Bias::Left => {
10748 if snippet.active_index > 0 {
10749 snippet.active_index -= 1;
10750 } else {
10751 self.snippet_stack.push(snippet);
10752 return false;
10753 }
10754 }
10755 Bias::Right => {
10756 if snippet.active_index + 1 < snippet.ranges.len() {
10757 snippet.active_index += 1;
10758 } else {
10759 self.snippet_stack.push(snippet);
10760 return false;
10761 }
10762 }
10763 }
10764 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10765 self.change_selections(Default::default(), window, cx, |s| {
10766 // Reverse order so that the first range is the newest created selection.
10767 // Completions will use it and autoscroll will prioritize it.
10768 s.select_ranges(current_ranges.iter().rev().cloned())
10769 });
10770
10771 if let Some(choices) = &snippet.choices[snippet.active_index]
10772 && let Some(selection) = current_ranges.first()
10773 {
10774 self.show_snippet_choices(choices, selection.clone(), cx);
10775 }
10776
10777 // If snippet state is not at the last tabstop, push it back on the stack
10778 if snippet.active_index + 1 < snippet.ranges.len() {
10779 self.snippet_stack.push(snippet);
10780 }
10781 return true;
10782 }
10783 }
10784
10785 false
10786 }
10787
10788 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10789 self.transact(window, cx, |this, window, cx| {
10790 this.select_all(&SelectAll, window, cx);
10791 this.insert("", window, cx);
10792 });
10793 }
10794
10795 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10796 if self.read_only(cx) {
10797 return;
10798 }
10799 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10800 self.transact(window, cx, |this, window, cx| {
10801 this.select_autoclose_pair(window, cx);
10802
10803 let linked_edits = this.linked_edits_for_selections(Arc::from(""), cx);
10804
10805 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10806 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10807 for selection in &mut selections {
10808 if selection.is_empty() {
10809 let old_head = selection.head();
10810 let mut new_head =
10811 movement::left(&display_map, old_head.to_display_point(&display_map))
10812 .to_point(&display_map);
10813 if let Some((buffer, line_buffer_range)) = display_map
10814 .buffer_snapshot()
10815 .buffer_line_for_row(MultiBufferRow(old_head.row))
10816 {
10817 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10818 let indent_len = match indent_size.kind {
10819 IndentKind::Space => {
10820 buffer.settings_at(line_buffer_range.start, cx).tab_size
10821 }
10822 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10823 };
10824 if old_head.column <= indent_size.len && old_head.column > 0 {
10825 let indent_len = indent_len.get();
10826 new_head = cmp::min(
10827 new_head,
10828 MultiBufferPoint::new(
10829 old_head.row,
10830 ((old_head.column - 1) / indent_len) * indent_len,
10831 ),
10832 );
10833 }
10834 }
10835
10836 selection.set_head(new_head, SelectionGoal::None);
10837 }
10838 }
10839
10840 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10841 this.insert("", window, cx);
10842 linked_edits.apply_with_left_expansion(cx);
10843 this.refresh_edit_prediction(true, false, window, cx);
10844 refresh_linked_ranges(this, window, cx);
10845 });
10846 }
10847
10848 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10849 if self.read_only(cx) {
10850 return;
10851 }
10852 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10853 self.transact(window, cx, |this, window, cx| {
10854 this.change_selections(Default::default(), window, cx, |s| {
10855 s.move_with(&mut |map, selection| {
10856 if selection.is_empty() {
10857 let cursor = movement::right(map, selection.head());
10858 selection.end = cursor;
10859 selection.reversed = true;
10860 selection.goal = SelectionGoal::None;
10861 }
10862 })
10863 });
10864 let linked_edits = this.linked_edits_for_selections(Arc::from(""), cx);
10865 this.insert("", window, cx);
10866 linked_edits.apply(cx);
10867 this.refresh_edit_prediction(true, false, window, cx);
10868 refresh_linked_ranges(this, window, cx);
10869 });
10870 }
10871
10872 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10873 if self.mode.is_single_line() {
10874 cx.propagate();
10875 return;
10876 }
10877
10878 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10879 if self.move_to_prev_snippet_tabstop(window, cx) {
10880 return;
10881 }
10882 self.outdent(&Outdent, window, cx);
10883 }
10884
10885 pub fn next_snippet_tabstop(
10886 &mut self,
10887 _: &NextSnippetTabstop,
10888 window: &mut Window,
10889 cx: &mut Context<Self>,
10890 ) {
10891 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10892 cx.propagate();
10893 return;
10894 }
10895
10896 if self.move_to_next_snippet_tabstop(window, cx) {
10897 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10898 return;
10899 }
10900 cx.propagate();
10901 }
10902
10903 pub fn previous_snippet_tabstop(
10904 &mut self,
10905 _: &PreviousSnippetTabstop,
10906 window: &mut Window,
10907 cx: &mut Context<Self>,
10908 ) {
10909 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10910 cx.propagate();
10911 return;
10912 }
10913
10914 if self.move_to_prev_snippet_tabstop(window, cx) {
10915 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10916 return;
10917 }
10918 cx.propagate();
10919 }
10920
10921 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10922 if self.mode.is_single_line() {
10923 cx.propagate();
10924 return;
10925 }
10926
10927 if self.move_to_next_snippet_tabstop(window, cx) {
10928 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10929 return;
10930 }
10931 if self.read_only(cx) {
10932 return;
10933 }
10934 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10935 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10936 let buffer = self.buffer.read(cx);
10937 let snapshot = buffer.snapshot(cx);
10938 let rows_iter = selections.iter().map(|s| s.head().row);
10939 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10940
10941 let has_some_cursor_in_whitespace = selections
10942 .iter()
10943 .filter(|selection| selection.is_empty())
10944 .any(|selection| {
10945 let cursor = selection.head();
10946 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10947 cursor.column < current_indent.len
10948 });
10949
10950 let mut edits = Vec::new();
10951 let mut prev_edited_row = 0;
10952 let mut row_delta = 0;
10953 for selection in &mut selections {
10954 if selection.start.row != prev_edited_row {
10955 row_delta = 0;
10956 }
10957 prev_edited_row = selection.end.row;
10958
10959 // If cursor is after a list prefix, make selection non-empty to trigger line indent
10960 if selection.is_empty() {
10961 let cursor = selection.head();
10962 let settings = buffer.language_settings_at(cursor, cx);
10963 if settings.indent_list_on_tab {
10964 if let Some(language) = snapshot.language_scope_at(Point::new(cursor.row, 0)) {
10965 if is_list_prefix_row(MultiBufferRow(cursor.row), &snapshot, &language) {
10966 row_delta = Self::indent_selection(
10967 buffer, &snapshot, selection, &mut edits, row_delta, cx,
10968 );
10969 continue;
10970 }
10971 }
10972 }
10973 }
10974
10975 // If the selection is non-empty, then increase the indentation of the selected lines.
10976 if !selection.is_empty() {
10977 row_delta =
10978 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10979 continue;
10980 }
10981
10982 let cursor = selection.head();
10983 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10984 if let Some(suggested_indent) =
10985 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10986 {
10987 // Don't do anything if already at suggested indent
10988 // and there is any other cursor which is not
10989 if has_some_cursor_in_whitespace
10990 && cursor.column == current_indent.len
10991 && current_indent.len == suggested_indent.len
10992 {
10993 continue;
10994 }
10995
10996 // Adjust line and move cursor to suggested indent
10997 // if cursor is not at suggested indent
10998 if cursor.column < suggested_indent.len
10999 && cursor.column <= current_indent.len
11000 && current_indent.len <= suggested_indent.len
11001 {
11002 selection.start = Point::new(cursor.row, suggested_indent.len);
11003 selection.end = selection.start;
11004 if row_delta == 0 {
11005 edits.extend(Buffer::edit_for_indent_size_adjustment(
11006 cursor.row,
11007 current_indent,
11008 suggested_indent,
11009 ));
11010 row_delta = suggested_indent.len - current_indent.len;
11011 }
11012 continue;
11013 }
11014
11015 // If current indent is more than suggested indent
11016 // only move cursor to current indent and skip indent
11017 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
11018 selection.start = Point::new(cursor.row, current_indent.len);
11019 selection.end = selection.start;
11020 continue;
11021 }
11022 }
11023
11024 // Otherwise, insert a hard or soft tab.
11025 let settings = buffer.language_settings_at(cursor, cx);
11026 let tab_size = if settings.hard_tabs {
11027 IndentSize::tab()
11028 } else {
11029 let tab_size = settings.tab_size.get();
11030 let indent_remainder = snapshot
11031 .text_for_range(Point::new(cursor.row, 0)..cursor)
11032 .flat_map(str::chars)
11033 .fold(row_delta % tab_size, |counter: u32, c| {
11034 if c == '\t' {
11035 0
11036 } else {
11037 (counter + 1) % tab_size
11038 }
11039 });
11040
11041 let chars_to_next_tab_stop = tab_size - indent_remainder;
11042 IndentSize::spaces(chars_to_next_tab_stop)
11043 };
11044 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
11045 selection.end = selection.start;
11046 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
11047 row_delta += tab_size.len;
11048 }
11049
11050 self.transact(window, cx, |this, window, cx| {
11051 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
11052 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11053 this.refresh_edit_prediction(true, false, window, cx);
11054 });
11055 }
11056
11057 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
11058 if self.read_only(cx) {
11059 return;
11060 }
11061 if self.mode.is_single_line() {
11062 cx.propagate();
11063 return;
11064 }
11065
11066 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11067 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
11068 let mut prev_edited_row = 0;
11069 let mut row_delta = 0;
11070 let mut edits = Vec::new();
11071 let buffer = self.buffer.read(cx);
11072 let snapshot = buffer.snapshot(cx);
11073 for selection in &mut selections {
11074 if selection.start.row != prev_edited_row {
11075 row_delta = 0;
11076 }
11077 prev_edited_row = selection.end.row;
11078
11079 row_delta =
11080 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
11081 }
11082
11083 self.transact(window, cx, |this, window, cx| {
11084 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
11085 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11086 });
11087 }
11088
11089 fn indent_selection(
11090 buffer: &MultiBuffer,
11091 snapshot: &MultiBufferSnapshot,
11092 selection: &mut Selection<Point>,
11093 edits: &mut Vec<(Range<Point>, String)>,
11094 delta_for_start_row: u32,
11095 cx: &App,
11096 ) -> u32 {
11097 let settings = buffer.language_settings_at(selection.start, cx);
11098 let tab_size = settings.tab_size.get();
11099 let indent_kind = if settings.hard_tabs {
11100 IndentKind::Tab
11101 } else {
11102 IndentKind::Space
11103 };
11104 let mut start_row = selection.start.row;
11105 let mut end_row = selection.end.row + 1;
11106
11107 // If a selection ends at the beginning of a line, don't indent
11108 // that last line.
11109 if selection.end.column == 0 && selection.end.row > selection.start.row {
11110 end_row -= 1;
11111 }
11112
11113 // Avoid re-indenting a row that has already been indented by a
11114 // previous selection, but still update this selection's column
11115 // to reflect that indentation.
11116 if delta_for_start_row > 0 {
11117 start_row += 1;
11118 selection.start.column += delta_for_start_row;
11119 if selection.end.row == selection.start.row {
11120 selection.end.column += delta_for_start_row;
11121 }
11122 }
11123
11124 let mut delta_for_end_row = 0;
11125 let has_multiple_rows = start_row + 1 != end_row;
11126 for row in start_row..end_row {
11127 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
11128 let indent_delta = match (current_indent.kind, indent_kind) {
11129 (IndentKind::Space, IndentKind::Space) => {
11130 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
11131 IndentSize::spaces(columns_to_next_tab_stop)
11132 }
11133 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
11134 (_, IndentKind::Tab) => IndentSize::tab(),
11135 };
11136
11137 let start = if has_multiple_rows || current_indent.len < selection.start.column {
11138 0
11139 } else {
11140 selection.start.column
11141 };
11142 let row_start = Point::new(row, start);
11143 edits.push((
11144 row_start..row_start,
11145 indent_delta.chars().collect::<String>(),
11146 ));
11147
11148 // Update this selection's endpoints to reflect the indentation.
11149 if row == selection.start.row {
11150 selection.start.column += indent_delta.len;
11151 }
11152 if row == selection.end.row {
11153 selection.end.column += indent_delta.len;
11154 delta_for_end_row = indent_delta.len;
11155 }
11156 }
11157
11158 if selection.start.row == selection.end.row {
11159 delta_for_start_row + delta_for_end_row
11160 } else {
11161 delta_for_end_row
11162 }
11163 }
11164
11165 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
11166 if self.read_only(cx) {
11167 return;
11168 }
11169 if self.mode.is_single_line() {
11170 cx.propagate();
11171 return;
11172 }
11173
11174 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11175 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11176 let selections = self.selections.all::<Point>(&display_map);
11177 let mut deletion_ranges = Vec::new();
11178 let mut last_outdent = None;
11179 {
11180 let buffer = self.buffer.read(cx);
11181 let snapshot = buffer.snapshot(cx);
11182 for selection in &selections {
11183 let settings = buffer.language_settings_at(selection.start, cx);
11184 let tab_size = settings.tab_size.get();
11185 let mut rows = selection.spanned_rows(false, &display_map);
11186
11187 // Avoid re-outdenting a row that has already been outdented by a
11188 // previous selection.
11189 if let Some(last_row) = last_outdent
11190 && last_row == rows.start
11191 {
11192 rows.start = rows.start.next_row();
11193 }
11194 let has_multiple_rows = rows.len() > 1;
11195 for row in rows.iter_rows() {
11196 let indent_size = snapshot.indent_size_for_line(row);
11197 if indent_size.len > 0 {
11198 let deletion_len = match indent_size.kind {
11199 IndentKind::Space => {
11200 let columns_to_prev_tab_stop = indent_size.len % tab_size;
11201 if columns_to_prev_tab_stop == 0 {
11202 tab_size
11203 } else {
11204 columns_to_prev_tab_stop
11205 }
11206 }
11207 IndentKind::Tab => 1,
11208 };
11209 let start = if has_multiple_rows
11210 || deletion_len > selection.start.column
11211 || indent_size.len < selection.start.column
11212 {
11213 0
11214 } else {
11215 selection.start.column - deletion_len
11216 };
11217 deletion_ranges.push(
11218 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
11219 );
11220 last_outdent = Some(row);
11221 }
11222 }
11223 }
11224 }
11225
11226 self.transact(window, cx, |this, window, cx| {
11227 this.buffer.update(cx, |buffer, cx| {
11228 let empty_str: Arc<str> = Arc::default();
11229 buffer.edit(
11230 deletion_ranges
11231 .into_iter()
11232 .map(|range| (range, empty_str.clone())),
11233 None,
11234 cx,
11235 );
11236 });
11237 let selections = this
11238 .selections
11239 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11240 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11241 });
11242 }
11243
11244 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
11245 if self.read_only(cx) {
11246 return;
11247 }
11248 if self.mode.is_single_line() {
11249 cx.propagate();
11250 return;
11251 }
11252
11253 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11254 let selections = self
11255 .selections
11256 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11257 .into_iter()
11258 .map(|s| s.range());
11259
11260 self.transact(window, cx, |this, window, cx| {
11261 this.buffer.update(cx, |buffer, cx| {
11262 buffer.autoindent_ranges(selections, cx);
11263 });
11264 let selections = this
11265 .selections
11266 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11267 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11268 });
11269 }
11270
11271 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
11272 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11273 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11274 let selections = self.selections.all::<Point>(&display_map);
11275
11276 let mut new_cursors = Vec::new();
11277 let mut edit_ranges = Vec::new();
11278 let mut selections = selections.iter().peekable();
11279 while let Some(selection) = selections.next() {
11280 let mut rows = selection.spanned_rows(false, &display_map);
11281
11282 // Accumulate contiguous regions of rows that we want to delete.
11283 while let Some(next_selection) = selections.peek() {
11284 let next_rows = next_selection.spanned_rows(false, &display_map);
11285 if next_rows.start <= rows.end {
11286 rows.end = next_rows.end;
11287 selections.next().unwrap();
11288 } else {
11289 break;
11290 }
11291 }
11292
11293 let buffer = display_map.buffer_snapshot();
11294 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
11295 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
11296 // If there's a line after the range, delete the \n from the end of the row range
11297 (
11298 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
11299 rows.end,
11300 )
11301 } else {
11302 // If there isn't a line after the range, delete the \n from the line before the
11303 // start of the row range
11304 edit_start = edit_start.saturating_sub_usize(1);
11305 (buffer.len(), rows.start.previous_row())
11306 };
11307
11308 let text_layout_details = self.text_layout_details(window, cx);
11309 let x = display_map.x_for_display_point(
11310 selection.head().to_display_point(&display_map),
11311 &text_layout_details,
11312 );
11313 let row = Point::new(target_row.0, 0)
11314 .to_display_point(&display_map)
11315 .row();
11316 let column = display_map.display_column_for_x(row, x, &text_layout_details);
11317
11318 new_cursors.push((
11319 selection.id,
11320 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
11321 SelectionGoal::None,
11322 ));
11323 edit_ranges.push(edit_start..edit_end);
11324 }
11325
11326 self.transact(window, cx, |this, window, cx| {
11327 let buffer = this.buffer.update(cx, |buffer, cx| {
11328 let empty_str: Arc<str> = Arc::default();
11329 buffer.edit(
11330 edit_ranges
11331 .into_iter()
11332 .map(|range| (range, empty_str.clone())),
11333 None,
11334 cx,
11335 );
11336 buffer.snapshot(cx)
11337 });
11338 let new_selections = new_cursors
11339 .into_iter()
11340 .map(|(id, cursor, goal)| {
11341 let cursor = cursor.to_point(&buffer);
11342 Selection {
11343 id,
11344 start: cursor,
11345 end: cursor,
11346 reversed: false,
11347 goal,
11348 }
11349 })
11350 .collect();
11351
11352 this.change_selections(Default::default(), window, cx, |s| {
11353 s.select(new_selections);
11354 });
11355 });
11356 }
11357
11358 pub fn join_lines_impl(
11359 &mut self,
11360 insert_whitespace: bool,
11361 window: &mut Window,
11362 cx: &mut Context<Self>,
11363 ) {
11364 if self.read_only(cx) {
11365 return;
11366 }
11367 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
11368 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
11369 let start = MultiBufferRow(selection.start.row);
11370 // Treat single line selections as if they include the next line. Otherwise this action
11371 // would do nothing for single line selections individual cursors.
11372 let end = if selection.start.row == selection.end.row {
11373 MultiBufferRow(selection.start.row + 1)
11374 } else if selection.end.column == 0 {
11375 // If the selection ends at the start of a line, it's logically at the end of the
11376 // previous line (plus its newline).
11377 // Don't include the end line unless there's only one line selected.
11378 if selection.start.row + 1 == selection.end.row {
11379 MultiBufferRow(selection.end.row)
11380 } else {
11381 MultiBufferRow(selection.end.row - 1)
11382 }
11383 } else {
11384 MultiBufferRow(selection.end.row)
11385 };
11386
11387 if let Some(last_row_range) = row_ranges.last_mut()
11388 && start <= last_row_range.end
11389 {
11390 last_row_range.end = end;
11391 continue;
11392 }
11393 row_ranges.push(start..end);
11394 }
11395
11396 let snapshot = self.buffer.read(cx).snapshot(cx);
11397 let mut cursor_positions = Vec::new();
11398 for row_range in &row_ranges {
11399 let anchor = snapshot.anchor_before(Point::new(
11400 row_range.end.previous_row().0,
11401 snapshot.line_len(row_range.end.previous_row()),
11402 ));
11403 cursor_positions.push(anchor..anchor);
11404 }
11405
11406 self.transact(window, cx, |this, window, cx| {
11407 for row_range in row_ranges.into_iter().rev() {
11408 for row in row_range.iter_rows().rev() {
11409 let end_of_line = Point::new(row.0, snapshot.line_len(row));
11410 let next_line_row = row.next_row();
11411 let indent = snapshot.indent_size_for_line(next_line_row);
11412 let mut join_start_column = indent.len;
11413
11414 if let Some(language_scope) =
11415 snapshot.language_scope_at(Point::new(next_line_row.0, indent.len))
11416 {
11417 let line_end =
11418 Point::new(next_line_row.0, snapshot.line_len(next_line_row));
11419 let line_text_after_indent = snapshot
11420 .text_for_range(Point::new(next_line_row.0, indent.len)..line_end)
11421 .collect::<String>();
11422
11423 if !line_text_after_indent.is_empty() {
11424 let block_prefix = language_scope
11425 .block_comment()
11426 .map(|c| c.prefix.as_ref())
11427 .filter(|p| !p.is_empty());
11428 let doc_prefix = language_scope
11429 .documentation_comment()
11430 .map(|c| c.prefix.as_ref())
11431 .filter(|p| !p.is_empty());
11432 let all_prefixes = language_scope
11433 .line_comment_prefixes()
11434 .iter()
11435 .map(|p| p.as_ref())
11436 .chain(block_prefix)
11437 .chain(doc_prefix)
11438 .chain(language_scope.unordered_list().iter().map(|p| p.as_ref()));
11439
11440 let mut longest_prefix_len = None;
11441 for prefix in all_prefixes {
11442 let trimmed = prefix.trim_end();
11443 if line_text_after_indent.starts_with(trimmed) {
11444 let candidate_len =
11445 if line_text_after_indent.starts_with(prefix) {
11446 prefix.len()
11447 } else {
11448 trimmed.len()
11449 };
11450 if longest_prefix_len.map_or(true, |len| candidate_len > len) {
11451 longest_prefix_len = Some(candidate_len);
11452 }
11453 }
11454 }
11455
11456 if let Some(prefix_len) = longest_prefix_len {
11457 join_start_column =
11458 join_start_column.saturating_add(prefix_len as u32);
11459 }
11460 }
11461 }
11462
11463 let start_of_next_line = Point::new(next_line_row.0, join_start_column);
11464
11465 let replace = if snapshot.line_len(next_line_row) > join_start_column
11466 && insert_whitespace
11467 {
11468 " "
11469 } else {
11470 ""
11471 };
11472
11473 this.buffer.update(cx, |buffer, cx| {
11474 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
11475 });
11476 }
11477 }
11478
11479 this.change_selections(Default::default(), window, cx, |s| {
11480 s.select_anchor_ranges(cursor_positions)
11481 });
11482 });
11483 }
11484
11485 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
11486 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11487 self.join_lines_impl(true, window, cx);
11488 }
11489
11490 pub fn sort_lines_case_sensitive(
11491 &mut self,
11492 _: &SortLinesCaseSensitive,
11493 window: &mut Window,
11494 cx: &mut Context<Self>,
11495 ) {
11496 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
11497 }
11498
11499 pub fn sort_lines_by_length(
11500 &mut self,
11501 _: &SortLinesByLength,
11502 window: &mut Window,
11503 cx: &mut Context<Self>,
11504 ) {
11505 self.manipulate_immutable_lines(window, cx, |lines| {
11506 lines.sort_by_key(|&line| line.chars().count())
11507 })
11508 }
11509
11510 pub fn sort_lines_case_insensitive(
11511 &mut self,
11512 _: &SortLinesCaseInsensitive,
11513 window: &mut Window,
11514 cx: &mut Context<Self>,
11515 ) {
11516 self.manipulate_immutable_lines(window, cx, |lines| {
11517 lines.sort_by_key(|line| line.to_lowercase())
11518 })
11519 }
11520
11521 pub fn unique_lines_case_insensitive(
11522 &mut self,
11523 _: &UniqueLinesCaseInsensitive,
11524 window: &mut Window,
11525 cx: &mut Context<Self>,
11526 ) {
11527 self.manipulate_immutable_lines(window, cx, |lines| {
11528 let mut seen = HashSet::default();
11529 lines.retain(|line| seen.insert(line.to_lowercase()));
11530 })
11531 }
11532
11533 pub fn unique_lines_case_sensitive(
11534 &mut self,
11535 _: &UniqueLinesCaseSensitive,
11536 window: &mut Window,
11537 cx: &mut Context<Self>,
11538 ) {
11539 self.manipulate_immutable_lines(window, cx, |lines| {
11540 let mut seen = HashSet::default();
11541 lines.retain(|line| seen.insert(*line));
11542 })
11543 }
11544
11545 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
11546 let snapshot = self.buffer.read(cx).snapshot(cx);
11547 for selection in self.selections.disjoint_anchors_arc().iter() {
11548 if snapshot
11549 .language_at(selection.start)
11550 .and_then(|lang| lang.config().wrap_characters.as_ref())
11551 .is_some()
11552 {
11553 return true;
11554 }
11555 }
11556 false
11557 }
11558
11559 fn wrap_selections_in_tag(
11560 &mut self,
11561 _: &WrapSelectionsInTag,
11562 window: &mut Window,
11563 cx: &mut Context<Self>,
11564 ) {
11565 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11566
11567 let snapshot = self.buffer.read(cx).snapshot(cx);
11568
11569 let mut edits = Vec::new();
11570 let mut boundaries = Vec::new();
11571
11572 for selection in self
11573 .selections
11574 .all_adjusted(&self.display_snapshot(cx))
11575 .iter()
11576 {
11577 let Some(wrap_config) = snapshot
11578 .language_at(selection.start)
11579 .and_then(|lang| lang.config().wrap_characters.clone())
11580 else {
11581 continue;
11582 };
11583
11584 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
11585 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
11586
11587 let start_before = snapshot.anchor_before(selection.start);
11588 let end_after = snapshot.anchor_after(selection.end);
11589
11590 edits.push((start_before..start_before, open_tag));
11591 edits.push((end_after..end_after, close_tag));
11592
11593 boundaries.push((
11594 start_before,
11595 end_after,
11596 wrap_config.start_prefix.len(),
11597 wrap_config.end_suffix.len(),
11598 ));
11599 }
11600
11601 if edits.is_empty() {
11602 return;
11603 }
11604
11605 self.transact(window, cx, |this, window, cx| {
11606 let buffer = this.buffer.update(cx, |buffer, cx| {
11607 buffer.edit(edits, None, cx);
11608 buffer.snapshot(cx)
11609 });
11610
11611 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
11612 for (start_before, end_after, start_prefix_len, end_suffix_len) in
11613 boundaries.into_iter()
11614 {
11615 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
11616 let close_offset = end_after
11617 .to_offset(&buffer)
11618 .saturating_sub_usize(end_suffix_len);
11619 new_selections.push(open_offset..open_offset);
11620 new_selections.push(close_offset..close_offset);
11621 }
11622
11623 this.change_selections(Default::default(), window, cx, |s| {
11624 s.select_ranges(new_selections);
11625 });
11626
11627 this.request_autoscroll(Autoscroll::fit(), cx);
11628 });
11629 }
11630
11631 pub fn toggle_read_only(
11632 &mut self,
11633 _: &workspace::ToggleReadOnlyFile,
11634 _: &mut Window,
11635 cx: &mut Context<Self>,
11636 ) {
11637 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
11638 buffer.update(cx, |buffer, cx| {
11639 buffer.set_capability(
11640 match buffer.capability() {
11641 Capability::ReadWrite => Capability::Read,
11642 Capability::Read => Capability::ReadWrite,
11643 Capability::ReadOnly => Capability::ReadOnly,
11644 },
11645 cx,
11646 );
11647 })
11648 }
11649 }
11650
11651 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11652 let Some(project) = self.project.clone() else {
11653 return;
11654 };
11655 let task = self.reload(project, window, cx);
11656 self.detach_and_notify_err(task, window, cx);
11657 }
11658
11659 pub fn restore_file(
11660 &mut self,
11661 _: &::git::RestoreFile,
11662 window: &mut Window,
11663 cx: &mut Context<Self>,
11664 ) {
11665 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11666 let mut buffer_ids = HashSet::default();
11667 let snapshot = self.buffer().read(cx).snapshot(cx);
11668 for selection in self
11669 .selections
11670 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11671 {
11672 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11673 }
11674
11675 let buffer = self.buffer().read(cx);
11676 let ranges = buffer_ids
11677 .into_iter()
11678 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11679 .collect::<Vec<_>>();
11680
11681 self.restore_hunks_in_ranges(ranges, window, cx);
11682 }
11683
11684 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11685 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11686 let selections = self
11687 .selections
11688 .all(&self.display_snapshot(cx))
11689 .into_iter()
11690 .map(|s| s.range())
11691 .collect();
11692 self.restore_hunks_in_ranges(selections, window, cx);
11693 }
11694
11695 /// Restores the diff hunks in the editor's selections and moves the cursor
11696 /// to the next diff hunk. Wraps around to the beginning of the buffer if
11697 /// not all diff hunks are expanded.
11698 pub fn restore_and_next(
11699 &mut self,
11700 _: &::git::RestoreAndNext,
11701 window: &mut Window,
11702 cx: &mut Context<Self>,
11703 ) {
11704 let selections = self
11705 .selections
11706 .all(&self.display_snapshot(cx))
11707 .into_iter()
11708 .map(|selection| selection.range())
11709 .collect();
11710
11711 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11712 self.restore_hunks_in_ranges(selections, window, cx);
11713
11714 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
11715 let wrap_around = !all_diff_hunks_expanded;
11716 let snapshot = self.snapshot(window, cx);
11717 let position = self
11718 .selections
11719 .newest::<Point>(&snapshot.display_snapshot)
11720 .head();
11721
11722 self.go_to_hunk_before_or_after_position(
11723 &snapshot,
11724 position,
11725 Direction::Next,
11726 wrap_around,
11727 window,
11728 cx,
11729 );
11730 }
11731
11732 pub fn restore_hunks_in_ranges(
11733 &mut self,
11734 ranges: Vec<Range<Point>>,
11735 window: &mut Window,
11736 cx: &mut Context<Editor>,
11737 ) {
11738 if self.delegate_stage_and_restore {
11739 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11740 if !hunks.is_empty() {
11741 cx.emit(EditorEvent::RestoreRequested { hunks });
11742 }
11743 return;
11744 }
11745 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11746 self.transact(window, cx, |editor, window, cx| {
11747 editor.restore_diff_hunks(hunks, cx);
11748 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
11749 selections.refresh()
11750 });
11751 });
11752 }
11753
11754 pub(crate) fn restore_diff_hunks(&self, hunks: Vec<MultiBufferDiffHunk>, cx: &mut App) {
11755 let mut revert_changes = HashMap::default();
11756 let chunk_by = hunks.into_iter().chunk_by(|hunk| hunk.buffer_id);
11757 for (buffer_id, hunks) in &chunk_by {
11758 let hunks = hunks.collect::<Vec<_>>();
11759 for hunk in &hunks {
11760 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11761 }
11762 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11763 }
11764 if !revert_changes.is_empty() {
11765 self.buffer().update(cx, |multi_buffer, cx| {
11766 for (buffer_id, changes) in revert_changes {
11767 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
11768 buffer.update(cx, |buffer, cx| {
11769 buffer.edit(
11770 changes
11771 .into_iter()
11772 .map(|(range, text)| (range, text.to_string())),
11773 None,
11774 cx,
11775 );
11776 });
11777 }
11778 }
11779 });
11780 }
11781 }
11782
11783 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11784 if let Some(status) = self
11785 .addons
11786 .iter()
11787 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11788 {
11789 return Some(status);
11790 }
11791 self.project
11792 .as_ref()?
11793 .read(cx)
11794 .status_for_buffer_id(buffer_id, cx)
11795 }
11796
11797 pub fn open_active_item_in_terminal(
11798 &mut self,
11799 _: &OpenInTerminal,
11800 window: &mut Window,
11801 cx: &mut Context<Self>,
11802 ) {
11803 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11804 let project_path = buffer.read(cx).project_path(cx)?;
11805 let project = self.project()?.read(cx);
11806 let entry = project.entry_for_path(&project_path, cx)?;
11807 let parent = match &entry.canonical_path {
11808 Some(canonical_path) => canonical_path.to_path_buf(),
11809 None => project.absolute_path(&project_path, cx)?,
11810 }
11811 .parent()?
11812 .to_path_buf();
11813 Some(parent)
11814 }) {
11815 window.dispatch_action(
11816 OpenTerminal {
11817 working_directory,
11818 local: false,
11819 }
11820 .boxed_clone(),
11821 cx,
11822 );
11823 }
11824 }
11825
11826 fn set_breakpoint_context_menu(
11827 &mut self,
11828 display_row: DisplayRow,
11829 position: Option<Anchor>,
11830 clicked_point: gpui::Point<Pixels>,
11831 window: &mut Window,
11832 cx: &mut Context<Self>,
11833 ) {
11834 let source = self
11835 .buffer
11836 .read(cx)
11837 .snapshot(cx)
11838 .anchor_before(Point::new(display_row.0, 0u32));
11839
11840 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11841
11842 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11843 self,
11844 source,
11845 clicked_point,
11846 context_menu,
11847 window,
11848 cx,
11849 );
11850 }
11851
11852 fn add_edit_breakpoint_block(
11853 &mut self,
11854 anchor: Anchor,
11855 breakpoint: &Breakpoint,
11856 edit_action: BreakpointPromptEditAction,
11857 window: &mut Window,
11858 cx: &mut Context<Self>,
11859 ) {
11860 let weak_editor = cx.weak_entity();
11861 let bp_prompt = cx.new(|cx| {
11862 BreakpointPromptEditor::new(
11863 weak_editor,
11864 anchor,
11865 breakpoint.clone(),
11866 edit_action,
11867 window,
11868 cx,
11869 )
11870 });
11871
11872 let height = bp_prompt.update(cx, |this, cx| {
11873 this.prompt
11874 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11875 });
11876 let cloned_prompt = bp_prompt.clone();
11877 let blocks = vec![BlockProperties {
11878 style: BlockStyle::Sticky,
11879 placement: BlockPlacement::Above(anchor),
11880 height: Some(height),
11881 render: Arc::new(move |cx| {
11882 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11883 cloned_prompt.clone().into_any_element()
11884 }),
11885 priority: 0,
11886 }];
11887
11888 let focus_handle = bp_prompt.focus_handle(cx);
11889 window.focus(&focus_handle, cx);
11890
11891 let block_ids = self.insert_blocks(blocks, None, cx);
11892 bp_prompt.update(cx, |prompt, _| {
11893 prompt.add_block_ids(block_ids);
11894 });
11895 }
11896
11897 pub(crate) fn breakpoint_at_row(
11898 &self,
11899 row: u32,
11900 window: &mut Window,
11901 cx: &mut Context<Self>,
11902 ) -> Option<(Anchor, Breakpoint)> {
11903 let snapshot = self.snapshot(window, cx);
11904 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11905
11906 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11907 }
11908
11909 pub(crate) fn breakpoint_at_anchor(
11910 &self,
11911 breakpoint_position: Anchor,
11912 snapshot: &EditorSnapshot,
11913 cx: &mut Context<Self>,
11914 ) -> Option<(Anchor, Breakpoint)> {
11915 let buffer = self
11916 .buffer
11917 .read(cx)
11918 .buffer_for_anchor(breakpoint_position, cx)?;
11919
11920 let enclosing_excerpt = breakpoint_position.excerpt_id;
11921 let buffer_snapshot = buffer.read(cx).snapshot();
11922
11923 let row = buffer_snapshot
11924 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11925 .row;
11926
11927 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11928 let anchor_end = snapshot
11929 .buffer_snapshot()
11930 .anchor_after(Point::new(row, line_len));
11931
11932 self.breakpoint_store
11933 .as_ref()?
11934 .read_with(cx, |breakpoint_store, cx| {
11935 breakpoint_store
11936 .breakpoints(
11937 &buffer,
11938 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11939 &buffer_snapshot,
11940 cx,
11941 )
11942 .next()
11943 .and_then(|(bp, _)| {
11944 let breakpoint_row = buffer_snapshot
11945 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11946 .row;
11947
11948 if breakpoint_row == row {
11949 snapshot
11950 .buffer_snapshot()
11951 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11952 .map(|position| (position, bp.bp.clone()))
11953 } else {
11954 None
11955 }
11956 })
11957 })
11958 }
11959
11960 pub fn edit_log_breakpoint(
11961 &mut self,
11962 _: &EditLogBreakpoint,
11963 window: &mut Window,
11964 cx: &mut Context<Self>,
11965 ) {
11966 if self.breakpoint_store.is_none() {
11967 return;
11968 }
11969
11970 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11971 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11972 message: None,
11973 state: BreakpointState::Enabled,
11974 condition: None,
11975 hit_condition: None,
11976 });
11977
11978 self.add_edit_breakpoint_block(
11979 anchor,
11980 &breakpoint,
11981 BreakpointPromptEditAction::Log,
11982 window,
11983 cx,
11984 );
11985 }
11986 }
11987
11988 fn breakpoints_at_cursors(
11989 &self,
11990 window: &mut Window,
11991 cx: &mut Context<Self>,
11992 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11993 let snapshot = self.snapshot(window, cx);
11994 let cursors = self
11995 .selections
11996 .disjoint_anchors_arc()
11997 .iter()
11998 .map(|selection| {
11999 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
12000
12001 let breakpoint_position = self
12002 .breakpoint_at_row(cursor_position.row, window, cx)
12003 .map(|bp| bp.0)
12004 .unwrap_or_else(|| {
12005 snapshot
12006 .display_snapshot
12007 .buffer_snapshot()
12008 .anchor_after(Point::new(cursor_position.row, 0))
12009 });
12010
12011 let breakpoint = self
12012 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
12013 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
12014
12015 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
12016 })
12017 // 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.
12018 .collect::<HashMap<Anchor, _>>();
12019
12020 cursors.into_iter().collect()
12021 }
12022
12023 pub fn enable_breakpoint(
12024 &mut self,
12025 _: &crate::actions::EnableBreakpoint,
12026 window: &mut Window,
12027 cx: &mut Context<Self>,
12028 ) {
12029 if self.breakpoint_store.is_none() {
12030 return;
12031 }
12032
12033 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
12034 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
12035 continue;
12036 };
12037 self.edit_breakpoint_at_anchor(
12038 anchor,
12039 breakpoint,
12040 BreakpointEditAction::InvertState,
12041 cx,
12042 );
12043 }
12044 }
12045
12046 pub fn align_selections(
12047 &mut self,
12048 _: &crate::actions::AlignSelections,
12049 window: &mut Window,
12050 cx: &mut Context<Self>,
12051 ) {
12052 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12053
12054 let display_snapshot = self.display_snapshot(cx);
12055
12056 struct CursorData {
12057 anchor: Anchor,
12058 point: Point,
12059 }
12060 let cursor_data: Vec<CursorData> = self
12061 .selections
12062 .disjoint_anchors()
12063 .iter()
12064 .map(|selection| {
12065 let anchor = if selection.reversed {
12066 selection.head()
12067 } else {
12068 selection.tail()
12069 };
12070 CursorData {
12071 anchor: anchor,
12072 point: anchor.to_point(&display_snapshot.buffer_snapshot()),
12073 }
12074 })
12075 .collect();
12076
12077 let rows_anchors_count: Vec<usize> = cursor_data
12078 .iter()
12079 .map(|cursor| cursor.point.row)
12080 .chunk_by(|&row| row)
12081 .into_iter()
12082 .map(|(_, group)| group.count())
12083 .collect();
12084 let max_columns = rows_anchors_count.iter().max().copied().unwrap_or(0);
12085 let mut rows_column_offset = vec![0; rows_anchors_count.len()];
12086 let mut edits = Vec::new();
12087
12088 for column_idx in 0..max_columns {
12089 let mut cursor_index = 0;
12090
12091 // Calculate target_column => position that the selections will go
12092 let mut target_column = 0;
12093 for (row_idx, cursor_count) in rows_anchors_count.iter().enumerate() {
12094 // Skip rows that don't have this column
12095 if column_idx >= *cursor_count {
12096 cursor_index += cursor_count;
12097 continue;
12098 }
12099
12100 let point = &cursor_data[cursor_index + column_idx].point;
12101 let adjusted_column = point.column + rows_column_offset[row_idx];
12102 if adjusted_column > target_column {
12103 target_column = adjusted_column;
12104 }
12105 cursor_index += cursor_count;
12106 }
12107
12108 // Collect edits for this column
12109 cursor_index = 0;
12110 for (row_idx, cursor_count) in rows_anchors_count.iter().enumerate() {
12111 // Skip rows that don't have this column
12112 if column_idx >= *cursor_count {
12113 cursor_index += *cursor_count;
12114 continue;
12115 }
12116
12117 let point = &cursor_data[cursor_index + column_idx].point;
12118 let spaces_needed = target_column - point.column - rows_column_offset[row_idx];
12119 if spaces_needed > 0 {
12120 let anchor = cursor_data[cursor_index + column_idx]
12121 .anchor
12122 .bias_left(&display_snapshot);
12123 edits.push((anchor..anchor, " ".repeat(spaces_needed as usize)));
12124 }
12125 rows_column_offset[row_idx] += spaces_needed;
12126
12127 cursor_index += *cursor_count;
12128 }
12129 }
12130
12131 if !edits.is_empty() {
12132 self.transact(window, cx, |editor, _window, cx| {
12133 editor.edit(edits, cx);
12134 });
12135 }
12136 }
12137
12138 pub fn disable_breakpoint(
12139 &mut self,
12140 _: &crate::actions::DisableBreakpoint,
12141 window: &mut Window,
12142 cx: &mut Context<Self>,
12143 ) {
12144 if self.breakpoint_store.is_none() {
12145 return;
12146 }
12147
12148 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
12149 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
12150 continue;
12151 };
12152 self.edit_breakpoint_at_anchor(
12153 anchor,
12154 breakpoint,
12155 BreakpointEditAction::InvertState,
12156 cx,
12157 );
12158 }
12159 }
12160
12161 pub fn toggle_breakpoint(
12162 &mut self,
12163 _: &crate::actions::ToggleBreakpoint,
12164 window: &mut Window,
12165 cx: &mut Context<Self>,
12166 ) {
12167 if self.breakpoint_store.is_none() {
12168 return;
12169 }
12170
12171 let snapshot = self.snapshot(window, cx);
12172 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
12173 if self.gutter_breakpoint_indicator.0.is_some() {
12174 let display_row = anchor
12175 .to_point(snapshot.buffer_snapshot())
12176 .to_display_point(&snapshot.display_snapshot)
12177 .row();
12178 self.update_breakpoint_collision_on_toggle(
12179 display_row,
12180 &BreakpointEditAction::Toggle,
12181 );
12182 }
12183
12184 if let Some(breakpoint) = breakpoint {
12185 self.edit_breakpoint_at_anchor(
12186 anchor,
12187 breakpoint,
12188 BreakpointEditAction::Toggle,
12189 cx,
12190 );
12191 } else {
12192 self.edit_breakpoint_at_anchor(
12193 anchor,
12194 Breakpoint::new_standard(),
12195 BreakpointEditAction::Toggle,
12196 cx,
12197 );
12198 }
12199 }
12200 }
12201
12202 fn update_breakpoint_collision_on_toggle(
12203 &mut self,
12204 display_row: DisplayRow,
12205 edit_action: &BreakpointEditAction,
12206 ) {
12207 if let Some(ref mut breakpoint_indicator) = self.gutter_breakpoint_indicator.0 {
12208 if breakpoint_indicator.display_row == display_row
12209 && matches!(edit_action, BreakpointEditAction::Toggle)
12210 {
12211 breakpoint_indicator.collides_with_existing_breakpoint =
12212 !breakpoint_indicator.collides_with_existing_breakpoint;
12213 }
12214 }
12215 }
12216
12217 pub fn edit_breakpoint_at_anchor(
12218 &mut self,
12219 breakpoint_position: Anchor,
12220 breakpoint: Breakpoint,
12221 edit_action: BreakpointEditAction,
12222 cx: &mut Context<Self>,
12223 ) {
12224 let Some(breakpoint_store) = &self.breakpoint_store else {
12225 return;
12226 };
12227
12228 let Some(buffer) = self
12229 .buffer
12230 .read(cx)
12231 .buffer_for_anchor(breakpoint_position, cx)
12232 else {
12233 return;
12234 };
12235
12236 breakpoint_store.update(cx, |breakpoint_store, cx| {
12237 breakpoint_store.toggle_breakpoint(
12238 buffer,
12239 BreakpointWithPosition {
12240 position: breakpoint_position.text_anchor,
12241 bp: breakpoint,
12242 },
12243 edit_action,
12244 cx,
12245 );
12246 });
12247
12248 cx.notify();
12249 }
12250
12251 #[cfg(any(test, feature = "test-support"))]
12252 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
12253 self.breakpoint_store.clone()
12254 }
12255
12256 pub fn prepare_restore_change(
12257 &self,
12258 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
12259 hunk: &MultiBufferDiffHunk,
12260 cx: &mut App,
12261 ) -> Option<()> {
12262 if hunk.is_created_file() {
12263 return None;
12264 }
12265 let buffer = self.buffer.read(cx);
12266 let diff = buffer.diff_for(hunk.buffer_id)?;
12267 let buffer = buffer.buffer(hunk.buffer_id)?;
12268 let buffer = buffer.read(cx);
12269 let original_text = diff
12270 .read(cx)
12271 .base_text(cx)
12272 .as_rope()
12273 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
12274 let buffer_snapshot = buffer.snapshot();
12275 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
12276 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
12277 probe
12278 .0
12279 .start
12280 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
12281 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
12282 }) {
12283 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
12284 Some(())
12285 } else {
12286 None
12287 }
12288 }
12289
12290 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
12291 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
12292 }
12293
12294 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
12295 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
12296 }
12297
12298 pub fn rotate_selections_forward(
12299 &mut self,
12300 _: &RotateSelectionsForward,
12301 window: &mut Window,
12302 cx: &mut Context<Self>,
12303 ) {
12304 self.rotate_selections(window, cx, false)
12305 }
12306
12307 pub fn rotate_selections_backward(
12308 &mut self,
12309 _: &RotateSelectionsBackward,
12310 window: &mut Window,
12311 cx: &mut Context<Self>,
12312 ) {
12313 self.rotate_selections(window, cx, true)
12314 }
12315
12316 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
12317 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12318 let display_snapshot = self.display_snapshot(cx);
12319 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
12320
12321 if selections.len() < 2 {
12322 return;
12323 }
12324
12325 let (edits, new_selections) = {
12326 let buffer = self.buffer.read(cx).read(cx);
12327 let has_selections = selections.iter().any(|s| !s.is_empty());
12328 if has_selections {
12329 let mut selected_texts: Vec<String> = selections
12330 .iter()
12331 .map(|selection| {
12332 buffer
12333 .text_for_range(selection.start..selection.end)
12334 .collect()
12335 })
12336 .collect();
12337
12338 if reverse {
12339 selected_texts.rotate_left(1);
12340 } else {
12341 selected_texts.rotate_right(1);
12342 }
12343
12344 let mut offset_delta: i64 = 0;
12345 let mut new_selections = Vec::new();
12346 let edits: Vec<_> = selections
12347 .iter()
12348 .zip(selected_texts.iter())
12349 .map(|(selection, new_text)| {
12350 let old_len = (selection.end.0 - selection.start.0) as i64;
12351 let new_len = new_text.len() as i64;
12352 let adjusted_start =
12353 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
12354 let adjusted_end =
12355 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
12356
12357 new_selections.push(Selection {
12358 id: selection.id,
12359 start: adjusted_start,
12360 end: adjusted_end,
12361 reversed: selection.reversed,
12362 goal: selection.goal,
12363 });
12364
12365 offset_delta += new_len - old_len;
12366 (selection.start..selection.end, new_text.clone())
12367 })
12368 .collect();
12369 (edits, new_selections)
12370 } else {
12371 let mut all_rows: Vec<u32> = selections
12372 .iter()
12373 .map(|selection| buffer.offset_to_point(selection.start).row)
12374 .collect();
12375 all_rows.sort_unstable();
12376 all_rows.dedup();
12377
12378 if all_rows.len() < 2 {
12379 return;
12380 }
12381
12382 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
12383 .iter()
12384 .map(|&row| {
12385 let start = Point::new(row, 0);
12386 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12387 buffer.point_to_offset(start)..buffer.point_to_offset(end)
12388 })
12389 .collect();
12390
12391 let mut line_texts: Vec<String> = line_ranges
12392 .iter()
12393 .map(|range| buffer.text_for_range(range.clone()).collect())
12394 .collect();
12395
12396 if reverse {
12397 line_texts.rotate_left(1);
12398 } else {
12399 line_texts.rotate_right(1);
12400 }
12401
12402 let edits = line_ranges
12403 .iter()
12404 .zip(line_texts.iter())
12405 .map(|(range, new_text)| (range.clone(), new_text.clone()))
12406 .collect();
12407
12408 let num_rows = all_rows.len();
12409 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
12410 .iter()
12411 .enumerate()
12412 .map(|(i, &row)| (row, i))
12413 .collect();
12414
12415 // Compute new line start offsets after rotation (handles CRLF)
12416 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
12417 let first_line_start = line_ranges[0].start.0;
12418 let mut new_line_starts: Vec<usize> = vec![first_line_start];
12419 for text in line_texts.iter().take(num_rows - 1) {
12420 let prev_start = *new_line_starts.last().unwrap();
12421 new_line_starts.push(prev_start + text.len() + newline_len);
12422 }
12423
12424 let new_selections = selections
12425 .iter()
12426 .map(|selection| {
12427 let point = buffer.offset_to_point(selection.start);
12428 let old_index = row_to_index[&point.row];
12429 let new_index = if reverse {
12430 (old_index + num_rows - 1) % num_rows
12431 } else {
12432 (old_index + 1) % num_rows
12433 };
12434 let new_offset =
12435 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
12436 Selection {
12437 id: selection.id,
12438 start: new_offset,
12439 end: new_offset,
12440 reversed: selection.reversed,
12441 goal: selection.goal,
12442 }
12443 })
12444 .collect();
12445
12446 (edits, new_selections)
12447 }
12448 };
12449
12450 self.transact(window, cx, |this, window, cx| {
12451 this.buffer.update(cx, |buffer, cx| {
12452 buffer.edit(edits, None, cx);
12453 });
12454 this.change_selections(Default::default(), window, cx, |s| {
12455 s.select(new_selections);
12456 });
12457 });
12458 }
12459
12460 fn manipulate_lines<M>(
12461 &mut self,
12462 window: &mut Window,
12463 cx: &mut Context<Self>,
12464 mut manipulate: M,
12465 ) where
12466 M: FnMut(&str) -> LineManipulationResult,
12467 {
12468 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12469
12470 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12471 let buffer = self.buffer.read(cx).snapshot(cx);
12472
12473 let mut edits = Vec::new();
12474
12475 let selections = self.selections.all::<Point>(&display_map);
12476 let mut selections = selections.iter().peekable();
12477 let mut contiguous_row_selections = Vec::new();
12478 let mut new_selections = Vec::new();
12479 let mut added_lines = 0;
12480 let mut removed_lines = 0;
12481
12482 while let Some(selection) = selections.next() {
12483 let (start_row, end_row) = consume_contiguous_rows(
12484 &mut contiguous_row_selections,
12485 selection,
12486 &display_map,
12487 &mut selections,
12488 );
12489
12490 let start_point = Point::new(start_row.0, 0);
12491 let end_point = Point::new(
12492 end_row.previous_row().0,
12493 buffer.line_len(end_row.previous_row()),
12494 );
12495 let text = buffer
12496 .text_for_range(start_point..end_point)
12497 .collect::<String>();
12498
12499 let LineManipulationResult {
12500 new_text,
12501 line_count_before,
12502 line_count_after,
12503 } = manipulate(&text);
12504
12505 edits.push((start_point..end_point, new_text));
12506
12507 // Selections must change based on added and removed line count
12508 let start_row =
12509 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
12510 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
12511 new_selections.push(Selection {
12512 id: selection.id,
12513 start: start_row,
12514 end: end_row,
12515 goal: SelectionGoal::None,
12516 reversed: selection.reversed,
12517 });
12518
12519 if line_count_after > line_count_before {
12520 added_lines += line_count_after - line_count_before;
12521 } else if line_count_before > line_count_after {
12522 removed_lines += line_count_before - line_count_after;
12523 }
12524 }
12525
12526 self.transact(window, cx, |this, window, cx| {
12527 let buffer = this.buffer.update(cx, |buffer, cx| {
12528 buffer.edit(edits, None, cx);
12529 buffer.snapshot(cx)
12530 });
12531
12532 // Recalculate offsets on newly edited buffer
12533 let new_selections = new_selections
12534 .iter()
12535 .map(|s| {
12536 let start_point = Point::new(s.start.0, 0);
12537 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
12538 Selection {
12539 id: s.id,
12540 start: buffer.point_to_offset(start_point),
12541 end: buffer.point_to_offset(end_point),
12542 goal: s.goal,
12543 reversed: s.reversed,
12544 }
12545 })
12546 .collect();
12547
12548 this.change_selections(Default::default(), window, cx, |s| {
12549 s.select(new_selections);
12550 });
12551
12552 this.request_autoscroll(Autoscroll::fit(), cx);
12553 });
12554 }
12555
12556 fn manipulate_immutable_lines<Fn>(
12557 &mut self,
12558 window: &mut Window,
12559 cx: &mut Context<Self>,
12560 mut callback: Fn,
12561 ) where
12562 Fn: FnMut(&mut Vec<&str>),
12563 {
12564 self.manipulate_lines(window, cx, |text| {
12565 let mut lines: Vec<&str> = text.split('\n').collect();
12566 let line_count_before = lines.len();
12567
12568 callback(&mut lines);
12569
12570 LineManipulationResult {
12571 new_text: lines.join("\n"),
12572 line_count_before,
12573 line_count_after: lines.len(),
12574 }
12575 });
12576 }
12577
12578 fn manipulate_mutable_lines<Fn>(
12579 &mut self,
12580 window: &mut Window,
12581 cx: &mut Context<Self>,
12582 mut callback: Fn,
12583 ) where
12584 Fn: FnMut(&mut Vec<Cow<'_, str>>),
12585 {
12586 self.manipulate_lines(window, cx, |text| {
12587 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
12588 let line_count_before = lines.len();
12589
12590 callback(&mut lines);
12591
12592 LineManipulationResult {
12593 new_text: lines.join("\n"),
12594 line_count_before,
12595 line_count_after: lines.len(),
12596 }
12597 });
12598 }
12599
12600 pub fn convert_indentation_to_spaces(
12601 &mut self,
12602 _: &ConvertIndentationToSpaces,
12603 window: &mut Window,
12604 cx: &mut Context<Self>,
12605 ) {
12606 let settings = self.buffer.read(cx).language_settings(cx);
12607 let tab_size = settings.tab_size.get() as usize;
12608
12609 self.manipulate_mutable_lines(window, cx, |lines| {
12610 // Allocates a reasonably sized scratch buffer once for the whole loop
12611 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12612 // Avoids recomputing spaces that could be inserted many times
12613 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12614 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12615 .collect();
12616
12617 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12618 let mut chars = line.as_ref().chars();
12619 let mut col = 0;
12620 let mut changed = false;
12621
12622 for ch in chars.by_ref() {
12623 match ch {
12624 ' ' => {
12625 reindented_line.push(' ');
12626 col += 1;
12627 }
12628 '\t' => {
12629 // \t are converted to spaces depending on the current column
12630 let spaces_len = tab_size - (col % tab_size);
12631 reindented_line.extend(&space_cache[spaces_len - 1]);
12632 col += spaces_len;
12633 changed = true;
12634 }
12635 _ => {
12636 // If we dont append before break, the character is consumed
12637 reindented_line.push(ch);
12638 break;
12639 }
12640 }
12641 }
12642
12643 if !changed {
12644 reindented_line.clear();
12645 continue;
12646 }
12647 // Append the rest of the line and replace old reference with new one
12648 reindented_line.extend(chars);
12649 *line = Cow::Owned(reindented_line.clone());
12650 reindented_line.clear();
12651 }
12652 });
12653 }
12654
12655 pub fn convert_indentation_to_tabs(
12656 &mut self,
12657 _: &ConvertIndentationToTabs,
12658 window: &mut Window,
12659 cx: &mut Context<Self>,
12660 ) {
12661 let settings = self.buffer.read(cx).language_settings(cx);
12662 let tab_size = settings.tab_size.get() as usize;
12663
12664 self.manipulate_mutable_lines(window, cx, |lines| {
12665 // Allocates a reasonably sized buffer once for the whole loop
12666 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12667 // Avoids recomputing spaces that could be inserted many times
12668 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12669 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12670 .collect();
12671
12672 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12673 let mut chars = line.chars();
12674 let mut spaces_count = 0;
12675 let mut first_non_indent_char = None;
12676 let mut changed = false;
12677
12678 for ch in chars.by_ref() {
12679 match ch {
12680 ' ' => {
12681 // Keep track of spaces. Append \t when we reach tab_size
12682 spaces_count += 1;
12683 changed = true;
12684 if spaces_count == tab_size {
12685 reindented_line.push('\t');
12686 spaces_count = 0;
12687 }
12688 }
12689 '\t' => {
12690 reindented_line.push('\t');
12691 spaces_count = 0;
12692 }
12693 _ => {
12694 // Dont append it yet, we might have remaining spaces
12695 first_non_indent_char = Some(ch);
12696 break;
12697 }
12698 }
12699 }
12700
12701 if !changed {
12702 reindented_line.clear();
12703 continue;
12704 }
12705 // Remaining spaces that didn't make a full tab stop
12706 if spaces_count > 0 {
12707 reindented_line.extend(&space_cache[spaces_count - 1]);
12708 }
12709 // If we consume an extra character that was not indentation, add it back
12710 if let Some(extra_char) = first_non_indent_char {
12711 reindented_line.push(extra_char);
12712 }
12713 // Append the rest of the line and replace old reference with new one
12714 reindented_line.extend(chars);
12715 *line = Cow::Owned(reindented_line.clone());
12716 reindented_line.clear();
12717 }
12718 });
12719 }
12720
12721 pub fn convert_to_upper_case(
12722 &mut self,
12723 _: &ConvertToUpperCase,
12724 window: &mut Window,
12725 cx: &mut Context<Self>,
12726 ) {
12727 self.manipulate_text(window, cx, |text| text.to_uppercase())
12728 }
12729
12730 pub fn convert_to_lower_case(
12731 &mut self,
12732 _: &ConvertToLowerCase,
12733 window: &mut Window,
12734 cx: &mut Context<Self>,
12735 ) {
12736 self.manipulate_text(window, cx, |text| text.to_lowercase())
12737 }
12738
12739 pub fn convert_to_title_case(
12740 &mut self,
12741 _: &ConvertToTitleCase,
12742 window: &mut Window,
12743 cx: &mut Context<Self>,
12744 ) {
12745 self.manipulate_text(window, cx, |text| {
12746 Self::convert_text_case(text, Case::Title)
12747 })
12748 }
12749
12750 pub fn convert_to_snake_case(
12751 &mut self,
12752 _: &ConvertToSnakeCase,
12753 window: &mut Window,
12754 cx: &mut Context<Self>,
12755 ) {
12756 self.manipulate_text(window, cx, |text| {
12757 Self::convert_text_case(text, Case::Snake)
12758 })
12759 }
12760
12761 pub fn convert_to_kebab_case(
12762 &mut self,
12763 _: &ConvertToKebabCase,
12764 window: &mut Window,
12765 cx: &mut Context<Self>,
12766 ) {
12767 self.manipulate_text(window, cx, |text| {
12768 Self::convert_text_case(text, Case::Kebab)
12769 })
12770 }
12771
12772 pub fn convert_to_upper_camel_case(
12773 &mut self,
12774 _: &ConvertToUpperCamelCase,
12775 window: &mut Window,
12776 cx: &mut Context<Self>,
12777 ) {
12778 self.manipulate_text(window, cx, |text| {
12779 Self::convert_text_case(text, Case::UpperCamel)
12780 })
12781 }
12782
12783 pub fn convert_to_lower_camel_case(
12784 &mut self,
12785 _: &ConvertToLowerCamelCase,
12786 window: &mut Window,
12787 cx: &mut Context<Self>,
12788 ) {
12789 self.manipulate_text(window, cx, |text| {
12790 Self::convert_text_case(text, Case::Camel)
12791 })
12792 }
12793
12794 pub fn convert_to_opposite_case(
12795 &mut self,
12796 _: &ConvertToOppositeCase,
12797 window: &mut Window,
12798 cx: &mut Context<Self>,
12799 ) {
12800 self.manipulate_text(window, cx, |text| {
12801 text.chars()
12802 .fold(String::with_capacity(text.len()), |mut t, c| {
12803 if c.is_uppercase() {
12804 t.extend(c.to_lowercase());
12805 } else {
12806 t.extend(c.to_uppercase());
12807 }
12808 t
12809 })
12810 })
12811 }
12812
12813 pub fn convert_to_sentence_case(
12814 &mut self,
12815 _: &ConvertToSentenceCase,
12816 window: &mut Window,
12817 cx: &mut Context<Self>,
12818 ) {
12819 self.manipulate_text(window, cx, |text| {
12820 Self::convert_text_case(text, Case::Sentence)
12821 })
12822 }
12823
12824 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
12825 self.manipulate_text(window, cx, |text| {
12826 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
12827 if has_upper_case_characters {
12828 text.to_lowercase()
12829 } else {
12830 text.to_uppercase()
12831 }
12832 })
12833 }
12834
12835 pub fn convert_to_rot13(
12836 &mut self,
12837 _: &ConvertToRot13,
12838 window: &mut Window,
12839 cx: &mut Context<Self>,
12840 ) {
12841 self.manipulate_text(window, cx, |text| {
12842 text.chars()
12843 .map(|c| match c {
12844 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
12845 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
12846 _ => c,
12847 })
12848 .collect()
12849 })
12850 }
12851
12852 fn convert_text_case(text: &str, case: Case) -> String {
12853 text.lines()
12854 .map(|line| {
12855 let trimmed_start = line.trim_start();
12856 let leading = &line[..line.len() - trimmed_start.len()];
12857 let trimmed = trimmed_start.trim_end();
12858 let trailing = &trimmed_start[trimmed.len()..];
12859 format!("{}{}{}", leading, trimmed.to_case(case), trailing)
12860 })
12861 .join("\n")
12862 }
12863
12864 pub fn convert_to_rot47(
12865 &mut self,
12866 _: &ConvertToRot47,
12867 window: &mut Window,
12868 cx: &mut Context<Self>,
12869 ) {
12870 self.manipulate_text(window, cx, |text| {
12871 text.chars()
12872 .map(|c| {
12873 let code_point = c as u32;
12874 if code_point >= 33 && code_point <= 126 {
12875 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
12876 }
12877 c
12878 })
12879 .collect()
12880 })
12881 }
12882
12883 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
12884 where
12885 Fn: FnMut(&str) -> String,
12886 {
12887 let buffer = self.buffer.read(cx).snapshot(cx);
12888
12889 let mut new_selections = Vec::new();
12890 let mut edits = Vec::new();
12891 let mut selection_adjustment = 0isize;
12892
12893 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
12894 let selection_is_empty = selection.is_empty();
12895
12896 let (start, end) = if selection_is_empty {
12897 let (word_range, _) = buffer.surrounding_word(selection.start, None);
12898 (word_range.start, word_range.end)
12899 } else {
12900 (
12901 buffer.point_to_offset(selection.start),
12902 buffer.point_to_offset(selection.end),
12903 )
12904 };
12905
12906 let text = buffer.text_for_range(start..end).collect::<String>();
12907 let old_length = text.len() as isize;
12908 let text = callback(&text);
12909
12910 new_selections.push(Selection {
12911 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
12912 end: MultiBufferOffset(
12913 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
12914 ),
12915 goal: SelectionGoal::None,
12916 id: selection.id,
12917 reversed: selection.reversed,
12918 });
12919
12920 selection_adjustment += old_length - text.len() as isize;
12921
12922 edits.push((start..end, text));
12923 }
12924
12925 self.transact(window, cx, |this, window, cx| {
12926 this.buffer.update(cx, |buffer, cx| {
12927 buffer.edit(edits, None, cx);
12928 });
12929
12930 this.change_selections(Default::default(), window, cx, |s| {
12931 s.select(new_selections);
12932 });
12933
12934 this.request_autoscroll(Autoscroll::fit(), cx);
12935 });
12936 }
12937
12938 pub fn move_selection_on_drop(
12939 &mut self,
12940 selection: &Selection<Anchor>,
12941 target: DisplayPoint,
12942 is_cut: bool,
12943 window: &mut Window,
12944 cx: &mut Context<Self>,
12945 ) {
12946 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12947 let buffer = display_map.buffer_snapshot();
12948 let mut edits = Vec::new();
12949 let insert_point = display_map
12950 .clip_point(target, Bias::Left)
12951 .to_point(&display_map);
12952 let text = buffer
12953 .text_for_range(selection.start..selection.end)
12954 .collect::<String>();
12955 if is_cut {
12956 edits.push(((selection.start..selection.end), String::new()));
12957 }
12958 let insert_anchor = buffer.anchor_before(insert_point);
12959 edits.push(((insert_anchor..insert_anchor), text));
12960 let last_edit_start = insert_anchor.bias_left(buffer);
12961 let last_edit_end = insert_anchor.bias_right(buffer);
12962 self.transact(window, cx, |this, window, cx| {
12963 this.buffer.update(cx, |buffer, cx| {
12964 buffer.edit(edits, None, cx);
12965 });
12966 this.change_selections(Default::default(), window, cx, |s| {
12967 s.select_anchor_ranges([last_edit_start..last_edit_end]);
12968 });
12969 });
12970 }
12971
12972 pub fn clear_selection_drag_state(&mut self) {
12973 self.selection_drag_state = SelectionDragState::None;
12974 }
12975
12976 pub fn duplicate(
12977 &mut self,
12978 upwards: bool,
12979 whole_lines: bool,
12980 window: &mut Window,
12981 cx: &mut Context<Self>,
12982 ) {
12983 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12984
12985 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12986 let buffer = display_map.buffer_snapshot();
12987 let selections = self.selections.all::<Point>(&display_map);
12988
12989 let mut edits = Vec::new();
12990 let mut selections_iter = selections.iter().peekable();
12991 while let Some(selection) = selections_iter.next() {
12992 let mut rows = selection.spanned_rows(false, &display_map);
12993 // duplicate line-wise
12994 if whole_lines || selection.start == selection.end {
12995 // Avoid duplicating the same lines twice.
12996 while let Some(next_selection) = selections_iter.peek() {
12997 let next_rows = next_selection.spanned_rows(false, &display_map);
12998 if next_rows.start < rows.end {
12999 rows.end = next_rows.end;
13000 selections_iter.next().unwrap();
13001 } else {
13002 break;
13003 }
13004 }
13005
13006 // Copy the text from the selected row region and splice it either at the start
13007 // or end of the region.
13008 let start = Point::new(rows.start.0, 0);
13009 let end = Point::new(
13010 rows.end.previous_row().0,
13011 buffer.line_len(rows.end.previous_row()),
13012 );
13013
13014 let mut text = buffer.text_for_range(start..end).collect::<String>();
13015
13016 let insert_location = if upwards {
13017 // When duplicating upward, we need to insert before the current line.
13018 // If we're on the last line and it doesn't end with a newline,
13019 // we need to add a newline before the duplicated content.
13020 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
13021 && buffer.max_point().column > 0
13022 && !text.ends_with('\n');
13023
13024 if needs_leading_newline {
13025 text.insert(0, '\n');
13026 end
13027 } else {
13028 text.push('\n');
13029 Point::new(rows.start.0, 0)
13030 }
13031 } else {
13032 text.push('\n');
13033 start
13034 };
13035 edits.push((insert_location..insert_location, text));
13036 } else {
13037 // duplicate character-wise
13038 let start = selection.start;
13039 let end = selection.end;
13040 let text = buffer.text_for_range(start..end).collect::<String>();
13041 edits.push((selection.end..selection.end, text));
13042 }
13043 }
13044
13045 self.transact(window, cx, |this, window, cx| {
13046 this.buffer.update(cx, |buffer, cx| {
13047 buffer.edit(edits, None, cx);
13048 });
13049
13050 // When duplicating upward with whole lines, move the cursor to the duplicated line
13051 if upwards && whole_lines {
13052 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
13053
13054 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13055 let mut new_ranges = Vec::new();
13056 let selections = s.all::<Point>(&display_map);
13057 let mut selections_iter = selections.iter().peekable();
13058
13059 while let Some(first_selection) = selections_iter.next() {
13060 // Group contiguous selections together to find the total row span
13061 let mut group_selections = vec![first_selection];
13062 let mut rows = first_selection.spanned_rows(false, &display_map);
13063
13064 while let Some(next_selection) = selections_iter.peek() {
13065 let next_rows = next_selection.spanned_rows(false, &display_map);
13066 if next_rows.start < rows.end {
13067 rows.end = next_rows.end;
13068 group_selections.push(selections_iter.next().unwrap());
13069 } else {
13070 break;
13071 }
13072 }
13073
13074 let row_count = rows.end.0 - rows.start.0;
13075
13076 // Move all selections in this group up by the total number of duplicated rows
13077 for selection in group_selections {
13078 let new_start = Point::new(
13079 selection.start.row.saturating_sub(row_count),
13080 selection.start.column,
13081 );
13082
13083 let new_end = Point::new(
13084 selection.end.row.saturating_sub(row_count),
13085 selection.end.column,
13086 );
13087
13088 new_ranges.push(new_start..new_end);
13089 }
13090 }
13091
13092 s.select_ranges(new_ranges);
13093 });
13094 }
13095
13096 this.request_autoscroll(Autoscroll::fit(), cx);
13097 });
13098 }
13099
13100 pub fn duplicate_line_up(
13101 &mut self,
13102 _: &DuplicateLineUp,
13103 window: &mut Window,
13104 cx: &mut Context<Self>,
13105 ) {
13106 self.duplicate(true, true, window, cx);
13107 }
13108
13109 pub fn duplicate_line_down(
13110 &mut self,
13111 _: &DuplicateLineDown,
13112 window: &mut Window,
13113 cx: &mut Context<Self>,
13114 ) {
13115 self.duplicate(false, true, window, cx);
13116 }
13117
13118 pub fn duplicate_selection(
13119 &mut self,
13120 _: &DuplicateSelection,
13121 window: &mut Window,
13122 cx: &mut Context<Self>,
13123 ) {
13124 self.duplicate(false, false, window, cx);
13125 }
13126
13127 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
13128 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13129 if self.mode.is_single_line() {
13130 cx.propagate();
13131 return;
13132 }
13133
13134 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13135 let buffer = self.buffer.read(cx).snapshot(cx);
13136
13137 let mut edits = Vec::new();
13138 let mut unfold_ranges = Vec::new();
13139 let mut refold_creases = Vec::new();
13140
13141 let selections = self.selections.all::<Point>(&display_map);
13142 let mut selections = selections.iter().peekable();
13143 let mut contiguous_row_selections = Vec::new();
13144 let mut new_selections = Vec::new();
13145
13146 while let Some(selection) = selections.next() {
13147 // Find all the selections that span a contiguous row range
13148 let (start_row, end_row) = consume_contiguous_rows(
13149 &mut contiguous_row_selections,
13150 selection,
13151 &display_map,
13152 &mut selections,
13153 );
13154
13155 // Move the text spanned by the row range to be before the line preceding the row range
13156 if start_row.0 > 0 {
13157 let range_to_move = Point::new(
13158 start_row.previous_row().0,
13159 buffer.line_len(start_row.previous_row()),
13160 )
13161 ..Point::new(
13162 end_row.previous_row().0,
13163 buffer.line_len(end_row.previous_row()),
13164 );
13165 let insertion_point = display_map
13166 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
13167 .0;
13168
13169 // Don't move lines across excerpts
13170 if buffer
13171 .excerpt_containing(insertion_point..range_to_move.end)
13172 .is_some()
13173 {
13174 let text = buffer
13175 .text_for_range(range_to_move.clone())
13176 .flat_map(|s| s.chars())
13177 .skip(1)
13178 .chain(['\n'])
13179 .collect::<String>();
13180
13181 edits.push((
13182 buffer.anchor_after(range_to_move.start)
13183 ..buffer.anchor_before(range_to_move.end),
13184 String::new(),
13185 ));
13186 let insertion_anchor = buffer.anchor_after(insertion_point);
13187 edits.push((insertion_anchor..insertion_anchor, text));
13188
13189 let row_delta = range_to_move.start.row - insertion_point.row + 1;
13190
13191 // Move selections up
13192 new_selections.extend(contiguous_row_selections.drain(..).map(
13193 |mut selection| {
13194 selection.start.row -= row_delta;
13195 selection.end.row -= row_delta;
13196 selection
13197 },
13198 ));
13199
13200 // Move folds up
13201 unfold_ranges.push(range_to_move.clone());
13202 for fold in display_map.folds_in_range(
13203 buffer.anchor_before(range_to_move.start)
13204 ..buffer.anchor_after(range_to_move.end),
13205 ) {
13206 let mut start = fold.range.start.to_point(&buffer);
13207 let mut end = fold.range.end.to_point(&buffer);
13208 start.row -= row_delta;
13209 end.row -= row_delta;
13210 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
13211 }
13212 }
13213 }
13214
13215 // If we didn't move line(s), preserve the existing selections
13216 new_selections.append(&mut contiguous_row_selections);
13217 }
13218
13219 self.transact(window, cx, |this, window, cx| {
13220 this.unfold_ranges(&unfold_ranges, true, true, cx);
13221 this.buffer.update(cx, |buffer, cx| {
13222 for (range, text) in edits {
13223 buffer.edit([(range, text)], None, cx);
13224 }
13225 });
13226 this.fold_creases(refold_creases, true, window, cx);
13227 this.change_selections(Default::default(), window, cx, |s| {
13228 s.select(new_selections);
13229 })
13230 });
13231 }
13232
13233 pub fn move_line_down(
13234 &mut self,
13235 _: &MoveLineDown,
13236 window: &mut Window,
13237 cx: &mut Context<Self>,
13238 ) {
13239 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13240 if self.mode.is_single_line() {
13241 cx.propagate();
13242 return;
13243 }
13244
13245 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13246 let buffer = self.buffer.read(cx).snapshot(cx);
13247
13248 let mut edits = Vec::new();
13249 let mut unfold_ranges = Vec::new();
13250 let mut refold_creases = Vec::new();
13251
13252 let selections = self.selections.all::<Point>(&display_map);
13253 let mut selections = selections.iter().peekable();
13254 let mut contiguous_row_selections = Vec::new();
13255 let mut new_selections = Vec::new();
13256
13257 while let Some(selection) = selections.next() {
13258 // Find all the selections that span a contiguous row range
13259 let (start_row, end_row) = consume_contiguous_rows(
13260 &mut contiguous_row_selections,
13261 selection,
13262 &display_map,
13263 &mut selections,
13264 );
13265
13266 // Move the text spanned by the row range to be after the last line of the row range
13267 if end_row.0 <= buffer.max_point().row {
13268 let range_to_move =
13269 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
13270 let insertion_point = display_map
13271 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
13272 .0;
13273
13274 // Don't move lines across excerpt boundaries
13275 if buffer
13276 .excerpt_containing(range_to_move.start..insertion_point)
13277 .is_some()
13278 {
13279 let mut text = String::from("\n");
13280 text.extend(buffer.text_for_range(range_to_move.clone()));
13281 text.pop(); // Drop trailing newline
13282 edits.push((
13283 buffer.anchor_after(range_to_move.start)
13284 ..buffer.anchor_before(range_to_move.end),
13285 String::new(),
13286 ));
13287 let insertion_anchor = buffer.anchor_after(insertion_point);
13288 edits.push((insertion_anchor..insertion_anchor, text));
13289
13290 let row_delta = insertion_point.row - range_to_move.end.row + 1;
13291
13292 // Move selections down
13293 new_selections.extend(contiguous_row_selections.drain(..).map(
13294 |mut selection| {
13295 selection.start.row += row_delta;
13296 selection.end.row += row_delta;
13297 selection
13298 },
13299 ));
13300
13301 // Move folds down
13302 unfold_ranges.push(range_to_move.clone());
13303 for fold in display_map.folds_in_range(
13304 buffer.anchor_before(range_to_move.start)
13305 ..buffer.anchor_after(range_to_move.end),
13306 ) {
13307 let mut start = fold.range.start.to_point(&buffer);
13308 let mut end = fold.range.end.to_point(&buffer);
13309 start.row += row_delta;
13310 end.row += row_delta;
13311 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
13312 }
13313 }
13314 }
13315
13316 // If we didn't move line(s), preserve the existing selections
13317 new_selections.append(&mut contiguous_row_selections);
13318 }
13319
13320 self.transact(window, cx, |this, window, cx| {
13321 this.unfold_ranges(&unfold_ranges, true, true, cx);
13322 this.buffer.update(cx, |buffer, cx| {
13323 for (range, text) in edits {
13324 buffer.edit([(range, text)], None, cx);
13325 }
13326 });
13327 this.fold_creases(refold_creases, true, window, cx);
13328 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
13329 });
13330 }
13331
13332 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
13333 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13334 let text_layout_details = &self.text_layout_details(window, cx);
13335 self.transact(window, cx, |this, window, cx| {
13336 let edits = this.change_selections(Default::default(), window, cx, |s| {
13337 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
13338 s.move_with(&mut |display_map, selection| {
13339 if !selection.is_empty() {
13340 return;
13341 }
13342
13343 let mut head = selection.head();
13344 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
13345 if head.column() == display_map.line_len(head.row()) {
13346 transpose_offset = display_map
13347 .buffer_snapshot()
13348 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13349 }
13350
13351 if transpose_offset == MultiBufferOffset(0) {
13352 return;
13353 }
13354
13355 *head.column_mut() += 1;
13356 head = display_map.clip_point(head, Bias::Right);
13357 let goal = SelectionGoal::HorizontalPosition(
13358 display_map
13359 .x_for_display_point(head, text_layout_details)
13360 .into(),
13361 );
13362 selection.collapse_to(head, goal);
13363
13364 let transpose_start = display_map
13365 .buffer_snapshot()
13366 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13367 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
13368 let transpose_end = display_map
13369 .buffer_snapshot()
13370 .clip_offset(transpose_offset + 1usize, Bias::Right);
13371 if let Some(ch) = display_map
13372 .buffer_snapshot()
13373 .chars_at(transpose_start)
13374 .next()
13375 {
13376 edits.push((transpose_start..transpose_offset, String::new()));
13377 edits.push((transpose_end..transpose_end, ch.to_string()));
13378 }
13379 }
13380 });
13381 edits
13382 });
13383 this.buffer
13384 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13385 let selections = this
13386 .selections
13387 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13388 this.change_selections(Default::default(), window, cx, |s| {
13389 s.select(selections);
13390 });
13391 });
13392 }
13393
13394 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
13395 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13396 if self.mode.is_single_line() {
13397 cx.propagate();
13398 return;
13399 }
13400
13401 self.rewrap_impl(RewrapOptions::default(), cx)
13402 }
13403
13404 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
13405 let buffer = self.buffer.read(cx).snapshot(cx);
13406 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13407
13408 #[derive(Clone, Debug, PartialEq)]
13409 enum CommentFormat {
13410 /// single line comment, with prefix for line
13411 Line(String),
13412 /// single line within a block comment, with prefix for line
13413 BlockLine(String),
13414 /// a single line of a block comment that includes the initial delimiter
13415 BlockCommentWithStart(BlockCommentConfig),
13416 /// a single line of a block comment that includes the ending delimiter
13417 BlockCommentWithEnd(BlockCommentConfig),
13418 }
13419
13420 // Split selections to respect paragraph, indent, and comment prefix boundaries.
13421 let wrap_ranges = selections.into_iter().flat_map(|selection| {
13422 let language_settings = buffer.language_settings_at(selection.head(), cx);
13423 let language_scope = buffer.language_scope_at(selection.head());
13424
13425 let indent_and_prefix_for_row =
13426 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
13427 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
13428 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
13429 &language_scope
13430 {
13431 let indent_end = Point::new(row, indent.len);
13432 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13433 let line_text_after_indent = buffer
13434 .text_for_range(indent_end..line_end)
13435 .collect::<String>();
13436
13437 let is_within_comment_override = buffer
13438 .language_scope_at(indent_end)
13439 .is_some_and(|scope| scope.override_name() == Some("comment"));
13440 let comment_delimiters = if is_within_comment_override {
13441 // we are within a comment syntax node, but we don't
13442 // yet know what kind of comment: block, doc or line
13443 match (
13444 language_scope.documentation_comment(),
13445 language_scope.block_comment(),
13446 ) {
13447 (Some(config), _) | (_, Some(config))
13448 if buffer.contains_str_at(indent_end, &config.start) =>
13449 {
13450 Some(CommentFormat::BlockCommentWithStart(config.clone()))
13451 }
13452 (Some(config), _) | (_, Some(config))
13453 if line_text_after_indent.ends_with(config.end.as_ref()) =>
13454 {
13455 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
13456 }
13457 (Some(config), _) | (_, Some(config))
13458 if buffer.contains_str_at(indent_end, &config.prefix) =>
13459 {
13460 Some(CommentFormat::BlockLine(config.prefix.to_string()))
13461 }
13462 (_, _) => language_scope
13463 .line_comment_prefixes()
13464 .iter()
13465 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13466 .map(|prefix| CommentFormat::Line(prefix.to_string())),
13467 }
13468 } else {
13469 // we not in an overridden comment node, but we may
13470 // be within a non-overridden line comment node
13471 language_scope
13472 .line_comment_prefixes()
13473 .iter()
13474 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13475 .map(|prefix| CommentFormat::Line(prefix.to_string()))
13476 };
13477
13478 let rewrap_prefix = language_scope
13479 .rewrap_prefixes()
13480 .iter()
13481 .find_map(|prefix_regex| {
13482 prefix_regex.find(&line_text_after_indent).map(|mat| {
13483 if mat.start() == 0 {
13484 Some(mat.as_str().to_string())
13485 } else {
13486 None
13487 }
13488 })
13489 })
13490 .flatten();
13491 (comment_delimiters, rewrap_prefix)
13492 } else {
13493 (None, None)
13494 };
13495 (indent, comment_prefix, rewrap_prefix)
13496 };
13497
13498 let mut start_row = selection.start.row;
13499 let mut end_row = selection.end.row;
13500
13501 if selection.is_empty() {
13502 let cursor_row = selection.start.row;
13503
13504 let (mut indent_size, comment_prefix, _) = indent_and_prefix_for_row(cursor_row);
13505 let line_prefix = match &comment_prefix {
13506 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
13507 Some(prefix.as_str())
13508 }
13509 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
13510 prefix, ..
13511 })) => Some(prefix.as_ref()),
13512 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13513 start: _,
13514 end: _,
13515 prefix,
13516 tab_size,
13517 })) => {
13518 indent_size.len += tab_size;
13519 Some(prefix.as_ref())
13520 }
13521 None => None,
13522 };
13523 let indent_prefix = indent_size.chars().collect::<String>();
13524 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
13525
13526 'expand_upwards: while start_row > 0 {
13527 let prev_row = start_row - 1;
13528 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
13529 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
13530 && !buffer.is_line_blank(MultiBufferRow(prev_row))
13531 {
13532 start_row = prev_row;
13533 } else {
13534 break 'expand_upwards;
13535 }
13536 }
13537
13538 'expand_downwards: while end_row < buffer.max_point().row {
13539 let next_row = end_row + 1;
13540 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
13541 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
13542 && !buffer.is_line_blank(MultiBufferRow(next_row))
13543 {
13544 end_row = next_row;
13545 } else {
13546 break 'expand_downwards;
13547 }
13548 }
13549 }
13550
13551 let mut non_blank_rows_iter = (start_row..=end_row)
13552 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
13553 .peekable();
13554
13555 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
13556 row
13557 } else {
13558 return Vec::new();
13559 };
13560
13561 let mut ranges = Vec::new();
13562
13563 let mut current_range_start = first_row;
13564 let mut prev_row = first_row;
13565 let (
13566 mut current_range_indent,
13567 mut current_range_comment_delimiters,
13568 mut current_range_rewrap_prefix,
13569 ) = indent_and_prefix_for_row(first_row);
13570
13571 for row in non_blank_rows_iter.skip(1) {
13572 let has_paragraph_break = row > prev_row + 1;
13573
13574 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
13575 indent_and_prefix_for_row(row);
13576
13577 let has_indent_change = row_indent != current_range_indent;
13578 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
13579
13580 let has_boundary_change = has_comment_change
13581 || row_rewrap_prefix.is_some()
13582 || (has_indent_change && current_range_comment_delimiters.is_some());
13583
13584 if has_paragraph_break || has_boundary_change {
13585 ranges.push((
13586 language_settings.clone(),
13587 Point::new(current_range_start, 0)
13588 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13589 current_range_indent,
13590 current_range_comment_delimiters.clone(),
13591 current_range_rewrap_prefix.clone(),
13592 ));
13593 current_range_start = row;
13594 current_range_indent = row_indent;
13595 current_range_comment_delimiters = row_comment_delimiters;
13596 current_range_rewrap_prefix = row_rewrap_prefix;
13597 }
13598 prev_row = row;
13599 }
13600
13601 ranges.push((
13602 language_settings.clone(),
13603 Point::new(current_range_start, 0)
13604 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13605 current_range_indent,
13606 current_range_comment_delimiters,
13607 current_range_rewrap_prefix,
13608 ));
13609
13610 ranges
13611 });
13612
13613 let mut edits = Vec::new();
13614 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
13615
13616 for (language_settings, wrap_range, mut indent_size, comment_prefix, rewrap_prefix) in
13617 wrap_ranges
13618 {
13619 let start_row = wrap_range.start.row;
13620 let end_row = wrap_range.end.row;
13621
13622 // Skip selections that overlap with a range that has already been rewrapped.
13623 let selection_range = start_row..end_row;
13624 if rewrapped_row_ranges
13625 .iter()
13626 .any(|range| range.overlaps(&selection_range))
13627 {
13628 continue;
13629 }
13630
13631 let tab_size = language_settings.tab_size;
13632
13633 let (line_prefix, inside_comment) = match &comment_prefix {
13634 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
13635 (Some(prefix.as_str()), true)
13636 }
13637 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
13638 (Some(prefix.as_ref()), true)
13639 }
13640 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13641 start: _,
13642 end: _,
13643 prefix,
13644 tab_size,
13645 })) => {
13646 indent_size.len += tab_size;
13647 (Some(prefix.as_ref()), true)
13648 }
13649 None => (None, false),
13650 };
13651 let indent_prefix = indent_size.chars().collect::<String>();
13652 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
13653
13654 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
13655 RewrapBehavior::InComments => inside_comment,
13656 RewrapBehavior::InSelections => !wrap_range.is_empty(),
13657 RewrapBehavior::Anywhere => true,
13658 };
13659
13660 let should_rewrap = options.override_language_settings
13661 || allow_rewrap_based_on_language
13662 || self.hard_wrap.is_some();
13663 if !should_rewrap {
13664 continue;
13665 }
13666
13667 let start = Point::new(start_row, 0);
13668 let start_offset = ToOffset::to_offset(&start, &buffer);
13669 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
13670 let selection_text = buffer.text_for_range(start..end).collect::<String>();
13671 let mut first_line_delimiter = None;
13672 let mut last_line_delimiter = None;
13673 let Some(lines_without_prefixes) = selection_text
13674 .lines()
13675 .enumerate()
13676 .map(|(ix, line)| {
13677 let line_trimmed = line.trim_start();
13678 if rewrap_prefix.is_some() && ix > 0 {
13679 Ok(line_trimmed)
13680 } else if let Some(
13681 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13682 start,
13683 prefix,
13684 end,
13685 tab_size,
13686 })
13687 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
13688 start,
13689 prefix,
13690 end,
13691 tab_size,
13692 }),
13693 ) = &comment_prefix
13694 {
13695 let line_trimmed = line_trimmed
13696 .strip_prefix(start.as_ref())
13697 .map(|s| {
13698 let mut indent_size = indent_size;
13699 indent_size.len -= tab_size;
13700 let indent_prefix: String = indent_size.chars().collect();
13701 first_line_delimiter = Some((indent_prefix, start));
13702 s.trim_start()
13703 })
13704 .unwrap_or(line_trimmed);
13705 let line_trimmed = line_trimmed
13706 .strip_suffix(end.as_ref())
13707 .map(|s| {
13708 last_line_delimiter = Some(end);
13709 s.trim_end()
13710 })
13711 .unwrap_or(line_trimmed);
13712 let line_trimmed = line_trimmed
13713 .strip_prefix(prefix.as_ref())
13714 .unwrap_or(line_trimmed);
13715 Ok(line_trimmed)
13716 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
13717 line_trimmed.strip_prefix(prefix).with_context(|| {
13718 format!("line did not start with prefix {prefix:?}: {line:?}")
13719 })
13720 } else {
13721 line_trimmed
13722 .strip_prefix(&line_prefix.trim_start())
13723 .with_context(|| {
13724 format!("line did not start with prefix {line_prefix:?}: {line:?}")
13725 })
13726 }
13727 })
13728 .collect::<Result<Vec<_>, _>>()
13729 .log_err()
13730 else {
13731 continue;
13732 };
13733
13734 let wrap_column = options.line_length.or(self.hard_wrap).unwrap_or_else(|| {
13735 buffer
13736 .language_settings_at(Point::new(start_row, 0), cx)
13737 .preferred_line_length as usize
13738 });
13739
13740 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
13741 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
13742 } else {
13743 line_prefix.clone()
13744 };
13745
13746 let wrapped_text = {
13747 let mut wrapped_text = wrap_with_prefix(
13748 line_prefix,
13749 subsequent_lines_prefix,
13750 lines_without_prefixes.join("\n"),
13751 wrap_column,
13752 tab_size,
13753 options.preserve_existing_whitespace,
13754 );
13755
13756 if let Some((indent, delimiter)) = first_line_delimiter {
13757 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
13758 }
13759 if let Some(last_line) = last_line_delimiter {
13760 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
13761 }
13762
13763 wrapped_text
13764 };
13765
13766 // TODO: should always use char-based diff while still supporting cursor behavior that
13767 // matches vim.
13768 let mut diff_options = DiffOptions::default();
13769 if options.override_language_settings {
13770 diff_options.max_word_diff_len = 0;
13771 diff_options.max_word_diff_line_count = 0;
13772 } else {
13773 diff_options.max_word_diff_len = usize::MAX;
13774 diff_options.max_word_diff_line_count = usize::MAX;
13775 }
13776
13777 for (old_range, new_text) in
13778 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
13779 {
13780 let edit_start = buffer.anchor_after(start_offset + old_range.start);
13781 let edit_end = buffer.anchor_after(start_offset + old_range.end);
13782 edits.push((edit_start..edit_end, new_text));
13783 }
13784
13785 rewrapped_row_ranges.push(start_row..=end_row);
13786 }
13787
13788 self.buffer
13789 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13790 }
13791
13792 pub fn cut_common(
13793 &mut self,
13794 cut_no_selection_line: bool,
13795 window: &mut Window,
13796 cx: &mut Context<Self>,
13797 ) -> ClipboardItem {
13798 let mut text = String::new();
13799 let buffer = self.buffer.read(cx).snapshot(cx);
13800 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13801 let mut clipboard_selections = Vec::with_capacity(selections.len());
13802 {
13803 let max_point = buffer.max_point();
13804 let mut is_first = true;
13805 let mut prev_selection_was_entire_line = false;
13806 for selection in &mut selections {
13807 let is_entire_line =
13808 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
13809 if is_entire_line {
13810 selection.start = Point::new(selection.start.row, 0);
13811 if !selection.is_empty() && selection.end.column == 0 {
13812 selection.end = cmp::min(max_point, selection.end);
13813 } else {
13814 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
13815 }
13816 selection.goal = SelectionGoal::None;
13817 }
13818 if is_first {
13819 is_first = false;
13820 } else if !prev_selection_was_entire_line {
13821 text += "\n";
13822 }
13823 prev_selection_was_entire_line = is_entire_line;
13824 let mut len = 0;
13825 for chunk in buffer.text_for_range(selection.start..selection.end) {
13826 text.push_str(chunk);
13827 len += chunk.len();
13828 }
13829
13830 clipboard_selections.push(ClipboardSelection::for_buffer(
13831 len,
13832 is_entire_line,
13833 selection.range(),
13834 &buffer,
13835 self.project.as_ref(),
13836 cx,
13837 ));
13838 }
13839 }
13840
13841 self.transact(window, cx, |this, window, cx| {
13842 this.change_selections(Default::default(), window, cx, |s| {
13843 s.select(selections);
13844 });
13845 this.insert("", window, cx);
13846 });
13847 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
13848 }
13849
13850 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
13851 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13852 let item = self.cut_common(true, window, cx);
13853 cx.write_to_clipboard(item);
13854 }
13855
13856 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
13857 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13858 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13859 s.move_with(&mut |snapshot, sel| {
13860 if sel.is_empty() {
13861 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
13862 }
13863 if sel.is_empty() {
13864 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13865 }
13866 });
13867 });
13868 let item = self.cut_common(false, window, cx);
13869 cx.set_global(KillRing(item))
13870 }
13871
13872 pub fn kill_ring_yank(
13873 &mut self,
13874 _: &KillRingYank,
13875 window: &mut Window,
13876 cx: &mut Context<Self>,
13877 ) {
13878 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13879 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
13880 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
13881 (kill_ring.text().to_string(), kill_ring.metadata_json())
13882 } else {
13883 return;
13884 }
13885 } else {
13886 return;
13887 };
13888 self.do_paste(&text, metadata, false, window, cx);
13889 }
13890
13891 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
13892 self.do_copy(true, cx);
13893 }
13894
13895 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
13896 self.do_copy(false, cx);
13897 }
13898
13899 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
13900 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13901 let buffer = self.buffer.read(cx).read(cx);
13902 let mut text = String::new();
13903 let mut clipboard_selections = Vec::with_capacity(selections.len());
13904
13905 let max_point = buffer.max_point();
13906 let mut is_first = true;
13907 for selection in &selections {
13908 let mut start = selection.start;
13909 let mut end = selection.end;
13910 let is_entire_line = selection.is_empty() || self.selections.line_mode();
13911 let mut add_trailing_newline = false;
13912 if is_entire_line {
13913 start = Point::new(start.row, 0);
13914 let next_line_start = Point::new(end.row + 1, 0);
13915 if next_line_start <= max_point {
13916 end = next_line_start;
13917 } else {
13918 // We're on the last line without a trailing newline.
13919 // Copy to the end of the line and add a newline afterwards.
13920 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
13921 add_trailing_newline = true;
13922 }
13923 }
13924
13925 let mut trimmed_selections = Vec::new();
13926 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
13927 let row = MultiBufferRow(start.row);
13928 let first_indent = buffer.indent_size_for_line(row);
13929 if first_indent.len == 0 || start.column > first_indent.len {
13930 trimmed_selections.push(start..end);
13931 } else {
13932 trimmed_selections.push(
13933 Point::new(row.0, first_indent.len)
13934 ..Point::new(row.0, buffer.line_len(row)),
13935 );
13936 for row in start.row + 1..=end.row {
13937 let mut line_len = buffer.line_len(MultiBufferRow(row));
13938 if row == end.row {
13939 line_len = end.column;
13940 }
13941 if line_len == 0 {
13942 trimmed_selections.push(Point::new(row, 0)..Point::new(row, line_len));
13943 continue;
13944 }
13945 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
13946 if row_indent_size.len >= first_indent.len {
13947 trimmed_selections
13948 .push(Point::new(row, first_indent.len)..Point::new(row, line_len));
13949 } else {
13950 trimmed_selections.clear();
13951 trimmed_selections.push(start..end);
13952 break;
13953 }
13954 }
13955 }
13956 } else {
13957 trimmed_selections.push(start..end);
13958 }
13959
13960 let is_multiline_trim = trimmed_selections.len() > 1;
13961 let mut selection_len: usize = 0;
13962 let prev_selection_was_entire_line = is_entire_line && !is_multiline_trim;
13963
13964 for trimmed_range in trimmed_selections {
13965 if is_first {
13966 is_first = false;
13967 } else if is_multiline_trim || !prev_selection_was_entire_line {
13968 text.push('\n');
13969 if is_multiline_trim {
13970 selection_len += 1;
13971 }
13972 }
13973 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
13974 text.push_str(chunk);
13975 selection_len += chunk.len();
13976 }
13977 if add_trailing_newline {
13978 text.push('\n');
13979 selection_len += 1;
13980 }
13981 }
13982
13983 clipboard_selections.push(ClipboardSelection::for_buffer(
13984 selection_len,
13985 is_entire_line,
13986 start..end,
13987 &buffer,
13988 self.project.as_ref(),
13989 cx,
13990 ));
13991 }
13992
13993 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
13994 text,
13995 clipboard_selections,
13996 ));
13997 }
13998
13999 pub fn do_paste(
14000 &mut self,
14001 text: &String,
14002 clipboard_selections: Option<Vec<ClipboardSelection>>,
14003 handle_entire_lines: bool,
14004 window: &mut Window,
14005 cx: &mut Context<Self>,
14006 ) {
14007 if self.read_only(cx) {
14008 return;
14009 }
14010
14011 let clipboard_text = Cow::Borrowed(text.as_str());
14012
14013 self.transact(window, cx, |this, window, cx| {
14014 let had_active_edit_prediction = this.has_active_edit_prediction();
14015 let display_map = this.display_snapshot(cx);
14016 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
14017 let cursor_offset = this
14018 .selections
14019 .last::<MultiBufferOffset>(&display_map)
14020 .head();
14021
14022 if let Some(mut clipboard_selections) = clipboard_selections {
14023 let all_selections_were_entire_line =
14024 clipboard_selections.iter().all(|s| s.is_entire_line);
14025 let first_selection_indent_column =
14026 clipboard_selections.first().map(|s| s.first_line_indent);
14027 if clipboard_selections.len() != old_selections.len() {
14028 clipboard_selections.drain(..);
14029 }
14030 let mut auto_indent_on_paste = true;
14031
14032 this.buffer.update(cx, |buffer, cx| {
14033 let snapshot = buffer.read(cx);
14034 auto_indent_on_paste = snapshot
14035 .language_settings_at(cursor_offset, cx)
14036 .auto_indent_on_paste;
14037
14038 let mut start_offset = 0;
14039 let mut edits = Vec::new();
14040 let mut original_indent_columns = Vec::new();
14041 for (ix, selection) in old_selections.iter().enumerate() {
14042 let to_insert;
14043 let entire_line;
14044 let original_indent_column;
14045 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
14046 let end_offset = start_offset + clipboard_selection.len;
14047 to_insert = &clipboard_text[start_offset..end_offset];
14048 entire_line = clipboard_selection.is_entire_line;
14049 start_offset = if entire_line {
14050 end_offset
14051 } else {
14052 end_offset + 1
14053 };
14054 original_indent_column = Some(clipboard_selection.first_line_indent);
14055 } else {
14056 to_insert = &*clipboard_text;
14057 entire_line = all_selections_were_entire_line;
14058 original_indent_column = first_selection_indent_column
14059 }
14060
14061 let (range, to_insert) =
14062 if selection.is_empty() && handle_entire_lines && entire_line {
14063 // If the corresponding selection was empty when this slice of the
14064 // clipboard text was written, then the entire line containing the
14065 // selection was copied. If this selection is also currently empty,
14066 // then paste the line before the current line of the buffer.
14067 let column = selection.start.to_point(&snapshot).column as usize;
14068 let line_start = selection.start - column;
14069 (line_start..line_start, Cow::Borrowed(to_insert))
14070 } else {
14071 let language = snapshot.language_at(selection.head());
14072 let range = selection.range();
14073 if let Some(language) = language
14074 && language.name() == "Markdown"
14075 {
14076 edit_for_markdown_paste(
14077 &snapshot,
14078 range,
14079 to_insert,
14080 url::Url::parse(to_insert).ok(),
14081 )
14082 } else {
14083 (range, Cow::Borrowed(to_insert))
14084 }
14085 };
14086
14087 edits.push((range, to_insert));
14088 original_indent_columns.push(original_indent_column);
14089 }
14090 drop(snapshot);
14091
14092 buffer.edit(
14093 edits,
14094 if auto_indent_on_paste {
14095 Some(AutoindentMode::Block {
14096 original_indent_columns,
14097 })
14098 } else {
14099 None
14100 },
14101 cx,
14102 );
14103 });
14104
14105 let selections = this
14106 .selections
14107 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
14108 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14109 } else {
14110 let url = url::Url::parse(&clipboard_text).ok();
14111
14112 let auto_indent_mode = if !clipboard_text.is_empty() {
14113 Some(AutoindentMode::Block {
14114 original_indent_columns: Vec::new(),
14115 })
14116 } else {
14117 None
14118 };
14119
14120 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
14121 let snapshot = buffer.snapshot(cx);
14122
14123 let anchors = old_selections
14124 .iter()
14125 .map(|s| {
14126 let anchor = snapshot.anchor_after(s.head());
14127 s.map(|_| anchor)
14128 })
14129 .collect::<Vec<_>>();
14130
14131 let mut edits = Vec::new();
14132
14133 // When pasting text without metadata (e.g. copied from an
14134 // external editor using multiple cursors) and the number of
14135 // lines matches the number of selections, distribute one
14136 // line per cursor instead of pasting the whole text at each.
14137 let lines: Vec<&str> = clipboard_text.split('\n').collect();
14138 let distribute_lines =
14139 old_selections.len() > 1 && lines.len() == old_selections.len();
14140
14141 for (ix, selection) in old_selections.iter().enumerate() {
14142 let language = snapshot.language_at(selection.head());
14143 let range = selection.range();
14144
14145 let text_for_cursor: &str = if distribute_lines {
14146 lines[ix]
14147 } else {
14148 &clipboard_text
14149 };
14150
14151 let (edit_range, edit_text) = if let Some(language) = language
14152 && language.name() == "Markdown"
14153 {
14154 edit_for_markdown_paste(&snapshot, range, text_for_cursor, url.clone())
14155 } else {
14156 (range, Cow::Borrowed(text_for_cursor))
14157 };
14158
14159 edits.push((edit_range, edit_text));
14160 }
14161
14162 drop(snapshot);
14163 buffer.edit(edits, auto_indent_mode, cx);
14164
14165 anchors
14166 });
14167
14168 this.change_selections(Default::default(), window, cx, |s| {
14169 s.select_anchors(selection_anchors);
14170 });
14171 }
14172
14173 // 🤔 | .. | show_in_menu |
14174 // | .. | true true
14175 // | had_edit_prediction | false true
14176
14177 let trigger_in_words =
14178 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
14179
14180 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
14181 });
14182 }
14183
14184 pub fn diff_clipboard_with_selection(
14185 &mut self,
14186 _: &DiffClipboardWithSelection,
14187 window: &mut Window,
14188 cx: &mut Context<Self>,
14189 ) {
14190 let selections = self
14191 .selections
14192 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
14193
14194 if selections.is_empty() {
14195 log::warn!("There should always be at least one selection in Zed. This is a bug.");
14196 return;
14197 };
14198
14199 let clipboard_text = cx.read_from_clipboard().and_then(|item| {
14200 item.entries().iter().find_map(|entry| match entry {
14201 ClipboardEntry::String(text) => Some(text.text().to_string()),
14202 _ => None,
14203 })
14204 });
14205
14206 let Some(clipboard_text) = clipboard_text else {
14207 log::warn!("Clipboard doesn't contain text.");
14208 return;
14209 };
14210
14211 window.dispatch_action(
14212 Box::new(DiffClipboardWithSelectionData {
14213 clipboard_text,
14214 editor: cx.entity(),
14215 }),
14216 cx,
14217 );
14218 }
14219
14220 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
14221 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14222 if let Some(item) = cx.read_from_clipboard() {
14223 let clipboard_string = item.entries().iter().find_map(|entry| match entry {
14224 ClipboardEntry::String(s) => Some(s),
14225 _ => None,
14226 });
14227 match clipboard_string {
14228 Some(clipboard_string) => self.do_paste(
14229 clipboard_string.text(),
14230 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
14231 true,
14232 window,
14233 cx,
14234 ),
14235 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
14236 }
14237 }
14238 }
14239
14240 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
14241 if self.read_only(cx) {
14242 return;
14243 }
14244
14245 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14246
14247 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
14248 if let Some((selections, _)) =
14249 self.selection_history.transaction(transaction_id).cloned()
14250 {
14251 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14252 s.select_anchors(selections.to_vec());
14253 });
14254 } else {
14255 log::error!(
14256 "No entry in selection_history found for undo. \
14257 This may correspond to a bug where undo does not update the selection. \
14258 If this is occurring, please add details to \
14259 https://github.com/zed-industries/zed/issues/22692"
14260 );
14261 }
14262 self.request_autoscroll(Autoscroll::fit(), cx);
14263 self.unmark_text(window, cx);
14264 self.refresh_edit_prediction(true, false, window, cx);
14265 cx.emit(EditorEvent::Edited { transaction_id });
14266 cx.emit(EditorEvent::TransactionUndone { transaction_id });
14267 }
14268 }
14269
14270 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
14271 if self.read_only(cx) {
14272 return;
14273 }
14274
14275 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14276
14277 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
14278 if let Some((_, Some(selections))) =
14279 self.selection_history.transaction(transaction_id).cloned()
14280 {
14281 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14282 s.select_anchors(selections.to_vec());
14283 });
14284 } else {
14285 log::error!(
14286 "No entry in selection_history found for redo. \
14287 This may correspond to a bug where undo does not update the selection. \
14288 If this is occurring, please add details to \
14289 https://github.com/zed-industries/zed/issues/22692"
14290 );
14291 }
14292 self.request_autoscroll(Autoscroll::fit(), cx);
14293 self.unmark_text(window, cx);
14294 self.refresh_edit_prediction(true, false, window, cx);
14295 cx.emit(EditorEvent::Edited { transaction_id });
14296 }
14297 }
14298
14299 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
14300 self.buffer
14301 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
14302 }
14303
14304 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
14305 self.buffer
14306 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
14307 }
14308
14309 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
14310 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14311 self.change_selections(Default::default(), window, cx, |s| {
14312 s.move_with(&mut |map, selection| {
14313 let cursor = if selection.is_empty() {
14314 movement::left(map, selection.start)
14315 } else {
14316 selection.start
14317 };
14318 selection.collapse_to(cursor, SelectionGoal::None);
14319 });
14320 })
14321 }
14322
14323 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
14324 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14325 self.change_selections(Default::default(), window, cx, |s| {
14326 s.move_heads_with(&mut |map, head, _| (movement::left(map, head), SelectionGoal::None));
14327 })
14328 }
14329
14330 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
14331 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14332 self.change_selections(Default::default(), window, cx, |s| {
14333 s.move_with(&mut |map, selection| {
14334 let cursor = if selection.is_empty() {
14335 movement::right(map, selection.end)
14336 } else {
14337 selection.end
14338 };
14339 selection.collapse_to(cursor, SelectionGoal::None)
14340 });
14341 })
14342 }
14343
14344 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
14345 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14346 self.change_selections(Default::default(), window, cx, |s| {
14347 s.move_heads_with(&mut |map, head, _| {
14348 (movement::right(map, head), SelectionGoal::None)
14349 });
14350 });
14351 }
14352
14353 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
14354 if self.take_rename(true, window, cx).is_some() {
14355 return;
14356 }
14357
14358 if self.mode.is_single_line() {
14359 cx.propagate();
14360 return;
14361 }
14362
14363 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14364
14365 let text_layout_details = &self.text_layout_details(window, cx);
14366 let selection_count = self.selections.count();
14367 let first_selection = self.selections.first_anchor();
14368
14369 self.change_selections(Default::default(), window, cx, |s| {
14370 s.move_with(&mut |map, selection| {
14371 if !selection.is_empty() {
14372 selection.goal = SelectionGoal::None;
14373 }
14374 let (cursor, goal) = movement::up(
14375 map,
14376 selection.start,
14377 selection.goal,
14378 false,
14379 text_layout_details,
14380 );
14381 selection.collapse_to(cursor, goal);
14382 });
14383 });
14384
14385 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14386 {
14387 cx.propagate();
14388 }
14389 }
14390
14391 pub fn move_up_by_lines(
14392 &mut self,
14393 action: &MoveUpByLines,
14394 window: &mut Window,
14395 cx: &mut Context<Self>,
14396 ) {
14397 if self.take_rename(true, window, cx).is_some() {
14398 return;
14399 }
14400
14401 if self.mode.is_single_line() {
14402 cx.propagate();
14403 return;
14404 }
14405
14406 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14407
14408 let text_layout_details = &self.text_layout_details(window, cx);
14409
14410 self.change_selections(Default::default(), window, cx, |s| {
14411 s.move_with(&mut |map, selection| {
14412 if !selection.is_empty() {
14413 selection.goal = SelectionGoal::None;
14414 }
14415 let (cursor, goal) = movement::up_by_rows(
14416 map,
14417 selection.start,
14418 action.lines,
14419 selection.goal,
14420 false,
14421 text_layout_details,
14422 );
14423 selection.collapse_to(cursor, goal);
14424 });
14425 })
14426 }
14427
14428 pub fn move_down_by_lines(
14429 &mut self,
14430 action: &MoveDownByLines,
14431 window: &mut Window,
14432 cx: &mut Context<Self>,
14433 ) {
14434 if self.take_rename(true, window, cx).is_some() {
14435 return;
14436 }
14437
14438 if self.mode.is_single_line() {
14439 cx.propagate();
14440 return;
14441 }
14442
14443 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14444
14445 let text_layout_details = &self.text_layout_details(window, cx);
14446
14447 self.change_selections(Default::default(), window, cx, |s| {
14448 s.move_with(&mut |map, selection| {
14449 if !selection.is_empty() {
14450 selection.goal = SelectionGoal::None;
14451 }
14452 let (cursor, goal) = movement::down_by_rows(
14453 map,
14454 selection.start,
14455 action.lines,
14456 selection.goal,
14457 false,
14458 text_layout_details,
14459 );
14460 selection.collapse_to(cursor, goal);
14461 });
14462 })
14463 }
14464
14465 pub fn select_down_by_lines(
14466 &mut self,
14467 action: &SelectDownByLines,
14468 window: &mut Window,
14469 cx: &mut Context<Self>,
14470 ) {
14471 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14472 let text_layout_details = &self.text_layout_details(window, cx);
14473 self.change_selections(Default::default(), window, cx, |s| {
14474 s.move_heads_with(&mut |map, head, goal| {
14475 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
14476 })
14477 })
14478 }
14479
14480 pub fn select_up_by_lines(
14481 &mut self,
14482 action: &SelectUpByLines,
14483 window: &mut Window,
14484 cx: &mut Context<Self>,
14485 ) {
14486 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14487 let text_layout_details = &self.text_layout_details(window, cx);
14488 self.change_selections(Default::default(), window, cx, |s| {
14489 s.move_heads_with(&mut |map, head, goal| {
14490 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
14491 })
14492 })
14493 }
14494
14495 pub fn select_page_up(
14496 &mut self,
14497 _: &SelectPageUp,
14498 window: &mut Window,
14499 cx: &mut Context<Self>,
14500 ) {
14501 let Some(row_count) = self.visible_row_count() else {
14502 return;
14503 };
14504
14505 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14506
14507 let text_layout_details = &self.text_layout_details(window, cx);
14508
14509 self.change_selections(Default::default(), window, cx, |s| {
14510 s.move_heads_with(&mut |map, head, goal| {
14511 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
14512 })
14513 })
14514 }
14515
14516 pub fn move_page_up(
14517 &mut self,
14518 action: &MovePageUp,
14519 window: &mut Window,
14520 cx: &mut Context<Self>,
14521 ) {
14522 if self.take_rename(true, window, cx).is_some() {
14523 return;
14524 }
14525
14526 if self
14527 .context_menu
14528 .borrow_mut()
14529 .as_mut()
14530 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
14531 .unwrap_or(false)
14532 {
14533 return;
14534 }
14535
14536 if matches!(self.mode, EditorMode::SingleLine) {
14537 cx.propagate();
14538 return;
14539 }
14540
14541 let Some(row_count) = self.visible_row_count() else {
14542 return;
14543 };
14544
14545 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14546
14547 let effects = if action.center_cursor {
14548 SelectionEffects::scroll(Autoscroll::center())
14549 } else {
14550 SelectionEffects::default()
14551 };
14552
14553 let text_layout_details = &self.text_layout_details(window, cx);
14554
14555 self.change_selections(effects, window, cx, |s| {
14556 s.move_with(&mut |map, selection| {
14557 if !selection.is_empty() {
14558 selection.goal = SelectionGoal::None;
14559 }
14560 let (cursor, goal) = movement::up_by_rows(
14561 map,
14562 selection.end,
14563 row_count,
14564 selection.goal,
14565 false,
14566 text_layout_details,
14567 );
14568 selection.collapse_to(cursor, goal);
14569 });
14570 });
14571 }
14572
14573 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
14574 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14575 let text_layout_details = &self.text_layout_details(window, cx);
14576 self.change_selections(Default::default(), window, cx, |s| {
14577 s.move_heads_with(&mut |map, head, goal| {
14578 movement::up(map, head, goal, false, text_layout_details)
14579 })
14580 })
14581 }
14582
14583 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
14584 self.take_rename(true, window, cx);
14585
14586 if self.mode.is_single_line() {
14587 cx.propagate();
14588 return;
14589 }
14590
14591 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14592
14593 let text_layout_details = &self.text_layout_details(window, cx);
14594 let selection_count = self.selections.count();
14595 let first_selection = self.selections.first_anchor();
14596
14597 self.change_selections(Default::default(), window, cx, |s| {
14598 s.move_with(&mut |map, selection| {
14599 if !selection.is_empty() {
14600 selection.goal = SelectionGoal::None;
14601 }
14602 let (cursor, goal) = movement::down(
14603 map,
14604 selection.end,
14605 selection.goal,
14606 false,
14607 text_layout_details,
14608 );
14609 selection.collapse_to(cursor, goal);
14610 });
14611 });
14612
14613 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14614 {
14615 cx.propagate();
14616 }
14617 }
14618
14619 pub fn select_page_down(
14620 &mut self,
14621 _: &SelectPageDown,
14622 window: &mut Window,
14623 cx: &mut Context<Self>,
14624 ) {
14625 let Some(row_count) = self.visible_row_count() else {
14626 return;
14627 };
14628
14629 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14630
14631 let text_layout_details = &self.text_layout_details(window, cx);
14632
14633 self.change_selections(Default::default(), window, cx, |s| {
14634 s.move_heads_with(&mut |map, head, goal| {
14635 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
14636 })
14637 })
14638 }
14639
14640 pub fn move_page_down(
14641 &mut self,
14642 action: &MovePageDown,
14643 window: &mut Window,
14644 cx: &mut Context<Self>,
14645 ) {
14646 if self.take_rename(true, window, cx).is_some() {
14647 return;
14648 }
14649
14650 if self
14651 .context_menu
14652 .borrow_mut()
14653 .as_mut()
14654 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
14655 .unwrap_or(false)
14656 {
14657 return;
14658 }
14659
14660 if matches!(self.mode, EditorMode::SingleLine) {
14661 cx.propagate();
14662 return;
14663 }
14664
14665 let Some(row_count) = self.visible_row_count() else {
14666 return;
14667 };
14668
14669 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14670
14671 let effects = if action.center_cursor {
14672 SelectionEffects::scroll(Autoscroll::center())
14673 } else {
14674 SelectionEffects::default()
14675 };
14676
14677 let text_layout_details = &self.text_layout_details(window, cx);
14678 self.change_selections(effects, window, cx, |s| {
14679 s.move_with(&mut |map, selection| {
14680 if !selection.is_empty() {
14681 selection.goal = SelectionGoal::None;
14682 }
14683 let (cursor, goal) = movement::down_by_rows(
14684 map,
14685 selection.end,
14686 row_count,
14687 selection.goal,
14688 false,
14689 text_layout_details,
14690 );
14691 selection.collapse_to(cursor, goal);
14692 });
14693 });
14694 }
14695
14696 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
14697 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14698 let text_layout_details = &self.text_layout_details(window, cx);
14699 self.change_selections(Default::default(), window, cx, |s| {
14700 s.move_heads_with(&mut |map, head, goal| {
14701 movement::down(map, head, goal, false, text_layout_details)
14702 })
14703 });
14704 }
14705
14706 pub fn context_menu_first(
14707 &mut self,
14708 _: &ContextMenuFirst,
14709 window: &mut Window,
14710 cx: &mut Context<Self>,
14711 ) {
14712 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14713 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
14714 }
14715 }
14716
14717 pub fn context_menu_prev(
14718 &mut self,
14719 _: &ContextMenuPrevious,
14720 window: &mut Window,
14721 cx: &mut Context<Self>,
14722 ) {
14723 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14724 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
14725 }
14726 }
14727
14728 pub fn context_menu_next(
14729 &mut self,
14730 _: &ContextMenuNext,
14731 window: &mut Window,
14732 cx: &mut Context<Self>,
14733 ) {
14734 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14735 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
14736 }
14737 }
14738
14739 pub fn context_menu_last(
14740 &mut self,
14741 _: &ContextMenuLast,
14742 window: &mut Window,
14743 cx: &mut Context<Self>,
14744 ) {
14745 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14746 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
14747 }
14748 }
14749
14750 pub fn signature_help_prev(
14751 &mut self,
14752 _: &SignatureHelpPrevious,
14753 _: &mut Window,
14754 cx: &mut Context<Self>,
14755 ) {
14756 if let Some(popover) = self.signature_help_state.popover_mut() {
14757 if popover.current_signature == 0 {
14758 popover.current_signature = popover.signatures.len() - 1;
14759 } else {
14760 popover.current_signature -= 1;
14761 }
14762 cx.notify();
14763 }
14764 }
14765
14766 pub fn signature_help_next(
14767 &mut self,
14768 _: &SignatureHelpNext,
14769 _: &mut Window,
14770 cx: &mut Context<Self>,
14771 ) {
14772 if let Some(popover) = self.signature_help_state.popover_mut() {
14773 if popover.current_signature + 1 == popover.signatures.len() {
14774 popover.current_signature = 0;
14775 } else {
14776 popover.current_signature += 1;
14777 }
14778 cx.notify();
14779 }
14780 }
14781
14782 pub fn move_to_previous_word_start(
14783 &mut self,
14784 _: &MoveToPreviousWordStart,
14785 window: &mut Window,
14786 cx: &mut Context<Self>,
14787 ) {
14788 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14789 self.change_selections(Default::default(), window, cx, |s| {
14790 s.move_cursors_with(&mut |map, head, _| {
14791 (
14792 movement::previous_word_start(map, head),
14793 SelectionGoal::None,
14794 )
14795 });
14796 })
14797 }
14798
14799 pub fn move_to_previous_subword_start(
14800 &mut self,
14801 _: &MoveToPreviousSubwordStart,
14802 window: &mut Window,
14803 cx: &mut Context<Self>,
14804 ) {
14805 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14806 self.change_selections(Default::default(), window, cx, |s| {
14807 s.move_cursors_with(&mut |map, head, _| {
14808 (
14809 movement::previous_subword_start(map, head),
14810 SelectionGoal::None,
14811 )
14812 });
14813 })
14814 }
14815
14816 pub fn select_to_previous_word_start(
14817 &mut self,
14818 _: &SelectToPreviousWordStart,
14819 window: &mut Window,
14820 cx: &mut Context<Self>,
14821 ) {
14822 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14823 self.change_selections(Default::default(), window, cx, |s| {
14824 s.move_heads_with(&mut |map, head, _| {
14825 (
14826 movement::previous_word_start(map, head),
14827 SelectionGoal::None,
14828 )
14829 });
14830 })
14831 }
14832
14833 pub fn select_to_previous_subword_start(
14834 &mut self,
14835 _: &SelectToPreviousSubwordStart,
14836 window: &mut Window,
14837 cx: &mut Context<Self>,
14838 ) {
14839 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14840 self.change_selections(Default::default(), window, cx, |s| {
14841 s.move_heads_with(&mut |map, head, _| {
14842 (
14843 movement::previous_subword_start(map, head),
14844 SelectionGoal::None,
14845 )
14846 });
14847 })
14848 }
14849
14850 pub fn delete_to_previous_word_start(
14851 &mut self,
14852 action: &DeleteToPreviousWordStart,
14853 window: &mut Window,
14854 cx: &mut Context<Self>,
14855 ) {
14856 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14857 self.transact(window, cx, |this, window, cx| {
14858 this.select_autoclose_pair(window, cx);
14859 this.change_selections(Default::default(), window, cx, |s| {
14860 s.move_with(&mut |map, selection| {
14861 if selection.is_empty() {
14862 let mut cursor = if action.ignore_newlines {
14863 movement::previous_word_start(map, selection.head())
14864 } else {
14865 movement::previous_word_start_or_newline(map, selection.head())
14866 };
14867 cursor = movement::adjust_greedy_deletion(
14868 map,
14869 selection.head(),
14870 cursor,
14871 action.ignore_brackets,
14872 );
14873 selection.set_head(cursor, SelectionGoal::None);
14874 }
14875 });
14876 });
14877 this.insert("", window, cx);
14878 });
14879 }
14880
14881 pub fn delete_to_previous_subword_start(
14882 &mut self,
14883 action: &DeleteToPreviousSubwordStart,
14884 window: &mut Window,
14885 cx: &mut Context<Self>,
14886 ) {
14887 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14888 self.transact(window, cx, |this, window, cx| {
14889 this.select_autoclose_pair(window, cx);
14890 this.change_selections(Default::default(), window, cx, |s| {
14891 s.move_with(&mut |map, selection| {
14892 if selection.is_empty() {
14893 let mut cursor = if action.ignore_newlines {
14894 movement::previous_subword_start(map, selection.head())
14895 } else {
14896 movement::previous_subword_start_or_newline(map, selection.head())
14897 };
14898 cursor = movement::adjust_greedy_deletion(
14899 map,
14900 selection.head(),
14901 cursor,
14902 action.ignore_brackets,
14903 );
14904 selection.set_head(cursor, SelectionGoal::None);
14905 }
14906 });
14907 });
14908 this.insert("", window, cx);
14909 });
14910 }
14911
14912 pub fn move_to_next_word_end(
14913 &mut self,
14914 _: &MoveToNextWordEnd,
14915 window: &mut Window,
14916 cx: &mut Context<Self>,
14917 ) {
14918 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14919 self.change_selections(Default::default(), window, cx, |s| {
14920 s.move_cursors_with(&mut |map, head, _| {
14921 (movement::next_word_end(map, head), SelectionGoal::None)
14922 });
14923 })
14924 }
14925
14926 pub fn move_to_next_subword_end(
14927 &mut self,
14928 _: &MoveToNextSubwordEnd,
14929 window: &mut Window,
14930 cx: &mut Context<Self>,
14931 ) {
14932 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14933 self.change_selections(Default::default(), window, cx, |s| {
14934 s.move_cursors_with(&mut |map, head, _| {
14935 (movement::next_subword_end(map, head), SelectionGoal::None)
14936 });
14937 })
14938 }
14939
14940 pub fn select_to_next_word_end(
14941 &mut self,
14942 _: &SelectToNextWordEnd,
14943 window: &mut Window,
14944 cx: &mut Context<Self>,
14945 ) {
14946 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14947 self.change_selections(Default::default(), window, cx, |s| {
14948 s.move_heads_with(&mut |map, head, _| {
14949 (movement::next_word_end(map, head), SelectionGoal::None)
14950 });
14951 })
14952 }
14953
14954 pub fn select_to_next_subword_end(
14955 &mut self,
14956 _: &SelectToNextSubwordEnd,
14957 window: &mut Window,
14958 cx: &mut Context<Self>,
14959 ) {
14960 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14961 self.change_selections(Default::default(), window, cx, |s| {
14962 s.move_heads_with(&mut |map, head, _| {
14963 (movement::next_subword_end(map, head), SelectionGoal::None)
14964 });
14965 })
14966 }
14967
14968 pub fn delete_to_next_word_end(
14969 &mut self,
14970 action: &DeleteToNextWordEnd,
14971 window: &mut Window,
14972 cx: &mut Context<Self>,
14973 ) {
14974 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14975 self.transact(window, cx, |this, window, cx| {
14976 this.change_selections(Default::default(), window, cx, |s| {
14977 s.move_with(&mut |map, selection| {
14978 if selection.is_empty() {
14979 let mut cursor = if action.ignore_newlines {
14980 movement::next_word_end(map, selection.head())
14981 } else {
14982 movement::next_word_end_or_newline(map, selection.head())
14983 };
14984 cursor = movement::adjust_greedy_deletion(
14985 map,
14986 selection.head(),
14987 cursor,
14988 action.ignore_brackets,
14989 );
14990 selection.set_head(cursor, SelectionGoal::None);
14991 }
14992 });
14993 });
14994 this.insert("", window, cx);
14995 });
14996 }
14997
14998 pub fn delete_to_next_subword_end(
14999 &mut self,
15000 action: &DeleteToNextSubwordEnd,
15001 window: &mut Window,
15002 cx: &mut Context<Self>,
15003 ) {
15004 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15005 self.transact(window, cx, |this, window, cx| {
15006 this.change_selections(Default::default(), window, cx, |s| {
15007 s.move_with(&mut |map, selection| {
15008 if selection.is_empty() {
15009 let mut cursor = if action.ignore_newlines {
15010 movement::next_subword_end(map, selection.head())
15011 } else {
15012 movement::next_subword_end_or_newline(map, selection.head())
15013 };
15014 cursor = movement::adjust_greedy_deletion(
15015 map,
15016 selection.head(),
15017 cursor,
15018 action.ignore_brackets,
15019 );
15020 selection.set_head(cursor, SelectionGoal::None);
15021 }
15022 });
15023 });
15024 this.insert("", window, cx);
15025 });
15026 }
15027
15028 pub fn move_to_beginning_of_line(
15029 &mut self,
15030 action: &MoveToBeginningOfLine,
15031 window: &mut Window,
15032 cx: &mut Context<Self>,
15033 ) {
15034 let stop_at_indent = action.stop_at_indent && !self.mode.is_single_line();
15035 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15036 self.change_selections(Default::default(), window, cx, |s| {
15037 s.move_cursors_with(&mut |map, head, _| {
15038 (
15039 movement::indented_line_beginning(
15040 map,
15041 head,
15042 action.stop_at_soft_wraps,
15043 stop_at_indent,
15044 ),
15045 SelectionGoal::None,
15046 )
15047 });
15048 })
15049 }
15050
15051 pub fn select_to_beginning_of_line(
15052 &mut self,
15053 action: &SelectToBeginningOfLine,
15054 window: &mut Window,
15055 cx: &mut Context<Self>,
15056 ) {
15057 let stop_at_indent = action.stop_at_indent && !self.mode.is_single_line();
15058 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15059 self.change_selections(Default::default(), window, cx, |s| {
15060 s.move_heads_with(&mut |map, head, _| {
15061 (
15062 movement::indented_line_beginning(
15063 map,
15064 head,
15065 action.stop_at_soft_wraps,
15066 stop_at_indent,
15067 ),
15068 SelectionGoal::None,
15069 )
15070 });
15071 });
15072 }
15073
15074 pub fn delete_to_beginning_of_line(
15075 &mut self,
15076 action: &DeleteToBeginningOfLine,
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 |_, selection| {
15084 selection.reversed = true;
15085 });
15086 });
15087
15088 this.select_to_beginning_of_line(
15089 &SelectToBeginningOfLine {
15090 stop_at_soft_wraps: false,
15091 stop_at_indent: action.stop_at_indent,
15092 },
15093 window,
15094 cx,
15095 );
15096 this.backspace(&Backspace, window, cx);
15097 });
15098 }
15099
15100 pub fn move_to_end_of_line(
15101 &mut self,
15102 action: &MoveToEndOfLine,
15103 window: &mut Window,
15104 cx: &mut Context<Self>,
15105 ) {
15106 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15107 self.change_selections(Default::default(), window, cx, |s| {
15108 s.move_cursors_with(&mut |map, head, _| {
15109 (
15110 movement::line_end(map, head, action.stop_at_soft_wraps),
15111 SelectionGoal::None,
15112 )
15113 });
15114 })
15115 }
15116
15117 pub fn select_to_end_of_line(
15118 &mut self,
15119 action: &SelectToEndOfLine,
15120 window: &mut Window,
15121 cx: &mut Context<Self>,
15122 ) {
15123 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15124 self.change_selections(Default::default(), window, cx, |s| {
15125 s.move_heads_with(&mut |map, head, _| {
15126 (
15127 movement::line_end(map, head, action.stop_at_soft_wraps),
15128 SelectionGoal::None,
15129 )
15130 });
15131 })
15132 }
15133
15134 pub fn delete_to_end_of_line(
15135 &mut self,
15136 _: &DeleteToEndOfLine,
15137 window: &mut Window,
15138 cx: &mut Context<Self>,
15139 ) {
15140 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15141 self.transact(window, cx, |this, window, cx| {
15142 this.select_to_end_of_line(
15143 &SelectToEndOfLine {
15144 stop_at_soft_wraps: false,
15145 },
15146 window,
15147 cx,
15148 );
15149 this.delete(&Delete, window, cx);
15150 });
15151 }
15152
15153 pub fn cut_to_end_of_line(
15154 &mut self,
15155 action: &CutToEndOfLine,
15156 window: &mut Window,
15157 cx: &mut Context<Self>,
15158 ) {
15159 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15160 self.transact(window, cx, |this, window, cx| {
15161 this.select_to_end_of_line(
15162 &SelectToEndOfLine {
15163 stop_at_soft_wraps: false,
15164 },
15165 window,
15166 cx,
15167 );
15168 if !action.stop_at_newlines {
15169 this.change_selections(Default::default(), window, cx, |s| {
15170 s.move_with(&mut |_, sel| {
15171 if sel.is_empty() {
15172 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
15173 }
15174 });
15175 });
15176 }
15177 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15178 let item = this.cut_common(false, window, cx);
15179 cx.write_to_clipboard(item);
15180 });
15181 }
15182
15183 pub fn move_to_start_of_paragraph(
15184 &mut self,
15185 _: &MoveToStartOfParagraph,
15186 window: &mut Window,
15187 cx: &mut Context<Self>,
15188 ) {
15189 if matches!(self.mode, EditorMode::SingleLine) {
15190 cx.propagate();
15191 return;
15192 }
15193 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15194 self.change_selections(Default::default(), window, cx, |s| {
15195 s.move_with(&mut |map, selection| {
15196 selection.collapse_to(
15197 movement::start_of_paragraph(map, selection.head(), 1),
15198 SelectionGoal::None,
15199 )
15200 });
15201 })
15202 }
15203
15204 pub fn move_to_end_of_paragraph(
15205 &mut self,
15206 _: &MoveToEndOfParagraph,
15207 window: &mut Window,
15208 cx: &mut Context<Self>,
15209 ) {
15210 if matches!(self.mode, EditorMode::SingleLine) {
15211 cx.propagate();
15212 return;
15213 }
15214 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15215 self.change_selections(Default::default(), window, cx, |s| {
15216 s.move_with(&mut |map, selection| {
15217 selection.collapse_to(
15218 movement::end_of_paragraph(map, selection.head(), 1),
15219 SelectionGoal::None,
15220 )
15221 });
15222 })
15223 }
15224
15225 pub fn select_to_start_of_paragraph(
15226 &mut self,
15227 _: &SelectToStartOfParagraph,
15228 window: &mut Window,
15229 cx: &mut Context<Self>,
15230 ) {
15231 if matches!(self.mode, EditorMode::SingleLine) {
15232 cx.propagate();
15233 return;
15234 }
15235 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15236 self.change_selections(Default::default(), window, cx, |s| {
15237 s.move_heads_with(&mut |map, head, _| {
15238 (
15239 movement::start_of_paragraph(map, head, 1),
15240 SelectionGoal::None,
15241 )
15242 });
15243 })
15244 }
15245
15246 pub fn select_to_end_of_paragraph(
15247 &mut self,
15248 _: &SelectToEndOfParagraph,
15249 window: &mut Window,
15250 cx: &mut Context<Self>,
15251 ) {
15252 if matches!(self.mode, EditorMode::SingleLine) {
15253 cx.propagate();
15254 return;
15255 }
15256 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15257 self.change_selections(Default::default(), window, cx, |s| {
15258 s.move_heads_with(&mut |map, head, _| {
15259 (
15260 movement::end_of_paragraph(map, head, 1),
15261 SelectionGoal::None,
15262 )
15263 });
15264 })
15265 }
15266
15267 pub fn move_to_start_of_excerpt(
15268 &mut self,
15269 _: &MoveToStartOfExcerpt,
15270 window: &mut Window,
15271 cx: &mut Context<Self>,
15272 ) {
15273 if matches!(self.mode, EditorMode::SingleLine) {
15274 cx.propagate();
15275 return;
15276 }
15277 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15278 self.change_selections(Default::default(), window, cx, |s| {
15279 s.move_with(&mut |map, selection| {
15280 selection.collapse_to(
15281 movement::start_of_excerpt(
15282 map,
15283 selection.head(),
15284 workspace::searchable::Direction::Prev,
15285 ),
15286 SelectionGoal::None,
15287 )
15288 });
15289 })
15290 }
15291
15292 pub fn move_to_start_of_next_excerpt(
15293 &mut self,
15294 _: &MoveToStartOfNextExcerpt,
15295 window: &mut Window,
15296 cx: &mut Context<Self>,
15297 ) {
15298 if matches!(self.mode, EditorMode::SingleLine) {
15299 cx.propagate();
15300 return;
15301 }
15302
15303 self.change_selections(Default::default(), window, cx, |s| {
15304 s.move_with(&mut |map, selection| {
15305 selection.collapse_to(
15306 movement::start_of_excerpt(
15307 map,
15308 selection.head(),
15309 workspace::searchable::Direction::Next,
15310 ),
15311 SelectionGoal::None,
15312 )
15313 });
15314 })
15315 }
15316
15317 pub fn move_to_end_of_excerpt(
15318 &mut self,
15319 _: &MoveToEndOfExcerpt,
15320 window: &mut Window,
15321 cx: &mut Context<Self>,
15322 ) {
15323 if matches!(self.mode, EditorMode::SingleLine) {
15324 cx.propagate();
15325 return;
15326 }
15327 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15328 self.change_selections(Default::default(), window, cx, |s| {
15329 s.move_with(&mut |map, selection| {
15330 selection.collapse_to(
15331 movement::end_of_excerpt(
15332 map,
15333 selection.head(),
15334 workspace::searchable::Direction::Next,
15335 ),
15336 SelectionGoal::None,
15337 )
15338 });
15339 })
15340 }
15341
15342 pub fn move_to_end_of_previous_excerpt(
15343 &mut self,
15344 _: &MoveToEndOfPreviousExcerpt,
15345 window: &mut Window,
15346 cx: &mut Context<Self>,
15347 ) {
15348 if matches!(self.mode, EditorMode::SingleLine) {
15349 cx.propagate();
15350 return;
15351 }
15352 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15353 self.change_selections(Default::default(), window, cx, |s| {
15354 s.move_with(&mut |map, selection| {
15355 selection.collapse_to(
15356 movement::end_of_excerpt(
15357 map,
15358 selection.head(),
15359 workspace::searchable::Direction::Prev,
15360 ),
15361 SelectionGoal::None,
15362 )
15363 });
15364 })
15365 }
15366
15367 pub fn select_to_start_of_excerpt(
15368 &mut self,
15369 _: &SelectToStartOfExcerpt,
15370 window: &mut Window,
15371 cx: &mut Context<Self>,
15372 ) {
15373 if matches!(self.mode, EditorMode::SingleLine) {
15374 cx.propagate();
15375 return;
15376 }
15377 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15378 self.change_selections(Default::default(), window, cx, |s| {
15379 s.move_heads_with(&mut |map, head, _| {
15380 (
15381 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15382 SelectionGoal::None,
15383 )
15384 });
15385 })
15386 }
15387
15388 pub fn select_to_start_of_next_excerpt(
15389 &mut self,
15390 _: &SelectToStartOfNextExcerpt,
15391 window: &mut Window,
15392 cx: &mut Context<Self>,
15393 ) {
15394 if matches!(self.mode, EditorMode::SingleLine) {
15395 cx.propagate();
15396 return;
15397 }
15398 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15399 self.change_selections(Default::default(), window, cx, |s| {
15400 s.move_heads_with(&mut |map, head, _| {
15401 (
15402 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
15403 SelectionGoal::None,
15404 )
15405 });
15406 })
15407 }
15408
15409 pub fn select_to_end_of_excerpt(
15410 &mut self,
15411 _: &SelectToEndOfExcerpt,
15412 window: &mut Window,
15413 cx: &mut Context<Self>,
15414 ) {
15415 if matches!(self.mode, EditorMode::SingleLine) {
15416 cx.propagate();
15417 return;
15418 }
15419 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15420 self.change_selections(Default::default(), window, cx, |s| {
15421 s.move_heads_with(&mut |map, head, _| {
15422 (
15423 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
15424 SelectionGoal::None,
15425 )
15426 });
15427 })
15428 }
15429
15430 pub fn select_to_end_of_previous_excerpt(
15431 &mut self,
15432 _: &SelectToEndOfPreviousExcerpt,
15433 window: &mut Window,
15434 cx: &mut Context<Self>,
15435 ) {
15436 if matches!(self.mode, EditorMode::SingleLine) {
15437 cx.propagate();
15438 return;
15439 }
15440 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15441 self.change_selections(Default::default(), window, cx, |s| {
15442 s.move_heads_with(&mut |map, head, _| {
15443 (
15444 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15445 SelectionGoal::None,
15446 )
15447 });
15448 })
15449 }
15450
15451 pub fn move_to_beginning(
15452 &mut self,
15453 _: &MoveToBeginning,
15454 window: &mut Window,
15455 cx: &mut Context<Self>,
15456 ) {
15457 if matches!(self.mode, EditorMode::SingleLine) {
15458 cx.propagate();
15459 return;
15460 }
15461 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15462 self.change_selections(Default::default(), window, cx, |s| {
15463 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
15464 });
15465 }
15466
15467 pub fn select_to_beginning(
15468 &mut self,
15469 _: &SelectToBeginning,
15470 window: &mut Window,
15471 cx: &mut Context<Self>,
15472 ) {
15473 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
15474 selection.set_head(Point::zero(), SelectionGoal::None);
15475 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15476 self.change_selections(Default::default(), window, cx, |s| {
15477 s.select(vec![selection]);
15478 });
15479 }
15480
15481 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
15482 if matches!(self.mode, EditorMode::SingleLine) {
15483 cx.propagate();
15484 return;
15485 }
15486 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15487 let cursor = self.buffer.read(cx).read(cx).len();
15488 self.change_selections(Default::default(), window, cx, |s| {
15489 s.select_ranges(vec![cursor..cursor])
15490 });
15491 }
15492
15493 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
15494 self.nav_history = nav_history;
15495 }
15496
15497 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
15498 self.nav_history.as_ref()
15499 }
15500
15501 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
15502 self.push_to_nav_history(
15503 self.selections.newest_anchor().head(),
15504 None,
15505 false,
15506 true,
15507 cx,
15508 );
15509 }
15510
15511 fn navigation_data(&self, cursor_anchor: Anchor, cx: &mut Context<Self>) -> NavigationData {
15512 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15513 let buffer = self.buffer.read(cx).read(cx);
15514 let cursor_position = cursor_anchor.to_point(&buffer);
15515 let scroll_anchor = self.scroll_manager.native_anchor(&display_snapshot, cx);
15516 let scroll_top_row = scroll_anchor.top_row(&buffer);
15517 drop(buffer);
15518
15519 NavigationData {
15520 cursor_anchor,
15521 cursor_position,
15522 scroll_anchor,
15523 scroll_top_row,
15524 }
15525 }
15526
15527 fn navigation_entry(
15528 &self,
15529 cursor_anchor: Anchor,
15530 cx: &mut Context<Self>,
15531 ) -> Option<NavigationEntry> {
15532 let Some(history) = self.nav_history.clone() else {
15533 return None;
15534 };
15535 let data = self.navigation_data(cursor_anchor, cx);
15536 Some(history.navigation_entry(Some(Arc::new(data) as Arc<dyn Any + Send + Sync>)))
15537 }
15538
15539 fn push_to_nav_history(
15540 &mut self,
15541 cursor_anchor: Anchor,
15542 new_position: Option<Point>,
15543 is_deactivate: bool,
15544 always: bool,
15545 cx: &mut Context<Self>,
15546 ) {
15547 let data = self.navigation_data(cursor_anchor, cx);
15548 if let Some(nav_history) = self.nav_history.as_mut() {
15549 if let Some(new_position) = new_position {
15550 let row_delta = (new_position.row as i64 - data.cursor_position.row as i64).abs();
15551 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
15552 return;
15553 }
15554 }
15555
15556 let cursor_row = data.cursor_position.row;
15557 nav_history.push(Some(data), Some(cursor_row), cx);
15558 cx.emit(EditorEvent::PushedToNavHistory {
15559 anchor: cursor_anchor,
15560 is_deactivate,
15561 })
15562 }
15563 }
15564
15565 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
15566 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15567 let buffer = self.buffer.read(cx).snapshot(cx);
15568 let mut selection = self
15569 .selections
15570 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
15571 selection.set_head(buffer.len(), SelectionGoal::None);
15572 self.change_selections(Default::default(), window, cx, |s| {
15573 s.select(vec![selection]);
15574 });
15575 }
15576
15577 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
15578 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15579 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15580 s.select_ranges([Anchor::min()..Anchor::max()]);
15581 });
15582 }
15583
15584 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
15585 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15586 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15587 let mut selections = self.selections.all::<Point>(&display_map);
15588 let max_point = display_map.buffer_snapshot().max_point();
15589 for selection in &mut selections {
15590 let rows = selection.spanned_rows(true, &display_map);
15591 selection.start = Point::new(rows.start.0, 0);
15592 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
15593 selection.reversed = false;
15594 }
15595 self.change_selections(Default::default(), window, cx, |s| {
15596 s.select(selections);
15597 });
15598 }
15599
15600 pub fn split_selection_into_lines(
15601 &mut self,
15602 action: &SplitSelectionIntoLines,
15603 window: &mut Window,
15604 cx: &mut Context<Self>,
15605 ) {
15606 let selections = self
15607 .selections
15608 .all::<Point>(&self.display_snapshot(cx))
15609 .into_iter()
15610 .map(|selection| selection.start..selection.end)
15611 .collect::<Vec<_>>();
15612 self.unfold_ranges(&selections, true, false, cx);
15613
15614 let mut new_selection_ranges = Vec::new();
15615 {
15616 let buffer = self.buffer.read(cx).read(cx);
15617 for selection in selections {
15618 for row in selection.start.row..selection.end.row {
15619 let line_start = Point::new(row, 0);
15620 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
15621
15622 if action.keep_selections {
15623 // Keep the selection range for each line
15624 let selection_start = if row == selection.start.row {
15625 selection.start
15626 } else {
15627 line_start
15628 };
15629 new_selection_ranges.push(selection_start..line_end);
15630 } else {
15631 // Collapse to cursor at end of line
15632 new_selection_ranges.push(line_end..line_end);
15633 }
15634 }
15635
15636 let is_multiline_selection = selection.start.row != selection.end.row;
15637 // Don't insert last one if it's a multi-line selection ending at the start of a line,
15638 // so this action feels more ergonomic when paired with other selection operations
15639 let should_skip_last = is_multiline_selection && selection.end.column == 0;
15640 if !should_skip_last {
15641 if action.keep_selections {
15642 if is_multiline_selection {
15643 let line_start = Point::new(selection.end.row, 0);
15644 new_selection_ranges.push(line_start..selection.end);
15645 } else {
15646 new_selection_ranges.push(selection.start..selection.end);
15647 }
15648 } else {
15649 new_selection_ranges.push(selection.end..selection.end);
15650 }
15651 }
15652 }
15653 }
15654 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15655 s.select_ranges(new_selection_ranges);
15656 });
15657 }
15658
15659 pub fn add_selection_above(
15660 &mut self,
15661 action: &AddSelectionAbove,
15662 window: &mut Window,
15663 cx: &mut Context<Self>,
15664 ) {
15665 self.add_selection(true, action.skip_soft_wrap, window, cx);
15666 }
15667
15668 pub fn add_selection_below(
15669 &mut self,
15670 action: &AddSelectionBelow,
15671 window: &mut Window,
15672 cx: &mut Context<Self>,
15673 ) {
15674 self.add_selection(false, action.skip_soft_wrap, window, cx);
15675 }
15676
15677 fn add_selection(
15678 &mut self,
15679 above: bool,
15680 skip_soft_wrap: bool,
15681 window: &mut Window,
15682 cx: &mut Context<Self>,
15683 ) {
15684 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15685
15686 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15687 let all_selections = self.selections.all::<Point>(&display_map);
15688 let text_layout_details = self.text_layout_details(window, cx);
15689
15690 let (mut columnar_selections, new_selections_to_columnarize) = {
15691 if let Some(state) = self.add_selections_state.as_ref() {
15692 let columnar_selection_ids: HashSet<_> = state
15693 .groups
15694 .iter()
15695 .flat_map(|group| group.stack.iter())
15696 .copied()
15697 .collect();
15698
15699 all_selections
15700 .into_iter()
15701 .partition(|s| columnar_selection_ids.contains(&s.id))
15702 } else {
15703 (Vec::new(), all_selections)
15704 }
15705 };
15706
15707 let mut state = self
15708 .add_selections_state
15709 .take()
15710 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
15711
15712 for selection in new_selections_to_columnarize {
15713 let range = selection.display_range(&display_map).sorted();
15714 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
15715 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
15716 let positions = start_x.min(end_x)..start_x.max(end_x);
15717 let mut stack = Vec::new();
15718 for row in range.start.row().0..=range.end.row().0 {
15719 if let Some(selection) = self.selections.build_columnar_selection(
15720 &display_map,
15721 DisplayRow(row),
15722 &positions,
15723 selection.reversed,
15724 &text_layout_details,
15725 ) {
15726 stack.push(selection.id);
15727 columnar_selections.push(selection);
15728 }
15729 }
15730 if !stack.is_empty() {
15731 if above {
15732 stack.reverse();
15733 }
15734 state.groups.push(AddSelectionsGroup { above, stack });
15735 }
15736 }
15737
15738 let mut final_selections = Vec::new();
15739 let end_row = if above {
15740 DisplayRow(0)
15741 } else {
15742 display_map.max_point().row()
15743 };
15744
15745 // When `skip_soft_wrap` is true, we use UTF-16 columns instead of pixel
15746 // positions to place new selections, so we need to keep track of the
15747 // column range of the oldest selection in each group, because
15748 // intermediate selections may have been clamped to shorter lines.
15749 let mut goal_columns_by_selection_id = if skip_soft_wrap {
15750 let mut map = HashMap::default();
15751 for group in state.groups.iter() {
15752 if let Some(oldest_id) = group.stack.first() {
15753 if let Some(oldest_selection) =
15754 columnar_selections.iter().find(|s| s.id == *oldest_id)
15755 {
15756 let snapshot = display_map.buffer_snapshot();
15757 let start_col =
15758 snapshot.point_to_point_utf16(oldest_selection.start).column;
15759 let end_col = snapshot.point_to_point_utf16(oldest_selection.end).column;
15760 let goal_columns = start_col.min(end_col)..start_col.max(end_col);
15761 for id in &group.stack {
15762 map.insert(*id, goal_columns.clone());
15763 }
15764 }
15765 }
15766 }
15767 map
15768 } else {
15769 HashMap::default()
15770 };
15771
15772 let mut last_added_item_per_group = HashMap::default();
15773 for group in state.groups.iter_mut() {
15774 if let Some(last_id) = group.stack.last() {
15775 last_added_item_per_group.insert(*last_id, group);
15776 }
15777 }
15778
15779 for selection in columnar_selections {
15780 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
15781 if above == group.above {
15782 let range = selection.display_range(&display_map).sorted();
15783 debug_assert_eq!(range.start.row(), range.end.row());
15784 let row = range.start.row();
15785 let positions =
15786 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
15787 Pixels::from(start)..Pixels::from(end)
15788 } else {
15789 let start_x =
15790 display_map.x_for_display_point(range.start, &text_layout_details);
15791 let end_x =
15792 display_map.x_for_display_point(range.end, &text_layout_details);
15793 start_x.min(end_x)..start_x.max(end_x)
15794 };
15795
15796 let maybe_new_selection = if skip_soft_wrap {
15797 let goal_columns = goal_columns_by_selection_id
15798 .remove(&selection.id)
15799 .unwrap_or_else(|| {
15800 let snapshot = display_map.buffer_snapshot();
15801 let start_col =
15802 snapshot.point_to_point_utf16(selection.start).column;
15803 let end_col = snapshot.point_to_point_utf16(selection.end).column;
15804 start_col.min(end_col)..start_col.max(end_col)
15805 });
15806 self.selections.find_next_columnar_selection_by_buffer_row(
15807 &display_map,
15808 row,
15809 end_row,
15810 above,
15811 &goal_columns,
15812 selection.reversed,
15813 &text_layout_details,
15814 )
15815 } else {
15816 self.selections.find_next_columnar_selection_by_display_row(
15817 &display_map,
15818 row,
15819 end_row,
15820 above,
15821 &positions,
15822 selection.reversed,
15823 &text_layout_details,
15824 )
15825 };
15826
15827 if let Some(new_selection) = maybe_new_selection {
15828 group.stack.push(new_selection.id);
15829 if above {
15830 final_selections.push(new_selection);
15831 final_selections.push(selection);
15832 } else {
15833 final_selections.push(selection);
15834 final_selections.push(new_selection);
15835 }
15836 } else {
15837 final_selections.push(selection);
15838 }
15839 } else {
15840 group.stack.pop();
15841 }
15842 } else {
15843 final_selections.push(selection);
15844 }
15845 }
15846
15847 self.change_selections(Default::default(), window, cx, |s| {
15848 s.select(final_selections);
15849 });
15850
15851 let final_selection_ids: HashSet<_> = self
15852 .selections
15853 .all::<Point>(&display_map)
15854 .iter()
15855 .map(|s| s.id)
15856 .collect();
15857 state.groups.retain_mut(|group| {
15858 // selections might get merged above so we remove invalid items from stacks
15859 group.stack.retain(|id| final_selection_ids.contains(id));
15860
15861 // single selection in stack can be treated as initial state
15862 group.stack.len() > 1
15863 });
15864
15865 if !state.groups.is_empty() {
15866 self.add_selections_state = Some(state);
15867 }
15868 }
15869
15870 pub fn insert_snippet_at_selections(
15871 &mut self,
15872 action: &InsertSnippet,
15873 window: &mut Window,
15874 cx: &mut Context<Self>,
15875 ) {
15876 self.try_insert_snippet_at_selections(action, window, cx)
15877 .log_err();
15878 }
15879
15880 fn try_insert_snippet_at_selections(
15881 &mut self,
15882 action: &InsertSnippet,
15883 window: &mut Window,
15884 cx: &mut Context<Self>,
15885 ) -> Result<()> {
15886 let insertion_ranges = self
15887 .selections
15888 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15889 .into_iter()
15890 .map(|selection| selection.range())
15891 .collect_vec();
15892
15893 let snippet = if let Some(snippet_body) = &action.snippet {
15894 if action.language.is_none() && action.name.is_none() {
15895 Snippet::parse(snippet_body)?
15896 } else {
15897 bail!("`snippet` is mutually exclusive with `language` and `name`")
15898 }
15899 } else if let Some(name) = &action.name {
15900 let project = self.project().context("no project")?;
15901 let snippet_store = project.read(cx).snippets().read(cx);
15902 let snippet = snippet_store
15903 .snippets_for(action.language.clone(), cx)
15904 .into_iter()
15905 .find(|snippet| snippet.name == *name)
15906 .context("snippet not found")?;
15907 Snippet::parse(&snippet.body)?
15908 } else {
15909 // todo(andrew): open modal to select snippet
15910 bail!("`name` or `snippet` is required")
15911 };
15912
15913 self.insert_snippet(&insertion_ranges, snippet, window, cx)
15914 }
15915
15916 fn select_match_ranges(
15917 &mut self,
15918 range: Range<MultiBufferOffset>,
15919 reversed: bool,
15920 replace_newest: bool,
15921 auto_scroll: Option<Autoscroll>,
15922 window: &mut Window,
15923 cx: &mut Context<Editor>,
15924 ) {
15925 self.unfold_ranges(
15926 std::slice::from_ref(&range),
15927 false,
15928 auto_scroll.is_some(),
15929 cx,
15930 );
15931 let effects = if let Some(scroll) = auto_scroll {
15932 SelectionEffects::scroll(scroll)
15933 } else {
15934 SelectionEffects::no_scroll()
15935 };
15936 self.change_selections(effects, window, cx, |s| {
15937 if replace_newest {
15938 s.delete(s.newest_anchor().id);
15939 }
15940 if reversed {
15941 s.insert_range(range.end..range.start);
15942 } else {
15943 s.insert_range(range);
15944 }
15945 });
15946 }
15947
15948 pub fn select_next_match_internal(
15949 &mut self,
15950 display_map: &DisplaySnapshot,
15951 replace_newest: bool,
15952 autoscroll: Option<Autoscroll>,
15953 window: &mut Window,
15954 cx: &mut Context<Self>,
15955 ) -> Result<()> {
15956 let buffer = display_map.buffer_snapshot();
15957 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15958 if let Some(mut select_next_state) = self.select_next_state.take() {
15959 let query = &select_next_state.query;
15960 if !select_next_state.done {
15961 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15962 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15963 let mut next_selected_range = None;
15964
15965 let bytes_after_last_selection =
15966 buffer.bytes_in_range(last_selection.end..buffer.len());
15967 let bytes_before_first_selection =
15968 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
15969 let query_matches = query
15970 .stream_find_iter(bytes_after_last_selection)
15971 .map(|result| (last_selection.end, result))
15972 .chain(
15973 query
15974 .stream_find_iter(bytes_before_first_selection)
15975 .map(|result| (MultiBufferOffset(0), result)),
15976 );
15977
15978 for (start_offset, query_match) in query_matches {
15979 let query_match = query_match.unwrap(); // can only fail due to I/O
15980 let offset_range =
15981 start_offset + query_match.start()..start_offset + query_match.end();
15982
15983 if !select_next_state.wordwise
15984 || (!buffer.is_inside_word(offset_range.start, None)
15985 && !buffer.is_inside_word(offset_range.end, None))
15986 {
15987 let idx = selections
15988 .partition_point(|selection| selection.end <= offset_range.start);
15989 let overlaps = selections
15990 .get(idx)
15991 .map_or(false, |selection| selection.start < offset_range.end);
15992
15993 if !overlaps {
15994 next_selected_range = Some(offset_range);
15995 break;
15996 }
15997 }
15998 }
15999
16000 if let Some(next_selected_range) = next_selected_range {
16001 self.select_match_ranges(
16002 next_selected_range,
16003 last_selection.reversed,
16004 replace_newest,
16005 autoscroll,
16006 window,
16007 cx,
16008 );
16009 } else {
16010 select_next_state.done = true;
16011 }
16012 }
16013
16014 self.select_next_state = Some(select_next_state);
16015 } else {
16016 let mut only_carets = true;
16017 let mut same_text_selected = true;
16018 let mut selected_text = None;
16019
16020 let mut selections_iter = selections.iter().peekable();
16021 while let Some(selection) = selections_iter.next() {
16022 if selection.start != selection.end {
16023 only_carets = false;
16024 }
16025
16026 if same_text_selected {
16027 if selected_text.is_none() {
16028 selected_text =
16029 Some(buffer.text_for_range(selection.range()).collect::<String>());
16030 }
16031
16032 if let Some(next_selection) = selections_iter.peek() {
16033 if next_selection.len() == selection.len() {
16034 let next_selected_text = buffer
16035 .text_for_range(next_selection.range())
16036 .collect::<String>();
16037 if Some(next_selected_text) != selected_text {
16038 same_text_selected = false;
16039 selected_text = None;
16040 }
16041 } else {
16042 same_text_selected = false;
16043 selected_text = None;
16044 }
16045 }
16046 }
16047 }
16048
16049 if only_carets {
16050 for selection in &mut selections {
16051 let (word_range, _) = buffer.surrounding_word(selection.start, None);
16052 selection.start = word_range.start;
16053 selection.end = word_range.end;
16054 selection.goal = SelectionGoal::None;
16055 selection.reversed = false;
16056 self.select_match_ranges(
16057 selection.start..selection.end,
16058 selection.reversed,
16059 replace_newest,
16060 autoscroll,
16061 window,
16062 cx,
16063 );
16064 }
16065
16066 if selections.len() == 1 {
16067 let selection = selections
16068 .last()
16069 .expect("ensured that there's only one selection");
16070 let query = buffer
16071 .text_for_range(selection.start..selection.end)
16072 .collect::<String>();
16073 let is_empty = query.is_empty();
16074 let select_state = SelectNextState {
16075 query: self.build_query(&[query], cx)?,
16076 wordwise: true,
16077 done: is_empty,
16078 };
16079 self.select_next_state = Some(select_state);
16080 } else {
16081 self.select_next_state = None;
16082 }
16083 } else if let Some(selected_text) = selected_text {
16084 self.select_next_state = Some(SelectNextState {
16085 query: self.build_query(&[selected_text], cx)?,
16086 wordwise: false,
16087 done: false,
16088 });
16089 self.select_next_match_internal(
16090 display_map,
16091 replace_newest,
16092 autoscroll,
16093 window,
16094 cx,
16095 )?;
16096 }
16097 }
16098 Ok(())
16099 }
16100
16101 pub fn select_all_matches(
16102 &mut self,
16103 _action: &SelectAllMatches,
16104 window: &mut Window,
16105 cx: &mut Context<Self>,
16106 ) -> Result<()> {
16107 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16108
16109 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16110
16111 self.select_next_match_internal(&display_map, false, None, window, cx)?;
16112 let Some(select_next_state) = self.select_next_state.as_mut().filter(|state| !state.done)
16113 else {
16114 return Ok(());
16115 };
16116
16117 let mut new_selections = Vec::new();
16118
16119 let reversed = self
16120 .selections
16121 .oldest::<MultiBufferOffset>(&display_map)
16122 .reversed;
16123 let buffer = display_map.buffer_snapshot();
16124 let query_matches = select_next_state
16125 .query
16126 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
16127
16128 for query_match in query_matches.into_iter() {
16129 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
16130 let offset_range = if reversed {
16131 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
16132 } else {
16133 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
16134 };
16135
16136 if !select_next_state.wordwise
16137 || (!buffer.is_inside_word(offset_range.start, None)
16138 && !buffer.is_inside_word(offset_range.end, None))
16139 {
16140 new_selections.push(offset_range.start..offset_range.end);
16141 }
16142 }
16143
16144 select_next_state.done = true;
16145
16146 if new_selections.is_empty() {
16147 log::error!("bug: new_selections is empty in select_all_matches");
16148 return Ok(());
16149 }
16150
16151 self.unfold_ranges(&new_selections, false, false, cx);
16152 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
16153 selections.select_ranges(new_selections)
16154 });
16155
16156 Ok(())
16157 }
16158
16159 pub fn select_next(
16160 &mut self,
16161 action: &SelectNext,
16162 window: &mut Window,
16163 cx: &mut Context<Self>,
16164 ) -> Result<()> {
16165 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16166 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16167 self.select_next_match_internal(
16168 &display_map,
16169 action.replace_newest,
16170 Some(Autoscroll::newest()),
16171 window,
16172 cx,
16173 )
16174 }
16175
16176 pub fn select_previous(
16177 &mut self,
16178 action: &SelectPrevious,
16179 window: &mut Window,
16180 cx: &mut Context<Self>,
16181 ) -> Result<()> {
16182 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16183 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16184 let buffer = display_map.buffer_snapshot();
16185 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
16186 if let Some(mut select_prev_state) = self.select_prev_state.take() {
16187 let query = &select_prev_state.query;
16188 if !select_prev_state.done {
16189 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
16190 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
16191 let mut next_selected_range = None;
16192 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
16193 let bytes_before_last_selection =
16194 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
16195 let bytes_after_first_selection =
16196 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
16197 let query_matches = query
16198 .stream_find_iter(bytes_before_last_selection)
16199 .map(|result| (last_selection.start, result))
16200 .chain(
16201 query
16202 .stream_find_iter(bytes_after_first_selection)
16203 .map(|result| (buffer.len(), result)),
16204 );
16205 for (end_offset, query_match) in query_matches {
16206 let query_match = query_match.unwrap(); // can only fail due to I/O
16207 let offset_range =
16208 end_offset - query_match.end()..end_offset - query_match.start();
16209
16210 if !select_prev_state.wordwise
16211 || (!buffer.is_inside_word(offset_range.start, None)
16212 && !buffer.is_inside_word(offset_range.end, None))
16213 {
16214 next_selected_range = Some(offset_range);
16215 break;
16216 }
16217 }
16218
16219 if let Some(next_selected_range) = next_selected_range {
16220 self.select_match_ranges(
16221 next_selected_range,
16222 last_selection.reversed,
16223 action.replace_newest,
16224 Some(Autoscroll::newest()),
16225 window,
16226 cx,
16227 );
16228 } else {
16229 select_prev_state.done = true;
16230 }
16231 }
16232
16233 self.select_prev_state = Some(select_prev_state);
16234 } else {
16235 let mut only_carets = true;
16236 let mut same_text_selected = true;
16237 let mut selected_text = None;
16238
16239 let mut selections_iter = selections.iter().peekable();
16240 while let Some(selection) = selections_iter.next() {
16241 if selection.start != selection.end {
16242 only_carets = false;
16243 }
16244
16245 if same_text_selected {
16246 if selected_text.is_none() {
16247 selected_text =
16248 Some(buffer.text_for_range(selection.range()).collect::<String>());
16249 }
16250
16251 if let Some(next_selection) = selections_iter.peek() {
16252 if next_selection.len() == selection.len() {
16253 let next_selected_text = buffer
16254 .text_for_range(next_selection.range())
16255 .collect::<String>();
16256 if Some(next_selected_text) != selected_text {
16257 same_text_selected = false;
16258 selected_text = None;
16259 }
16260 } else {
16261 same_text_selected = false;
16262 selected_text = None;
16263 }
16264 }
16265 }
16266 }
16267
16268 if only_carets {
16269 for selection in &mut selections {
16270 let (word_range, _) = buffer.surrounding_word(selection.start, None);
16271 selection.start = word_range.start;
16272 selection.end = word_range.end;
16273 selection.goal = SelectionGoal::None;
16274 selection.reversed = false;
16275 self.select_match_ranges(
16276 selection.start..selection.end,
16277 selection.reversed,
16278 action.replace_newest,
16279 Some(Autoscroll::newest()),
16280 window,
16281 cx,
16282 );
16283 }
16284 if selections.len() == 1 {
16285 let selection = selections
16286 .last()
16287 .expect("ensured that there's only one selection");
16288 let query = buffer
16289 .text_for_range(selection.start..selection.end)
16290 .collect::<String>();
16291 let is_empty = query.is_empty();
16292 let select_state = SelectNextState {
16293 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
16294 wordwise: true,
16295 done: is_empty,
16296 };
16297 self.select_prev_state = Some(select_state);
16298 } else {
16299 self.select_prev_state = None;
16300 }
16301 } else if let Some(selected_text) = selected_text {
16302 self.select_prev_state = Some(SelectNextState {
16303 query: self
16304 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
16305 wordwise: false,
16306 done: false,
16307 });
16308 self.select_previous(action, window, cx)?;
16309 }
16310 }
16311 Ok(())
16312 }
16313
16314 /// Builds an `AhoCorasick` automaton from the provided patterns, while
16315 /// setting the case sensitivity based on the global
16316 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
16317 /// editor's settings.
16318 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
16319 where
16320 I: IntoIterator<Item = P>,
16321 P: AsRef<[u8]>,
16322 {
16323 let case_sensitive = self
16324 .select_next_is_case_sensitive
16325 .unwrap_or_else(|| EditorSettings::get_global(cx).search.case_sensitive);
16326
16327 let mut builder = AhoCorasickBuilder::new();
16328 builder.ascii_case_insensitive(!case_sensitive);
16329 builder.build(patterns)
16330 }
16331
16332 pub fn find_next_match(
16333 &mut self,
16334 _: &FindNextMatch,
16335 window: &mut Window,
16336 cx: &mut Context<Self>,
16337 ) -> Result<()> {
16338 let selections = self.selections.disjoint_anchors_arc();
16339 match selections.first() {
16340 Some(first) if selections.len() >= 2 => {
16341 self.change_selections(Default::default(), window, cx, |s| {
16342 s.select_ranges([first.range()]);
16343 });
16344 }
16345 _ => self.select_next(
16346 &SelectNext {
16347 replace_newest: true,
16348 },
16349 window,
16350 cx,
16351 )?,
16352 }
16353 Ok(())
16354 }
16355
16356 pub fn find_previous_match(
16357 &mut self,
16358 _: &FindPreviousMatch,
16359 window: &mut Window,
16360 cx: &mut Context<Self>,
16361 ) -> Result<()> {
16362 let selections = self.selections.disjoint_anchors_arc();
16363 match selections.last() {
16364 Some(last) if selections.len() >= 2 => {
16365 self.change_selections(Default::default(), window, cx, |s| {
16366 s.select_ranges([last.range()]);
16367 });
16368 }
16369 _ => self.select_previous(
16370 &SelectPrevious {
16371 replace_newest: true,
16372 },
16373 window,
16374 cx,
16375 )?,
16376 }
16377 Ok(())
16378 }
16379
16380 pub fn toggle_comments(
16381 &mut self,
16382 action: &ToggleComments,
16383 window: &mut Window,
16384 cx: &mut Context<Self>,
16385 ) {
16386 if self.read_only(cx) {
16387 return;
16388 }
16389 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16390 let text_layout_details = &self.text_layout_details(window, cx);
16391 self.transact(window, cx, |this, window, cx| {
16392 let mut selections = this
16393 .selections
16394 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
16395 let mut edits = Vec::new();
16396 let mut selection_edit_ranges = Vec::new();
16397 let mut last_toggled_row = None;
16398 let snapshot = this.buffer.read(cx).read(cx);
16399 let empty_str: Arc<str> = Arc::default();
16400 let mut suffixes_inserted = Vec::new();
16401 let ignore_indent = action.ignore_indent;
16402
16403 fn comment_prefix_range(
16404 snapshot: &MultiBufferSnapshot,
16405 row: MultiBufferRow,
16406 comment_prefix: &str,
16407 comment_prefix_whitespace: &str,
16408 ignore_indent: bool,
16409 ) -> Range<Point> {
16410 let indent_size = if ignore_indent {
16411 0
16412 } else {
16413 snapshot.indent_size_for_line(row).len
16414 };
16415
16416 let start = Point::new(row.0, indent_size);
16417
16418 let mut line_bytes = snapshot
16419 .bytes_in_range(start..snapshot.max_point())
16420 .flatten()
16421 .copied();
16422
16423 // If this line currently begins with the line comment prefix, then record
16424 // the range containing the prefix.
16425 if line_bytes
16426 .by_ref()
16427 .take(comment_prefix.len())
16428 .eq(comment_prefix.bytes())
16429 {
16430 // Include any whitespace that matches the comment prefix.
16431 let matching_whitespace_len = line_bytes
16432 .zip(comment_prefix_whitespace.bytes())
16433 .take_while(|(a, b)| a == b)
16434 .count() as u32;
16435 let end = Point::new(
16436 start.row,
16437 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
16438 );
16439 start..end
16440 } else {
16441 start..start
16442 }
16443 }
16444
16445 fn comment_suffix_range(
16446 snapshot: &MultiBufferSnapshot,
16447 row: MultiBufferRow,
16448 comment_suffix: &str,
16449 comment_suffix_has_leading_space: bool,
16450 ) -> Range<Point> {
16451 let end = Point::new(row.0, snapshot.line_len(row));
16452 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
16453
16454 let mut line_end_bytes = snapshot
16455 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
16456 .flatten()
16457 .copied();
16458
16459 let leading_space_len = if suffix_start_column > 0
16460 && line_end_bytes.next() == Some(b' ')
16461 && comment_suffix_has_leading_space
16462 {
16463 1
16464 } else {
16465 0
16466 };
16467
16468 // If this line currently begins with the line comment prefix, then record
16469 // the range containing the prefix.
16470 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
16471 let start = Point::new(end.row, suffix_start_column - leading_space_len);
16472 start..end
16473 } else {
16474 end..end
16475 }
16476 }
16477
16478 // TODO: Handle selections that cross excerpts
16479 for selection in &mut selections {
16480 let start_column = snapshot
16481 .indent_size_for_line(MultiBufferRow(selection.start.row))
16482 .len;
16483 let language = if let Some(language) =
16484 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
16485 {
16486 language
16487 } else {
16488 continue;
16489 };
16490
16491 selection_edit_ranges.clear();
16492
16493 // If multiple selections contain a given row, avoid processing that
16494 // row more than once.
16495 let mut start_row = MultiBufferRow(selection.start.row);
16496 if last_toggled_row == Some(start_row) {
16497 start_row = start_row.next_row();
16498 }
16499 let end_row =
16500 if selection.end.row > selection.start.row && selection.end.column == 0 {
16501 MultiBufferRow(selection.end.row - 1)
16502 } else {
16503 MultiBufferRow(selection.end.row)
16504 };
16505 last_toggled_row = Some(end_row);
16506
16507 if start_row > end_row {
16508 continue;
16509 }
16510
16511 // If the language has line comments, toggle those.
16512 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
16513
16514 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
16515 if ignore_indent {
16516 full_comment_prefixes = full_comment_prefixes
16517 .into_iter()
16518 .map(|s| Arc::from(s.trim_end()))
16519 .collect();
16520 }
16521
16522 if !full_comment_prefixes.is_empty() {
16523 let first_prefix = full_comment_prefixes
16524 .first()
16525 .expect("prefixes is non-empty");
16526 let prefix_trimmed_lengths = full_comment_prefixes
16527 .iter()
16528 .map(|p| p.trim_end_matches(' ').len())
16529 .collect::<SmallVec<[usize; 4]>>();
16530
16531 let mut all_selection_lines_are_comments = true;
16532
16533 for row in start_row.0..=end_row.0 {
16534 let row = MultiBufferRow(row);
16535 if start_row < end_row && snapshot.is_line_blank(row) {
16536 continue;
16537 }
16538
16539 let prefix_range = full_comment_prefixes
16540 .iter()
16541 .zip(prefix_trimmed_lengths.iter().copied())
16542 .map(|(prefix, trimmed_prefix_len)| {
16543 comment_prefix_range(
16544 snapshot.deref(),
16545 row,
16546 &prefix[..trimmed_prefix_len],
16547 &prefix[trimmed_prefix_len..],
16548 ignore_indent,
16549 )
16550 })
16551 .max_by_key(|range| range.end.column - range.start.column)
16552 .expect("prefixes is non-empty");
16553
16554 if prefix_range.is_empty() {
16555 all_selection_lines_are_comments = false;
16556 }
16557
16558 selection_edit_ranges.push(prefix_range);
16559 }
16560
16561 if all_selection_lines_are_comments {
16562 edits.extend(
16563 selection_edit_ranges
16564 .iter()
16565 .cloned()
16566 .map(|range| (range, empty_str.clone())),
16567 );
16568 } else {
16569 let min_column = selection_edit_ranges
16570 .iter()
16571 .map(|range| range.start.column)
16572 .min()
16573 .unwrap_or(0);
16574 edits.extend(selection_edit_ranges.iter().map(|range| {
16575 let position = Point::new(range.start.row, min_column);
16576 (position..position, first_prefix.clone())
16577 }));
16578 }
16579 } else if let Some(BlockCommentConfig {
16580 start: full_comment_prefix,
16581 end: comment_suffix,
16582 ..
16583 }) = language.block_comment()
16584 {
16585 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
16586 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
16587 let prefix_range = comment_prefix_range(
16588 snapshot.deref(),
16589 start_row,
16590 comment_prefix,
16591 comment_prefix_whitespace,
16592 ignore_indent,
16593 );
16594 let suffix_range = comment_suffix_range(
16595 snapshot.deref(),
16596 end_row,
16597 comment_suffix.trim_start_matches(' '),
16598 comment_suffix.starts_with(' '),
16599 );
16600
16601 if prefix_range.is_empty() || suffix_range.is_empty() {
16602 edits.push((
16603 prefix_range.start..prefix_range.start,
16604 full_comment_prefix.clone(),
16605 ));
16606 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
16607 suffixes_inserted.push((end_row, comment_suffix.len()));
16608 } else {
16609 edits.push((prefix_range, empty_str.clone()));
16610 edits.push((suffix_range, empty_str.clone()));
16611 }
16612 } else {
16613 continue;
16614 }
16615 }
16616
16617 drop(snapshot);
16618 this.buffer.update(cx, |buffer, cx| {
16619 buffer.edit(edits, None, cx);
16620 });
16621
16622 // Adjust selections so that they end before any comment suffixes that
16623 // were inserted.
16624 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
16625 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16626 let snapshot = this.buffer.read(cx).read(cx);
16627 for selection in &mut selections {
16628 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
16629 match row.cmp(&MultiBufferRow(selection.end.row)) {
16630 Ordering::Less => {
16631 suffixes_inserted.next();
16632 continue;
16633 }
16634 Ordering::Greater => break,
16635 Ordering::Equal => {
16636 if selection.end.column == snapshot.line_len(row) {
16637 if selection.is_empty() {
16638 selection.start.column -= suffix_len as u32;
16639 }
16640 selection.end.column -= suffix_len as u32;
16641 }
16642 break;
16643 }
16644 }
16645 }
16646 }
16647
16648 drop(snapshot);
16649 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
16650
16651 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16652 let selections_on_single_row = selections.windows(2).all(|selections| {
16653 selections[0].start.row == selections[1].start.row
16654 && selections[0].end.row == selections[1].end.row
16655 && selections[0].start.row == selections[0].end.row
16656 });
16657 let selections_selecting = selections
16658 .iter()
16659 .any(|selection| selection.start != selection.end);
16660 let advance_downwards = action.advance_downwards
16661 && selections_on_single_row
16662 && !selections_selecting
16663 && !matches!(this.mode, EditorMode::SingleLine);
16664
16665 if advance_downwards {
16666 let snapshot = this.buffer.read(cx).snapshot(cx);
16667
16668 this.change_selections(Default::default(), window, cx, |s| {
16669 s.move_cursors_with(&mut |display_snapshot, display_point, _| {
16670 let mut point = display_point.to_point(display_snapshot);
16671 point.row += 1;
16672 point = snapshot.clip_point(point, Bias::Left);
16673 let display_point = point.to_display_point(display_snapshot);
16674 let goal = SelectionGoal::HorizontalPosition(
16675 display_snapshot
16676 .x_for_display_point(display_point, text_layout_details)
16677 .into(),
16678 );
16679 (display_point, goal)
16680 })
16681 });
16682 }
16683 });
16684 }
16685
16686 pub fn select_enclosing_symbol(
16687 &mut self,
16688 _: &SelectEnclosingSymbol,
16689 window: &mut Window,
16690 cx: &mut Context<Self>,
16691 ) {
16692 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16693
16694 let buffer = self.buffer.read(cx).snapshot(cx);
16695 let old_selections = self
16696 .selections
16697 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16698 .into_boxed_slice();
16699
16700 fn update_selection(
16701 selection: &Selection<MultiBufferOffset>,
16702 buffer_snap: &MultiBufferSnapshot,
16703 ) -> Option<Selection<MultiBufferOffset>> {
16704 let cursor = selection.head();
16705 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
16706 for symbol in symbols.iter().rev() {
16707 let start = symbol.range.start.to_offset(buffer_snap);
16708 let end = symbol.range.end.to_offset(buffer_snap);
16709 let new_range = start..end;
16710 if start < selection.start || end > selection.end {
16711 return Some(Selection {
16712 id: selection.id,
16713 start: new_range.start,
16714 end: new_range.end,
16715 goal: SelectionGoal::None,
16716 reversed: selection.reversed,
16717 });
16718 }
16719 }
16720 None
16721 }
16722
16723 let mut selected_larger_symbol = false;
16724 let new_selections = old_selections
16725 .iter()
16726 .map(|selection| match update_selection(selection, &buffer) {
16727 Some(new_selection) => {
16728 if new_selection.range() != selection.range() {
16729 selected_larger_symbol = true;
16730 }
16731 new_selection
16732 }
16733 None => selection.clone(),
16734 })
16735 .collect::<Vec<_>>();
16736
16737 if selected_larger_symbol {
16738 self.change_selections(Default::default(), window, cx, |s| {
16739 s.select(new_selections);
16740 });
16741 }
16742 }
16743
16744 pub fn select_larger_syntax_node(
16745 &mut self,
16746 _: &SelectLargerSyntaxNode,
16747 window: &mut Window,
16748 cx: &mut Context<Self>,
16749 ) {
16750 let Some(visible_row_count) = self.visible_row_count() else {
16751 return;
16752 };
16753 let old_selections: Box<[_]> = self
16754 .selections
16755 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16756 .into();
16757 if old_selections.is_empty() {
16758 return;
16759 }
16760
16761 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16762
16763 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16764 let buffer = self.buffer.read(cx).snapshot(cx);
16765
16766 let mut selected_larger_node = false;
16767 let mut new_selections = old_selections
16768 .iter()
16769 .map(|selection| {
16770 let old_range = selection.start..selection.end;
16771
16772 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
16773 // manually select word at selection
16774 if ["string_content", "inline"].contains(&node.kind()) {
16775 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
16776 // ignore if word is already selected
16777 if !word_range.is_empty() && old_range != word_range {
16778 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
16779 // only select word if start and end point belongs to same word
16780 if word_range == last_word_range {
16781 selected_larger_node = true;
16782 return Selection {
16783 id: selection.id,
16784 start: word_range.start,
16785 end: word_range.end,
16786 goal: SelectionGoal::None,
16787 reversed: selection.reversed,
16788 };
16789 }
16790 }
16791 }
16792 }
16793
16794 let mut new_range = old_range.clone();
16795 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
16796 new_range = range;
16797 if !node.is_named() {
16798 continue;
16799 }
16800 if !display_map.intersects_fold(new_range.start)
16801 && !display_map.intersects_fold(new_range.end)
16802 {
16803 break;
16804 }
16805 }
16806
16807 selected_larger_node |= new_range != old_range;
16808 Selection {
16809 id: selection.id,
16810 start: new_range.start,
16811 end: new_range.end,
16812 goal: SelectionGoal::None,
16813 reversed: selection.reversed,
16814 }
16815 })
16816 .collect::<Vec<_>>();
16817
16818 if !selected_larger_node {
16819 return; // don't put this call in the history
16820 }
16821
16822 // scroll based on transformation done to the last selection created by the user
16823 let (last_old, last_new) = old_selections
16824 .last()
16825 .zip(new_selections.last().cloned())
16826 .expect("old_selections isn't empty");
16827
16828 let is_selection_reversed = if new_selections.len() == 1 {
16829 let should_be_reversed = last_old.start != last_new.start;
16830 new_selections.last_mut().expect("checked above").reversed = should_be_reversed;
16831 should_be_reversed
16832 } else {
16833 last_new.reversed
16834 };
16835
16836 if selected_larger_node {
16837 self.select_syntax_node_history.disable_clearing = true;
16838 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16839 s.select(new_selections.clone());
16840 });
16841 self.select_syntax_node_history.disable_clearing = false;
16842 }
16843
16844 let start_row = last_new.start.to_display_point(&display_map).row().0;
16845 let end_row = last_new.end.to_display_point(&display_map).row().0;
16846 let selection_height = end_row - start_row + 1;
16847 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
16848
16849 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
16850 let scroll_behavior = if fits_on_the_screen {
16851 self.request_autoscroll(Autoscroll::fit(), cx);
16852 SelectSyntaxNodeScrollBehavior::FitSelection
16853 } else if is_selection_reversed {
16854 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16855 SelectSyntaxNodeScrollBehavior::CursorTop
16856 } else {
16857 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16858 SelectSyntaxNodeScrollBehavior::CursorBottom
16859 };
16860
16861 let old_selections: Box<[Selection<Anchor>]> = old_selections
16862 .iter()
16863 .map(|s| s.map(|offset| buffer.anchor_before(offset)))
16864 .collect();
16865 self.select_syntax_node_history.push((
16866 old_selections,
16867 scroll_behavior,
16868 is_selection_reversed,
16869 ));
16870 }
16871
16872 pub fn select_smaller_syntax_node(
16873 &mut self,
16874 _: &SelectSmallerSyntaxNode,
16875 window: &mut Window,
16876 cx: &mut Context<Self>,
16877 ) {
16878 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16879
16880 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
16881 self.select_syntax_node_history.pop()
16882 {
16883 if let Some(selection) = selections.last_mut() {
16884 selection.reversed = is_selection_reversed;
16885 }
16886
16887 let snapshot = self.buffer.read(cx).snapshot(cx);
16888 let selections: Vec<Selection<MultiBufferOffset>> = selections
16889 .iter()
16890 .map(|s| s.map(|anchor| anchor.to_offset(&snapshot)))
16891 .collect();
16892
16893 self.select_syntax_node_history.disable_clearing = true;
16894 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16895 s.select(selections);
16896 });
16897 self.select_syntax_node_history.disable_clearing = false;
16898
16899 match scroll_behavior {
16900 SelectSyntaxNodeScrollBehavior::CursorTop => {
16901 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16902 }
16903 SelectSyntaxNodeScrollBehavior::FitSelection => {
16904 self.request_autoscroll(Autoscroll::fit(), cx);
16905 }
16906 SelectSyntaxNodeScrollBehavior::CursorBottom => {
16907 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16908 }
16909 }
16910 }
16911 }
16912
16913 pub fn unwrap_syntax_node(
16914 &mut self,
16915 _: &UnwrapSyntaxNode,
16916 window: &mut Window,
16917 cx: &mut Context<Self>,
16918 ) {
16919 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16920
16921 let buffer = self.buffer.read(cx).snapshot(cx);
16922 let selections = self
16923 .selections
16924 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16925 .into_iter()
16926 // subtracting the offset requires sorting
16927 .sorted_by_key(|i| i.start);
16928
16929 let full_edits = selections
16930 .into_iter()
16931 .filter_map(|selection| {
16932 let child = if selection.is_empty()
16933 && let Some((_, ancestor_range)) =
16934 buffer.syntax_ancestor(selection.start..selection.end)
16935 {
16936 ancestor_range
16937 } else {
16938 selection.range()
16939 };
16940
16941 let mut parent = child.clone();
16942 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
16943 parent = ancestor_range;
16944 if parent.start < child.start || parent.end > child.end {
16945 break;
16946 }
16947 }
16948
16949 if parent == child {
16950 return None;
16951 }
16952 let text = buffer.text_for_range(child).collect::<String>();
16953 Some((selection.id, parent, text))
16954 })
16955 .collect::<Vec<_>>();
16956 if full_edits.is_empty() {
16957 return;
16958 }
16959
16960 self.transact(window, cx, |this, window, cx| {
16961 this.buffer.update(cx, |buffer, cx| {
16962 buffer.edit(
16963 full_edits
16964 .iter()
16965 .map(|(_, p, t)| (p.clone(), t.clone()))
16966 .collect::<Vec<_>>(),
16967 None,
16968 cx,
16969 );
16970 });
16971 this.change_selections(Default::default(), window, cx, |s| {
16972 let mut offset = 0;
16973 let mut selections = vec![];
16974 for (id, parent, text) in full_edits {
16975 let start = parent.start - offset;
16976 offset += (parent.end - parent.start) - text.len();
16977 selections.push(Selection {
16978 id,
16979 start,
16980 end: start + text.len(),
16981 reversed: false,
16982 goal: Default::default(),
16983 });
16984 }
16985 s.select(selections);
16986 });
16987 });
16988 }
16989
16990 pub fn select_next_syntax_node(
16991 &mut self,
16992 _: &SelectNextSyntaxNode,
16993 window: &mut Window,
16994 cx: &mut Context<Self>,
16995 ) {
16996 let old_selections: Box<[_]> = self
16997 .selections
16998 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16999 .into();
17000 if old_selections.is_empty() {
17001 return;
17002 }
17003
17004 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17005
17006 let buffer = self.buffer.read(cx).snapshot(cx);
17007 let mut selected_sibling = false;
17008
17009 let new_selections = old_selections
17010 .iter()
17011 .map(|selection| {
17012 let old_range = selection.start..selection.end;
17013
17014 let old_range =
17015 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
17016 let excerpt = buffer.excerpt_containing(old_range.clone());
17017
17018 if let Some(mut excerpt) = excerpt
17019 && let Some(node) = excerpt
17020 .buffer()
17021 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
17022 {
17023 let new_range = excerpt.map_range_from_buffer(
17024 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
17025 );
17026 selected_sibling = true;
17027 Selection {
17028 id: selection.id,
17029 start: new_range.start,
17030 end: new_range.end,
17031 goal: SelectionGoal::None,
17032 reversed: selection.reversed,
17033 }
17034 } else {
17035 selection.clone()
17036 }
17037 })
17038 .collect::<Vec<_>>();
17039
17040 if selected_sibling {
17041 self.change_selections(
17042 SelectionEffects::scroll(Autoscroll::fit()),
17043 window,
17044 cx,
17045 |s| {
17046 s.select(new_selections);
17047 },
17048 );
17049 }
17050 }
17051
17052 pub fn select_prev_syntax_node(
17053 &mut self,
17054 _: &SelectPreviousSyntaxNode,
17055 window: &mut Window,
17056 cx: &mut Context<Self>,
17057 ) {
17058 let old_selections: Box<[_]> = self
17059 .selections
17060 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
17061 .into();
17062 if old_selections.is_empty() {
17063 return;
17064 }
17065
17066 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17067
17068 let buffer = self.buffer.read(cx).snapshot(cx);
17069 let mut selected_sibling = false;
17070
17071 let new_selections = old_selections
17072 .iter()
17073 .map(|selection| {
17074 let old_range = selection.start..selection.end;
17075 let old_range =
17076 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
17077 let excerpt = buffer.excerpt_containing(old_range.clone());
17078
17079 if let Some(mut excerpt) = excerpt
17080 && let Some(node) = excerpt
17081 .buffer()
17082 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
17083 {
17084 let new_range = excerpt.map_range_from_buffer(
17085 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
17086 );
17087 selected_sibling = true;
17088 Selection {
17089 id: selection.id,
17090 start: new_range.start,
17091 end: new_range.end,
17092 goal: SelectionGoal::None,
17093 reversed: selection.reversed,
17094 }
17095 } else {
17096 selection.clone()
17097 }
17098 })
17099 .collect::<Vec<_>>();
17100
17101 if selected_sibling {
17102 self.change_selections(
17103 SelectionEffects::scroll(Autoscroll::fit()),
17104 window,
17105 cx,
17106 |s| {
17107 s.select(new_selections);
17108 },
17109 );
17110 }
17111 }
17112
17113 pub fn move_to_start_of_larger_syntax_node(
17114 &mut self,
17115 _: &MoveToStartOfLargerSyntaxNode,
17116 window: &mut Window,
17117 cx: &mut Context<Self>,
17118 ) {
17119 self.move_cursors_to_syntax_nodes(window, cx, false);
17120 }
17121
17122 pub fn move_to_end_of_larger_syntax_node(
17123 &mut self,
17124 _: &MoveToEndOfLargerSyntaxNode,
17125 window: &mut Window,
17126 cx: &mut Context<Self>,
17127 ) {
17128 self.move_cursors_to_syntax_nodes(window, cx, true);
17129 }
17130
17131 fn find_syntax_node_boundary(
17132 &self,
17133 selection_pos: MultiBufferOffset,
17134 move_to_end: bool,
17135 display_map: &DisplaySnapshot,
17136 buffer: &MultiBufferSnapshot,
17137 ) -> MultiBufferOffset {
17138 let old_range = selection_pos..selection_pos;
17139 let mut new_pos = selection_pos;
17140 let mut search_range = old_range;
17141 while let Some((node, range)) = buffer.syntax_ancestor(search_range.clone()) {
17142 search_range = range.clone();
17143 if !node.is_named()
17144 || display_map.intersects_fold(range.start)
17145 || display_map.intersects_fold(range.end)
17146 // If cursor is already at the end of the syntax node, continue searching
17147 || (move_to_end && range.end == selection_pos)
17148 // If cursor is already at the start of the syntax node, continue searching
17149 || (!move_to_end && range.start == selection_pos)
17150 {
17151 continue;
17152 }
17153
17154 // If we found a string_content node, find the largest parent that is still string_content
17155 // Enables us to skip to the end of strings without taking multiple steps inside the string
17156 let (_, final_range) = if node.kind() == "string_content" {
17157 let mut current_node = node;
17158 let mut current_range = range;
17159 while let Some((parent, parent_range)) =
17160 buffer.syntax_ancestor(current_range.clone())
17161 {
17162 if parent.kind() == "string_content" {
17163 current_node = parent;
17164 current_range = parent_range;
17165 } else {
17166 break;
17167 }
17168 }
17169
17170 (current_node, current_range)
17171 } else {
17172 (node, range)
17173 };
17174
17175 new_pos = if move_to_end {
17176 final_range.end
17177 } else {
17178 final_range.start
17179 };
17180
17181 break;
17182 }
17183
17184 new_pos
17185 }
17186
17187 fn move_cursors_to_syntax_nodes(
17188 &mut self,
17189 window: &mut Window,
17190 cx: &mut Context<Self>,
17191 move_to_end: bool,
17192 ) -> bool {
17193 let old_selections: Box<[_]> = self
17194 .selections
17195 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
17196 .into();
17197 if old_selections.is_empty() {
17198 return false;
17199 }
17200
17201 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17202
17203 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17204 let buffer = self.buffer.read(cx).snapshot(cx);
17205
17206 let mut any_cursor_moved = false;
17207 let new_selections = old_selections
17208 .iter()
17209 .map(|selection| {
17210 if !selection.is_empty() {
17211 return selection.clone();
17212 }
17213
17214 let selection_pos = selection.head();
17215 let new_pos = self.find_syntax_node_boundary(
17216 selection_pos,
17217 move_to_end,
17218 &display_map,
17219 &buffer,
17220 );
17221
17222 any_cursor_moved |= new_pos != selection_pos;
17223
17224 Selection {
17225 id: selection.id,
17226 start: new_pos,
17227 end: new_pos,
17228 goal: SelectionGoal::None,
17229 reversed: false,
17230 }
17231 })
17232 .collect::<Vec<_>>();
17233
17234 self.change_selections(Default::default(), window, cx, |s| {
17235 s.select(new_selections);
17236 });
17237 self.request_autoscroll(Autoscroll::newest(), cx);
17238
17239 any_cursor_moved
17240 }
17241
17242 pub fn select_to_start_of_larger_syntax_node(
17243 &mut self,
17244 _: &SelectToStartOfLargerSyntaxNode,
17245 window: &mut Window,
17246 cx: &mut Context<Self>,
17247 ) {
17248 self.select_to_syntax_nodes(window, cx, false);
17249 }
17250
17251 pub fn select_to_end_of_larger_syntax_node(
17252 &mut self,
17253 _: &SelectToEndOfLargerSyntaxNode,
17254 window: &mut Window,
17255 cx: &mut Context<Self>,
17256 ) {
17257 self.select_to_syntax_nodes(window, cx, true);
17258 }
17259
17260 fn select_to_syntax_nodes(
17261 &mut self,
17262 window: &mut Window,
17263 cx: &mut Context<Self>,
17264 move_to_end: bool,
17265 ) {
17266 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17267
17268 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17269 let buffer = self.buffer.read(cx).snapshot(cx);
17270 let old_selections = self.selections.all::<MultiBufferOffset>(&display_map);
17271
17272 let new_selections = old_selections
17273 .iter()
17274 .map(|selection| {
17275 let new_pos = self.find_syntax_node_boundary(
17276 selection.head(),
17277 move_to_end,
17278 &display_map,
17279 &buffer,
17280 );
17281
17282 let mut new_selection = selection.clone();
17283 new_selection.set_head(new_pos, SelectionGoal::None);
17284 new_selection
17285 })
17286 .collect::<Vec<_>>();
17287
17288 self.change_selections(Default::default(), window, cx, |s| {
17289 s.select(new_selections);
17290 });
17291 }
17292
17293 pub fn move_to_enclosing_bracket(
17294 &mut self,
17295 _: &MoveToEnclosingBracket,
17296 window: &mut Window,
17297 cx: &mut Context<Self>,
17298 ) {
17299 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17300 self.change_selections(Default::default(), window, cx, |s| {
17301 s.move_offsets_with(&mut |snapshot, selection| {
17302 let Some(enclosing_bracket_ranges) =
17303 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
17304 else {
17305 return;
17306 };
17307
17308 let mut best_length = usize::MAX;
17309 let mut best_inside = false;
17310 let mut best_in_bracket_range = false;
17311 let mut best_destination = None;
17312 for (open, close) in enclosing_bracket_ranges {
17313 let close = close.to_inclusive();
17314 let length = *close.end() - open.start;
17315 let inside = selection.start >= open.end && selection.end <= *close.start();
17316 let in_bracket_range = open.to_inclusive().contains(&selection.head())
17317 || close.contains(&selection.head());
17318
17319 // If best is next to a bracket and current isn't, skip
17320 if !in_bracket_range && best_in_bracket_range {
17321 continue;
17322 }
17323
17324 // Prefer smaller lengths unless best is inside and current isn't
17325 if length > best_length && (best_inside || !inside) {
17326 continue;
17327 }
17328
17329 best_length = length;
17330 best_inside = inside;
17331 best_in_bracket_range = in_bracket_range;
17332 best_destination = Some(
17333 if close.contains(&selection.start) && close.contains(&selection.end) {
17334 if inside { open.end } else { open.start }
17335 } else if inside {
17336 *close.start()
17337 } else {
17338 *close.end()
17339 },
17340 );
17341 }
17342
17343 if let Some(destination) = best_destination {
17344 selection.collapse_to(destination, SelectionGoal::None);
17345 }
17346 })
17347 });
17348 }
17349
17350 pub fn undo_selection(
17351 &mut self,
17352 _: &UndoSelection,
17353 window: &mut Window,
17354 cx: &mut Context<Self>,
17355 ) {
17356 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17357 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
17358 self.selection_history.mode = SelectionHistoryMode::Undoing;
17359 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17360 this.end_selection(window, cx);
17361 this.change_selections(
17362 SelectionEffects::scroll(Autoscroll::newest()),
17363 window,
17364 cx,
17365 |s| s.select_anchors(entry.selections.to_vec()),
17366 );
17367 });
17368 self.selection_history.mode = SelectionHistoryMode::Normal;
17369
17370 self.select_next_state = entry.select_next_state;
17371 self.select_prev_state = entry.select_prev_state;
17372 self.add_selections_state = entry.add_selections_state;
17373 }
17374 }
17375
17376 pub fn redo_selection(
17377 &mut self,
17378 _: &RedoSelection,
17379 window: &mut Window,
17380 cx: &mut Context<Self>,
17381 ) {
17382 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17383 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
17384 self.selection_history.mode = SelectionHistoryMode::Redoing;
17385 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17386 this.end_selection(window, cx);
17387 this.change_selections(
17388 SelectionEffects::scroll(Autoscroll::newest()),
17389 window,
17390 cx,
17391 |s| s.select_anchors(entry.selections.to_vec()),
17392 );
17393 });
17394 self.selection_history.mode = SelectionHistoryMode::Normal;
17395
17396 self.select_next_state = entry.select_next_state;
17397 self.select_prev_state = entry.select_prev_state;
17398 self.add_selections_state = entry.add_selections_state;
17399 }
17400 }
17401
17402 pub fn expand_excerpts(
17403 &mut self,
17404 action: &ExpandExcerpts,
17405 _: &mut Window,
17406 cx: &mut Context<Self>,
17407 ) {
17408 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
17409 }
17410
17411 pub fn expand_excerpts_down(
17412 &mut self,
17413 action: &ExpandExcerptsDown,
17414 _: &mut Window,
17415 cx: &mut Context<Self>,
17416 ) {
17417 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
17418 }
17419
17420 pub fn expand_excerpts_up(
17421 &mut self,
17422 action: &ExpandExcerptsUp,
17423 _: &mut Window,
17424 cx: &mut Context<Self>,
17425 ) {
17426 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
17427 }
17428
17429 pub fn expand_excerpts_for_direction(
17430 &mut self,
17431 lines: u32,
17432 direction: ExpandExcerptDirection,
17433 cx: &mut Context<Self>,
17434 ) {
17435 let selections = self.selections.disjoint_anchors_arc();
17436
17437 let lines = if lines == 0 {
17438 EditorSettings::get_global(cx).expand_excerpt_lines
17439 } else {
17440 lines
17441 };
17442
17443 let snapshot = self.buffer.read(cx).snapshot(cx);
17444 let excerpt_ids = selections
17445 .iter()
17446 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
17447 .unique()
17448 .sorted()
17449 .collect::<Vec<_>>();
17450
17451 if self.delegate_expand_excerpts {
17452 cx.emit(EditorEvent::ExpandExcerptsRequested {
17453 excerpt_ids,
17454 lines,
17455 direction,
17456 });
17457 return;
17458 }
17459
17460 self.buffer.update(cx, |buffer, cx| {
17461 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
17462 })
17463 }
17464
17465 pub fn expand_excerpt(
17466 &mut self,
17467 excerpt: ExcerptId,
17468 direction: ExpandExcerptDirection,
17469 window: &mut Window,
17470 cx: &mut Context<Self>,
17471 ) {
17472 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
17473
17474 if self.delegate_expand_excerpts {
17475 cx.emit(EditorEvent::ExpandExcerptsRequested {
17476 excerpt_ids: vec![excerpt],
17477 lines: lines_to_expand,
17478 direction,
17479 });
17480 return;
17481 }
17482
17483 let current_scroll_position = self.scroll_position(cx);
17484 let mut scroll = None;
17485
17486 if direction == ExpandExcerptDirection::Down {
17487 let multi_buffer = self.buffer.read(cx);
17488 let snapshot = multi_buffer.snapshot(cx);
17489 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
17490 && let Some(buffer) = multi_buffer.buffer(buffer_id)
17491 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
17492 {
17493 let buffer_snapshot = buffer.read(cx).snapshot();
17494 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
17495 let last_row = buffer_snapshot.max_point().row;
17496 let lines_below = last_row.saturating_sub(excerpt_end_row);
17497 if lines_below >= lines_to_expand {
17498 scroll = Some(
17499 current_scroll_position
17500 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
17501 );
17502 }
17503 }
17504 }
17505 if direction == ExpandExcerptDirection::Up
17506 && self
17507 .buffer
17508 .read(cx)
17509 .snapshot(cx)
17510 .excerpt_before(excerpt)
17511 .is_none()
17512 {
17513 scroll = Some(current_scroll_position);
17514 }
17515
17516 self.buffer.update(cx, |buffer, cx| {
17517 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
17518 });
17519
17520 if let Some(new_scroll_position) = scroll {
17521 self.set_scroll_position(new_scroll_position, window, cx);
17522 }
17523 }
17524
17525 pub fn go_to_singleton_buffer_point(
17526 &mut self,
17527 point: Point,
17528 window: &mut Window,
17529 cx: &mut Context<Self>,
17530 ) {
17531 self.go_to_singleton_buffer_range(point..point, window, cx);
17532 }
17533
17534 pub fn go_to_singleton_buffer_range(
17535 &mut self,
17536 range: Range<Point>,
17537 window: &mut Window,
17538 cx: &mut Context<Self>,
17539 ) {
17540 let multibuffer = self.buffer().read(cx);
17541 let Some(buffer) = multibuffer.as_singleton() else {
17542 return;
17543 };
17544 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
17545 return;
17546 };
17547 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
17548 return;
17549 };
17550 self.change_selections(
17551 SelectionEffects::default().nav_history(true),
17552 window,
17553 cx,
17554 |s| s.select_anchor_ranges([start..end]),
17555 );
17556 }
17557
17558 pub fn go_to_diagnostic(
17559 &mut self,
17560 action: &GoToDiagnostic,
17561 window: &mut Window,
17562 cx: &mut Context<Self>,
17563 ) {
17564 if !self.diagnostics_enabled() {
17565 return;
17566 }
17567 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17568 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
17569 }
17570
17571 pub fn go_to_prev_diagnostic(
17572 &mut self,
17573 action: &GoToPreviousDiagnostic,
17574 window: &mut Window,
17575 cx: &mut Context<Self>,
17576 ) {
17577 if !self.diagnostics_enabled() {
17578 return;
17579 }
17580 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17581 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
17582 }
17583
17584 pub fn go_to_diagnostic_impl(
17585 &mut self,
17586 direction: Direction,
17587 severity: GoToDiagnosticSeverityFilter,
17588 window: &mut Window,
17589 cx: &mut Context<Self>,
17590 ) {
17591 let buffer = self.buffer.read(cx).snapshot(cx);
17592 let selection = self
17593 .selections
17594 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
17595
17596 let mut active_group_id = None;
17597 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
17598 && active_group.active_range.start.to_offset(&buffer) == selection.start
17599 {
17600 active_group_id = Some(active_group.group_id);
17601 }
17602
17603 fn filtered<'a>(
17604 severity: GoToDiagnosticSeverityFilter,
17605 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
17606 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
17607 diagnostics
17608 .filter(move |entry| severity.matches(entry.diagnostic.severity))
17609 .filter(|entry| entry.range.start != entry.range.end)
17610 .filter(|entry| !entry.diagnostic.is_unnecessary)
17611 }
17612
17613 let before = filtered(
17614 severity,
17615 buffer
17616 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
17617 .filter(|entry| entry.range.start <= selection.start),
17618 );
17619 let after = filtered(
17620 severity,
17621 buffer
17622 .diagnostics_in_range(selection.start..buffer.len())
17623 .filter(|entry| entry.range.start >= selection.start),
17624 );
17625
17626 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
17627 if direction == Direction::Prev {
17628 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
17629 {
17630 for diagnostic in prev_diagnostics.into_iter().rev() {
17631 if diagnostic.range.start != selection.start
17632 || active_group_id
17633 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
17634 {
17635 found = Some(diagnostic);
17636 break 'outer;
17637 }
17638 }
17639 }
17640 } else {
17641 for diagnostic in after.chain(before) {
17642 if diagnostic.range.start != selection.start
17643 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
17644 {
17645 found = Some(diagnostic);
17646 break;
17647 }
17648 }
17649 }
17650 let Some(next_diagnostic) = found else {
17651 return;
17652 };
17653
17654 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
17655 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
17656 return;
17657 };
17658 let snapshot = self.snapshot(window, cx);
17659 if snapshot.intersects_fold(next_diagnostic.range.start) {
17660 self.unfold_ranges(
17661 std::slice::from_ref(&next_diagnostic.range),
17662 true,
17663 false,
17664 cx,
17665 );
17666 }
17667 self.change_selections(Default::default(), window, cx, |s| {
17668 s.select_ranges(vec![
17669 next_diagnostic.range.start..next_diagnostic.range.start,
17670 ])
17671 });
17672 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
17673 self.refresh_edit_prediction(false, true, window, cx);
17674 }
17675
17676 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
17677 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17678 let snapshot = self.snapshot(window, cx);
17679 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
17680 self.go_to_hunk_before_or_after_position(
17681 &snapshot,
17682 selection.head(),
17683 Direction::Next,
17684 true,
17685 window,
17686 cx,
17687 );
17688 }
17689
17690 pub fn go_to_hunk_before_or_after_position(
17691 &mut self,
17692 snapshot: &EditorSnapshot,
17693 position: Point,
17694 direction: Direction,
17695 wrap_around: bool,
17696 window: &mut Window,
17697 cx: &mut Context<Editor>,
17698 ) {
17699 let row = if direction == Direction::Next {
17700 self.hunk_after_position(snapshot, position, wrap_around)
17701 .map(|hunk| hunk.row_range.start)
17702 } else {
17703 self.hunk_before_position(snapshot, position, wrap_around)
17704 };
17705
17706 if let Some(row) = row {
17707 let destination = Point::new(row.0, 0);
17708 let autoscroll = Autoscroll::center();
17709
17710 self.unfold_ranges(&[destination..destination], false, false, cx);
17711 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17712 s.select_ranges([destination..destination]);
17713 });
17714 }
17715 }
17716
17717 fn hunk_after_position(
17718 &mut self,
17719 snapshot: &EditorSnapshot,
17720 position: Point,
17721 wrap_around: bool,
17722 ) -> Option<MultiBufferDiffHunk> {
17723 let result = snapshot
17724 .buffer_snapshot()
17725 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
17726 .find(|hunk| hunk.row_range.start.0 > position.row);
17727
17728 if wrap_around {
17729 result.or_else(|| {
17730 snapshot
17731 .buffer_snapshot()
17732 .diff_hunks_in_range(Point::zero()..position)
17733 .find(|hunk| hunk.row_range.end.0 < position.row)
17734 })
17735 } else {
17736 result
17737 }
17738 }
17739
17740 fn go_to_prev_hunk(
17741 &mut self,
17742 _: &GoToPreviousHunk,
17743 window: &mut Window,
17744 cx: &mut Context<Self>,
17745 ) {
17746 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17747 let snapshot = self.snapshot(window, cx);
17748 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
17749 self.go_to_hunk_before_or_after_position(
17750 &snapshot,
17751 selection.head(),
17752 Direction::Prev,
17753 true,
17754 window,
17755 cx,
17756 );
17757 }
17758
17759 fn hunk_before_position(
17760 &mut self,
17761 snapshot: &EditorSnapshot,
17762 position: Point,
17763 wrap_around: bool,
17764 ) -> Option<MultiBufferRow> {
17765 let result = snapshot.buffer_snapshot().diff_hunk_before(position);
17766
17767 if wrap_around {
17768 result.or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
17769 } else {
17770 result
17771 }
17772 }
17773
17774 fn go_to_next_change(
17775 &mut self,
17776 _: &GoToNextChange,
17777 window: &mut Window,
17778 cx: &mut Context<Self>,
17779 ) {
17780 if let Some(selections) = self
17781 .change_list
17782 .next_change(1, Direction::Next)
17783 .map(|s| s.to_vec())
17784 {
17785 self.change_selections(Default::default(), window, cx, |s| {
17786 let map = s.display_snapshot();
17787 s.select_display_ranges(selections.iter().map(|a| {
17788 let point = a.to_display_point(&map);
17789 point..point
17790 }))
17791 })
17792 }
17793 }
17794
17795 fn go_to_previous_change(
17796 &mut self,
17797 _: &GoToPreviousChange,
17798 window: &mut Window,
17799 cx: &mut Context<Self>,
17800 ) {
17801 if let Some(selections) = self
17802 .change_list
17803 .next_change(1, Direction::Prev)
17804 .map(|s| s.to_vec())
17805 {
17806 self.change_selections(Default::default(), window, cx, |s| {
17807 let map = s.display_snapshot();
17808 s.select_display_ranges(selections.iter().map(|a| {
17809 let point = a.to_display_point(&map);
17810 point..point
17811 }))
17812 })
17813 }
17814 }
17815
17816 pub fn go_to_next_document_highlight(
17817 &mut self,
17818 _: &GoToNextDocumentHighlight,
17819 window: &mut Window,
17820 cx: &mut Context<Self>,
17821 ) {
17822 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
17823 }
17824
17825 pub fn go_to_prev_document_highlight(
17826 &mut self,
17827 _: &GoToPreviousDocumentHighlight,
17828 window: &mut Window,
17829 cx: &mut Context<Self>,
17830 ) {
17831 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
17832 }
17833
17834 pub fn go_to_document_highlight_before_or_after_position(
17835 &mut self,
17836 direction: Direction,
17837 window: &mut Window,
17838 cx: &mut Context<Editor>,
17839 ) {
17840 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17841 let snapshot = self.snapshot(window, cx);
17842 let buffer = &snapshot.buffer_snapshot();
17843 let position = self
17844 .selections
17845 .newest::<Point>(&snapshot.display_snapshot)
17846 .head();
17847 let anchor_position = buffer.anchor_after(position);
17848
17849 // Get all document highlights (both read and write)
17850 let mut all_highlights = Vec::new();
17851
17852 if let Some((_, read_highlights)) = self
17853 .background_highlights
17854 .get(&HighlightKey::DocumentHighlightRead)
17855 {
17856 all_highlights.extend(read_highlights.iter());
17857 }
17858
17859 if let Some((_, write_highlights)) = self
17860 .background_highlights
17861 .get(&HighlightKey::DocumentHighlightWrite)
17862 {
17863 all_highlights.extend(write_highlights.iter());
17864 }
17865
17866 if all_highlights.is_empty() {
17867 return;
17868 }
17869
17870 // Sort highlights by position
17871 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
17872
17873 let target_highlight = match direction {
17874 Direction::Next => {
17875 // Find the first highlight after the current position
17876 all_highlights
17877 .iter()
17878 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
17879 }
17880 Direction::Prev => {
17881 // Find the last highlight before the current position
17882 all_highlights
17883 .iter()
17884 .rev()
17885 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
17886 }
17887 };
17888
17889 if let Some(highlight) = target_highlight {
17890 let destination = highlight.start.to_point(buffer);
17891 let autoscroll = Autoscroll::center();
17892
17893 self.unfold_ranges(&[destination..destination], false, false, cx);
17894 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17895 s.select_ranges([destination..destination]);
17896 });
17897 }
17898 }
17899
17900 fn go_to_line<T: 'static>(
17901 &mut self,
17902 position: Anchor,
17903 highlight_color: Option<Hsla>,
17904 window: &mut Window,
17905 cx: &mut Context<Self>,
17906 ) {
17907 let snapshot = self.snapshot(window, cx).display_snapshot;
17908 let position = position.to_point(&snapshot.buffer_snapshot());
17909 let start = snapshot
17910 .buffer_snapshot()
17911 .clip_point(Point::new(position.row, 0), Bias::Left);
17912 let end = start + Point::new(1, 0);
17913 let start = snapshot.buffer_snapshot().anchor_before(start);
17914 let end = snapshot.buffer_snapshot().anchor_before(end);
17915
17916 self.highlight_rows::<T>(
17917 start..end,
17918 highlight_color
17919 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
17920 Default::default(),
17921 cx,
17922 );
17923
17924 if self.buffer.read(cx).is_singleton() {
17925 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
17926 }
17927 }
17928
17929 pub fn go_to_definition(
17930 &mut self,
17931 _: &GoToDefinition,
17932 window: &mut Window,
17933 cx: &mut Context<Self>,
17934 ) -> Task<Result<Navigated>> {
17935 let definition =
17936 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
17937 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
17938 cx.spawn_in(window, async move |editor, cx| {
17939 if definition.await? == Navigated::Yes {
17940 return Ok(Navigated::Yes);
17941 }
17942 match fallback_strategy {
17943 GoToDefinitionFallback::None => Ok(Navigated::No),
17944 GoToDefinitionFallback::FindAllReferences => {
17945 match editor.update_in(cx, |editor, window, cx| {
17946 editor.find_all_references(&FindAllReferences::default(), window, cx)
17947 })? {
17948 Some(references) => references.await,
17949 None => Ok(Navigated::No),
17950 }
17951 }
17952 }
17953 })
17954 }
17955
17956 pub fn go_to_declaration(
17957 &mut self,
17958 _: &GoToDeclaration,
17959 window: &mut Window,
17960 cx: &mut Context<Self>,
17961 ) -> Task<Result<Navigated>> {
17962 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
17963 }
17964
17965 pub fn go_to_declaration_split(
17966 &mut self,
17967 _: &GoToDeclaration,
17968 window: &mut Window,
17969 cx: &mut Context<Self>,
17970 ) -> Task<Result<Navigated>> {
17971 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
17972 }
17973
17974 pub fn go_to_implementation(
17975 &mut self,
17976 _: &GoToImplementation,
17977 window: &mut Window,
17978 cx: &mut Context<Self>,
17979 ) -> Task<Result<Navigated>> {
17980 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
17981 }
17982
17983 pub fn go_to_implementation_split(
17984 &mut self,
17985 _: &GoToImplementationSplit,
17986 window: &mut Window,
17987 cx: &mut Context<Self>,
17988 ) -> Task<Result<Navigated>> {
17989 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
17990 }
17991
17992 pub fn go_to_type_definition(
17993 &mut self,
17994 _: &GoToTypeDefinition,
17995 window: &mut Window,
17996 cx: &mut Context<Self>,
17997 ) -> Task<Result<Navigated>> {
17998 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
17999 }
18000
18001 pub fn go_to_definition_split(
18002 &mut self,
18003 _: &GoToDefinitionSplit,
18004 window: &mut Window,
18005 cx: &mut Context<Self>,
18006 ) -> Task<Result<Navigated>> {
18007 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
18008 }
18009
18010 pub fn go_to_type_definition_split(
18011 &mut self,
18012 _: &GoToTypeDefinitionSplit,
18013 window: &mut Window,
18014 cx: &mut Context<Self>,
18015 ) -> Task<Result<Navigated>> {
18016 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
18017 }
18018
18019 fn go_to_definition_of_kind(
18020 &mut self,
18021 kind: GotoDefinitionKind,
18022 split: bool,
18023 window: &mut Window,
18024 cx: &mut Context<Self>,
18025 ) -> Task<Result<Navigated>> {
18026 let Some(provider) = self.semantics_provider.clone() else {
18027 return Task::ready(Ok(Navigated::No));
18028 };
18029 let head = self
18030 .selections
18031 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
18032 .head();
18033 let buffer = self.buffer.read(cx);
18034 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
18035 return Task::ready(Ok(Navigated::No));
18036 };
18037 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
18038 return Task::ready(Ok(Navigated::No));
18039 };
18040
18041 let nav_entry = self.navigation_entry(self.selections.newest_anchor().head(), cx);
18042
18043 cx.spawn_in(window, async move |editor, cx| {
18044 let Some(definitions) = definitions.await? else {
18045 return Ok(Navigated::No);
18046 };
18047 let navigated = editor
18048 .update_in(cx, |editor, window, cx| {
18049 editor.navigate_to_hover_links(
18050 Some(kind),
18051 definitions
18052 .into_iter()
18053 .filter(|location| {
18054 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
18055 })
18056 .map(HoverLink::Text)
18057 .collect::<Vec<_>>(),
18058 nav_entry,
18059 split,
18060 window,
18061 cx,
18062 )
18063 })?
18064 .await?;
18065 anyhow::Ok(navigated)
18066 })
18067 }
18068
18069 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
18070 let selection = self.selections.newest_anchor();
18071 let head = selection.head();
18072 let tail = selection.tail();
18073
18074 let Some((buffer, start_position)) =
18075 self.buffer.read(cx).text_anchor_for_position(head, cx)
18076 else {
18077 return;
18078 };
18079
18080 let end_position = if head != tail {
18081 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
18082 return;
18083 };
18084 Some(pos)
18085 } else {
18086 None
18087 };
18088
18089 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
18090 let url = if let Some(end_pos) = end_position {
18091 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
18092 } else {
18093 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
18094 };
18095
18096 if let Some(url) = url {
18097 cx.update(|window, cx| {
18098 if parse_zed_link(&url, cx).is_some() {
18099 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
18100 } else {
18101 cx.open_url(&url);
18102 }
18103 })?;
18104 }
18105
18106 anyhow::Ok(())
18107 });
18108
18109 url_finder.detach();
18110 }
18111
18112 pub fn open_selected_filename(
18113 &mut self,
18114 _: &OpenSelectedFilename,
18115 window: &mut Window,
18116 cx: &mut Context<Self>,
18117 ) {
18118 let Some(workspace) = self.workspace() else {
18119 return;
18120 };
18121
18122 let position = self.selections.newest_anchor().head();
18123
18124 let Some((buffer, buffer_position)) =
18125 self.buffer.read(cx).text_anchor_for_position(position, cx)
18126 else {
18127 return;
18128 };
18129
18130 let project = self.project.clone();
18131
18132 cx.spawn_in(window, async move |_, cx| {
18133 let result = find_file(&buffer, project, buffer_position, cx).await;
18134
18135 if let Some((_, path)) = result {
18136 workspace
18137 .update_in(cx, |workspace, window, cx| {
18138 workspace.open_resolved_path(path, window, cx)
18139 })?
18140 .await?;
18141 }
18142 anyhow::Ok(())
18143 })
18144 .detach();
18145 }
18146
18147 pub(crate) fn navigate_to_hover_links(
18148 &mut self,
18149 kind: Option<GotoDefinitionKind>,
18150 definitions: Vec<HoverLink>,
18151 origin: Option<NavigationEntry>,
18152 split: bool,
18153 window: &mut Window,
18154 cx: &mut Context<Editor>,
18155 ) -> Task<Result<Navigated>> {
18156 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
18157 let mut first_url_or_file = None;
18158 let definitions: Vec<_> = definitions
18159 .into_iter()
18160 .filter_map(|def| match def {
18161 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
18162 HoverLink::InlayHint(lsp_location, server_id) => {
18163 let computation =
18164 self.compute_target_location(lsp_location, server_id, window, cx);
18165 Some(cx.background_spawn(computation))
18166 }
18167 HoverLink::Url(url) => {
18168 first_url_or_file = Some(Either::Left(url));
18169 None
18170 }
18171 HoverLink::File(path) => {
18172 first_url_or_file = Some(Either::Right(path));
18173 None
18174 }
18175 })
18176 .collect();
18177
18178 let workspace = self.workspace();
18179
18180 let excerpt_context_lines = multi_buffer::excerpt_context_lines(cx);
18181 cx.spawn_in(window, async move |editor, cx| {
18182 let locations: Vec<Location> = future::join_all(definitions)
18183 .await
18184 .into_iter()
18185 .filter_map(|location| location.transpose())
18186 .collect::<Result<_>>()
18187 .context("location tasks")?;
18188 let mut locations = cx.update(|_, cx| {
18189 locations
18190 .into_iter()
18191 .map(|location| {
18192 let buffer = location.buffer.read(cx);
18193 (location.buffer, location.range.to_point(buffer))
18194 })
18195 .into_group_map()
18196 })?;
18197 let mut num_locations = 0;
18198 for ranges in locations.values_mut() {
18199 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18200 ranges.dedup();
18201 let fits_in_one_excerpt = ranges
18202 .iter()
18203 .tuple_windows()
18204 .all(|(a, b)| b.start.row - a.end.row <= 2 * excerpt_context_lines);
18205 num_locations += if fits_in_one_excerpt { 1 } else { ranges.len() };
18206 }
18207
18208 if num_locations > 1 {
18209 let tab_kind = match kind {
18210 Some(GotoDefinitionKind::Implementation) => "Implementations",
18211 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
18212 Some(GotoDefinitionKind::Declaration) => "Declarations",
18213 Some(GotoDefinitionKind::Type) => "Types",
18214 };
18215 let title = editor
18216 .update_in(cx, |_, _, cx| {
18217 let target = locations
18218 .iter()
18219 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18220 .map(|(buffer, location)| {
18221 buffer
18222 .read(cx)
18223 .text_for_range(location.clone())
18224 .collect::<String>()
18225 })
18226 .filter(|text| !text.contains('\n'))
18227 .unique()
18228 .take(3)
18229 .join(", ");
18230 if target.is_empty() {
18231 tab_kind.to_owned()
18232 } else {
18233 format!("{tab_kind} for {target}")
18234 }
18235 })
18236 .context("buffer title")?;
18237
18238 let Some(workspace) = workspace else {
18239 return Ok(Navigated::No);
18240 };
18241
18242 let opened = workspace
18243 .update_in(cx, |workspace, window, cx| {
18244 let allow_preview = PreviewTabsSettings::get_global(cx)
18245 .enable_preview_multibuffer_from_code_navigation;
18246 if let Some((target_editor, target_pane)) =
18247 Self::open_locations_in_multibuffer(
18248 workspace,
18249 locations,
18250 title,
18251 split,
18252 allow_preview,
18253 MultibufferSelectionMode::First,
18254 window,
18255 cx,
18256 )
18257 {
18258 // We create our own nav history instead of using
18259 // `target_editor.nav_history` because `nav_history`
18260 // seems to be populated asynchronously when an item
18261 // is added to a pane
18262 let mut nav_history = target_pane
18263 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18264 target_editor.update(cx, |editor, cx| {
18265 let nav_data = editor
18266 .navigation_data(editor.selections.newest_anchor().head(), cx);
18267 let target =
18268 Some(nav_history.navigation_entry(Some(
18269 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18270 )));
18271 nav_history.push_tag(origin, target);
18272 })
18273 }
18274 })
18275 .is_ok();
18276
18277 anyhow::Ok(Navigated::from_bool(opened))
18278 } else if num_locations == 0 {
18279 // If there is one url or file, open it directly
18280 match first_url_or_file {
18281 Some(Either::Left(url)) => {
18282 cx.update(|window, cx| {
18283 if parse_zed_link(&url, cx).is_some() {
18284 window
18285 .dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
18286 } else {
18287 cx.open_url(&url);
18288 }
18289 })?;
18290 Ok(Navigated::Yes)
18291 }
18292 Some(Either::Right(path)) => {
18293 // TODO(andrew): respect preview tab settings
18294 // `enable_keep_preview_on_code_navigation` and
18295 // `enable_preview_file_from_code_navigation`
18296 let Some(workspace) = workspace else {
18297 return Ok(Navigated::No);
18298 };
18299 workspace
18300 .update_in(cx, |workspace, window, cx| {
18301 workspace.open_resolved_path(path, window, cx)
18302 })?
18303 .await?;
18304 Ok(Navigated::Yes)
18305 }
18306 None => Ok(Navigated::No),
18307 }
18308 } else {
18309 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18310
18311 editor.update_in(cx, |editor, window, cx| {
18312 let target_ranges = target_ranges
18313 .into_iter()
18314 .map(|r| editor.range_for_match(&r))
18315 .map(collapse_multiline_range)
18316 .collect::<Vec<_>>();
18317 if !split
18318 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
18319 {
18320 let multibuffer = editor.buffer.read(cx);
18321 let target_ranges = target_ranges
18322 .into_iter()
18323 .filter_map(|r| {
18324 let start = multibuffer.buffer_point_to_anchor(
18325 &target_buffer,
18326 r.start,
18327 cx,
18328 )?;
18329 let end = multibuffer.buffer_point_to_anchor(
18330 &target_buffer,
18331 r.end,
18332 cx,
18333 )?;
18334 Some(start..end)
18335 })
18336 .collect::<Vec<_>>();
18337 if target_ranges.is_empty() {
18338 return Navigated::No;
18339 }
18340
18341 editor.change_selections(
18342 SelectionEffects::default().nav_history(true),
18343 window,
18344 cx,
18345 |s| s.select_anchor_ranges(target_ranges),
18346 );
18347
18348 let target =
18349 editor.navigation_entry(editor.selections.newest_anchor().head(), cx);
18350 if let Some(mut nav_history) = editor.nav_history.clone() {
18351 nav_history.push_tag(origin, target);
18352 }
18353 } else {
18354 let Some(workspace) = workspace else {
18355 return Navigated::No;
18356 };
18357 let pane = workspace.read(cx).active_pane().clone();
18358 window.defer(cx, move |window, cx| {
18359 let (target_editor, target_pane): (Entity<Self>, Entity<Pane>) =
18360 workspace.update(cx, |workspace, cx| {
18361 let pane = if split {
18362 workspace.adjacent_pane(window, cx)
18363 } else {
18364 workspace.active_pane().clone()
18365 };
18366
18367 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18368 let keep_old_preview = preview_tabs_settings
18369 .enable_keep_preview_on_code_navigation;
18370 let allow_new_preview = preview_tabs_settings
18371 .enable_preview_file_from_code_navigation;
18372
18373 let editor = workspace.open_project_item(
18374 pane.clone(),
18375 target_buffer.clone(),
18376 true,
18377 true,
18378 keep_old_preview,
18379 allow_new_preview,
18380 window,
18381 cx,
18382 );
18383 (editor, pane)
18384 });
18385 // We create our own nav history instead of using
18386 // `target_editor.nav_history` because `nav_history`
18387 // seems to be populated asynchronously when an item
18388 // is added to a pane
18389 let mut nav_history = target_pane
18390 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18391 target_editor.update(cx, |target_editor, cx| {
18392 // When selecting a definition in a different buffer, disable the nav history
18393 // to avoid creating a history entry at the previous cursor location.
18394 pane.update(cx, |pane, _| pane.disable_history());
18395
18396 let multibuffer = target_editor.buffer.read(cx);
18397 let Some(target_buffer) = multibuffer.as_singleton() else {
18398 return Navigated::No;
18399 };
18400 let target_ranges = target_ranges
18401 .into_iter()
18402 .filter_map(|r| {
18403 let start = multibuffer.buffer_point_to_anchor(
18404 &target_buffer,
18405 r.start,
18406 cx,
18407 )?;
18408 let end = multibuffer.buffer_point_to_anchor(
18409 &target_buffer,
18410 r.end,
18411 cx,
18412 )?;
18413 Some(start..end)
18414 })
18415 .collect::<Vec<_>>();
18416 if target_ranges.is_empty() {
18417 return Navigated::No;
18418 }
18419
18420 target_editor.change_selections(
18421 SelectionEffects::default().nav_history(true),
18422 window,
18423 cx,
18424 |s| s.select_anchor_ranges(target_ranges),
18425 );
18426
18427 let nav_data = target_editor.navigation_data(
18428 target_editor.selections.newest_anchor().head(),
18429 cx,
18430 );
18431 let target =
18432 Some(nav_history.navigation_entry(Some(
18433 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18434 )));
18435 nav_history.push_tag(origin, target);
18436 pane.update(cx, |pane, _| pane.enable_history());
18437 Navigated::Yes
18438 });
18439 });
18440 }
18441 Navigated::Yes
18442 })
18443 }
18444 })
18445 }
18446
18447 fn compute_target_location(
18448 &self,
18449 lsp_location: lsp::Location,
18450 server_id: LanguageServerId,
18451 window: &mut Window,
18452 cx: &mut Context<Self>,
18453 ) -> Task<anyhow::Result<Option<Location>>> {
18454 let Some(project) = self.project.clone() else {
18455 return Task::ready(Ok(None));
18456 };
18457
18458 cx.spawn_in(window, async move |editor, cx| {
18459 let location_task = editor.update(cx, |_, cx| {
18460 project.update(cx, |project, cx| {
18461 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
18462 })
18463 })?;
18464 let location = Some({
18465 let target_buffer_handle = location_task.await.context("open local buffer")?;
18466 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
18467 let target_start = target_buffer
18468 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
18469 let target_end = target_buffer
18470 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
18471 target_buffer.anchor_after(target_start)
18472 ..target_buffer.anchor_before(target_end)
18473 });
18474 Location {
18475 buffer: target_buffer_handle,
18476 range,
18477 }
18478 });
18479 Ok(location)
18480 })
18481 }
18482
18483 fn go_to_next_reference(
18484 &mut self,
18485 _: &GoToNextReference,
18486 window: &mut Window,
18487 cx: &mut Context<Self>,
18488 ) {
18489 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
18490 if let Some(task) = task {
18491 task.detach();
18492 };
18493 }
18494
18495 fn go_to_prev_reference(
18496 &mut self,
18497 _: &GoToPreviousReference,
18498 window: &mut Window,
18499 cx: &mut Context<Self>,
18500 ) {
18501 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
18502 if let Some(task) = task {
18503 task.detach();
18504 };
18505 }
18506
18507 fn go_to_symbol_by_offset(
18508 &mut self,
18509 window: &mut Window,
18510 cx: &mut Context<Self>,
18511 offset: i8,
18512 ) -> Task<Result<()>> {
18513 let editor_snapshot = self.snapshot(window, cx);
18514
18515 // We don't care about multi-buffer symbols
18516 let Some((excerpt_id, _, _)) = editor_snapshot.as_singleton() else {
18517 return Task::ready(Ok(()));
18518 };
18519
18520 let cursor_offset = self
18521 .selections
18522 .newest::<MultiBufferOffset>(&editor_snapshot.display_snapshot)
18523 .head();
18524
18525 cx.spawn_in(window, async move |editor, wcx| -> Result<()> {
18526 let Ok(Some(remote_id)) = editor.update(wcx, |ed, cx| {
18527 let buffer = ed.buffer.read(cx).as_singleton()?;
18528 Some(buffer.read(cx).remote_id())
18529 }) else {
18530 return Ok(());
18531 };
18532
18533 let task = editor.update(wcx, |ed, cx| ed.buffer_outline_items(remote_id, cx))?;
18534 let outline_items: Vec<OutlineItem<text::Anchor>> = task.await;
18535
18536 let multi_snapshot = editor_snapshot.buffer();
18537 let buffer_range = |range: &Range<_>| {
18538 Anchor::range_in_buffer(excerpt_id, range.clone()).to_offset(multi_snapshot)
18539 };
18540
18541 wcx.update_window(wcx.window_handle(), |_, window, acx| {
18542 let current_idx = outline_items
18543 .iter()
18544 .enumerate()
18545 .filter_map(|(idx, item)| {
18546 // Find the closest outline item by distance between outline text and cursor location
18547 let source_range = buffer_range(&item.source_range_for_text);
18548 let distance_to_closest_endpoint = cmp::min(
18549 (source_range.start.0 as isize - cursor_offset.0 as isize).abs(),
18550 (source_range.end.0 as isize - cursor_offset.0 as isize).abs(),
18551 );
18552
18553 let item_towards_offset =
18554 (source_range.start.0 as isize - cursor_offset.0 as isize).signum()
18555 == (offset as isize).signum();
18556
18557 let source_range_contains_cursor = source_range.contains(&cursor_offset);
18558
18559 // To pick the next outline to jump to, we should jump in the direction of the offset, and
18560 // we should not already be within the outline's source range. We then pick the closest outline
18561 // item.
18562 (item_towards_offset && !source_range_contains_cursor)
18563 .then_some((distance_to_closest_endpoint, idx))
18564 })
18565 .min()
18566 .map(|(_, idx)| idx);
18567
18568 let Some(idx) = current_idx else {
18569 return;
18570 };
18571
18572 let range = buffer_range(&outline_items[idx].source_range_for_text);
18573 let selection = [range.start..range.start];
18574
18575 let _ = editor
18576 .update(acx, |editor, ecx| {
18577 editor.change_selections(
18578 SelectionEffects::scroll(Autoscroll::newest()),
18579 window,
18580 ecx,
18581 |s| s.select_ranges(selection),
18582 );
18583 })
18584 .ok();
18585 })?;
18586
18587 Ok(())
18588 })
18589 }
18590
18591 fn go_to_next_symbol(
18592 &mut self,
18593 _: &GoToNextSymbol,
18594 window: &mut Window,
18595 cx: &mut Context<Self>,
18596 ) {
18597 self.go_to_symbol_by_offset(window, cx, 1).detach();
18598 }
18599
18600 fn go_to_previous_symbol(
18601 &mut self,
18602 _: &GoToPreviousSymbol,
18603 window: &mut Window,
18604 cx: &mut Context<Self>,
18605 ) {
18606 self.go_to_symbol_by_offset(window, cx, -1).detach();
18607 }
18608
18609 pub fn go_to_reference_before_or_after_position(
18610 &mut self,
18611 direction: Direction,
18612 count: usize,
18613 window: &mut Window,
18614 cx: &mut Context<Self>,
18615 ) -> Option<Task<Result<()>>> {
18616 let selection = self.selections.newest_anchor();
18617 let head = selection.head();
18618
18619 let multi_buffer = self.buffer.read(cx);
18620
18621 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
18622 let workspace = self.workspace()?;
18623 let project = workspace.read(cx).project().clone();
18624 let references =
18625 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
18626 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
18627 let Some(locations) = references.await? else {
18628 return Ok(());
18629 };
18630
18631 if locations.is_empty() {
18632 // totally normal - the cursor may be on something which is not
18633 // a symbol (e.g. a keyword)
18634 log::info!("no references found under cursor");
18635 return Ok(());
18636 }
18637
18638 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
18639
18640 let (locations, current_location_index) =
18641 multi_buffer.update(cx, |multi_buffer, cx| {
18642 let mut locations = locations
18643 .into_iter()
18644 .filter_map(|loc| {
18645 let start = multi_buffer.buffer_anchor_to_anchor(
18646 &loc.buffer,
18647 loc.range.start,
18648 cx,
18649 )?;
18650 let end = multi_buffer.buffer_anchor_to_anchor(
18651 &loc.buffer,
18652 loc.range.end,
18653 cx,
18654 )?;
18655 Some(start..end)
18656 })
18657 .collect::<Vec<_>>();
18658
18659 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18660 // There is an O(n) implementation, but given this list will be
18661 // small (usually <100 items), the extra O(log(n)) factor isn't
18662 // worth the (surprisingly large amount of) extra complexity.
18663 locations
18664 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
18665
18666 let head_offset = head.to_offset(&multi_buffer_snapshot);
18667
18668 let current_location_index = locations.iter().position(|loc| {
18669 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
18670 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
18671 });
18672
18673 (locations, current_location_index)
18674 });
18675
18676 let Some(current_location_index) = current_location_index else {
18677 // This indicates something has gone wrong, because we already
18678 // handle the "no references" case above
18679 log::error!(
18680 "failed to find current reference under cursor. Total references: {}",
18681 locations.len()
18682 );
18683 return Ok(());
18684 };
18685
18686 let destination_location_index = match direction {
18687 Direction::Next => (current_location_index + count) % locations.len(),
18688 Direction::Prev => {
18689 (current_location_index + locations.len() - count % locations.len())
18690 % locations.len()
18691 }
18692 };
18693
18694 // TODO(cameron): is this needed?
18695 // the thinking is to avoid "jumping to the current location" (avoid
18696 // polluting "jumplist" in vim terms)
18697 if current_location_index == destination_location_index {
18698 return Ok(());
18699 }
18700
18701 let Range { start, end } = locations[destination_location_index];
18702
18703 editor.update_in(cx, |editor, window, cx| {
18704 let effects = SelectionEffects::default();
18705
18706 editor.unfold_ranges(&[start..end], false, false, cx);
18707 editor.change_selections(effects, window, cx, |s| {
18708 s.select_ranges([start..start]);
18709 });
18710 })?;
18711
18712 Ok(())
18713 }))
18714 }
18715
18716 pub fn find_all_references(
18717 &mut self,
18718 action: &FindAllReferences,
18719 window: &mut Window,
18720 cx: &mut Context<Self>,
18721 ) -> Option<Task<Result<Navigated>>> {
18722 let always_open_multibuffer = action.always_open_multibuffer;
18723 let selection = self.selections.newest_anchor();
18724 let multi_buffer = self.buffer.read(cx);
18725 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18726 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
18727 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
18728 let head = selection_offset.head();
18729
18730 let head_anchor = multi_buffer_snapshot.anchor_at(
18731 head,
18732 if head < selection_offset.tail() {
18733 Bias::Right
18734 } else {
18735 Bias::Left
18736 },
18737 );
18738
18739 match self
18740 .find_all_references_task_sources
18741 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18742 {
18743 Ok(_) => {
18744 log::info!(
18745 "Ignoring repeated FindAllReferences invocation with the position of already running task"
18746 );
18747 return None;
18748 }
18749 Err(i) => {
18750 self.find_all_references_task_sources.insert(i, head_anchor);
18751 }
18752 }
18753
18754 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
18755 let workspace = self.workspace()?;
18756 let project = workspace.read(cx).project().clone();
18757 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
18758 Some(cx.spawn_in(window, async move |editor, cx| {
18759 let _cleanup = cx.on_drop(&editor, move |editor, _| {
18760 if let Ok(i) = editor
18761 .find_all_references_task_sources
18762 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18763 {
18764 editor.find_all_references_task_sources.remove(i);
18765 }
18766 });
18767
18768 let Some(locations) = references.await? else {
18769 return anyhow::Ok(Navigated::No);
18770 };
18771 let mut locations = cx.update(|_, cx| {
18772 locations
18773 .into_iter()
18774 .map(|location| {
18775 let buffer = location.buffer.read(cx);
18776 (location.buffer, location.range.to_point(buffer))
18777 })
18778 // if special-casing the single-match case, remove ranges
18779 // that intersect current selection
18780 .filter(|(location_buffer, location)| {
18781 if always_open_multibuffer || &buffer != location_buffer {
18782 return true;
18783 }
18784
18785 !location.contains_inclusive(&selection_point.range())
18786 })
18787 .into_group_map()
18788 })?;
18789 if locations.is_empty() {
18790 return anyhow::Ok(Navigated::No);
18791 }
18792 for ranges in locations.values_mut() {
18793 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18794 ranges.dedup();
18795 }
18796 let mut num_locations = 0;
18797 for ranges in locations.values_mut() {
18798 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18799 ranges.dedup();
18800 num_locations += ranges.len();
18801 }
18802
18803 if num_locations == 1 && !always_open_multibuffer {
18804 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18805 let target_range = target_ranges.first().unwrap().clone();
18806
18807 return editor.update_in(cx, |editor, window, cx| {
18808 let range = target_range.to_point(target_buffer.read(cx));
18809 let range = editor.range_for_match(&range);
18810 let range = range.start..range.start;
18811
18812 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
18813 editor.go_to_singleton_buffer_range(range, window, cx);
18814 } else {
18815 let pane = workspace.read(cx).active_pane().clone();
18816 window.defer(cx, move |window, cx| {
18817 let target_editor: Entity<Self> =
18818 workspace.update(cx, |workspace, cx| {
18819 let pane = workspace.active_pane().clone();
18820
18821 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18822 let keep_old_preview = preview_tabs_settings
18823 .enable_keep_preview_on_code_navigation;
18824 let allow_new_preview = preview_tabs_settings
18825 .enable_preview_file_from_code_navigation;
18826
18827 workspace.open_project_item(
18828 pane,
18829 target_buffer.clone(),
18830 true,
18831 true,
18832 keep_old_preview,
18833 allow_new_preview,
18834 window,
18835 cx,
18836 )
18837 });
18838 target_editor.update(cx, |target_editor, cx| {
18839 // When selecting a definition in a different buffer, disable the nav history
18840 // to avoid creating a history entry at the previous cursor location.
18841 pane.update(cx, |pane, _| pane.disable_history());
18842 target_editor.go_to_singleton_buffer_range(range, window, cx);
18843 pane.update(cx, |pane, _| pane.enable_history());
18844 });
18845 });
18846 }
18847 Navigated::No
18848 });
18849 }
18850
18851 workspace.update_in(cx, |workspace, window, cx| {
18852 let target = locations
18853 .iter()
18854 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18855 .map(|(buffer, location)| {
18856 buffer
18857 .read(cx)
18858 .text_for_range(location.clone())
18859 .collect::<String>()
18860 })
18861 .filter(|text| !text.contains('\n'))
18862 .unique()
18863 .take(3)
18864 .join(", ");
18865 let title = if target.is_empty() {
18866 "References".to_owned()
18867 } else {
18868 format!("References to {target}")
18869 };
18870 let allow_preview = PreviewTabsSettings::get_global(cx)
18871 .enable_preview_multibuffer_from_code_navigation;
18872 Self::open_locations_in_multibuffer(
18873 workspace,
18874 locations,
18875 title,
18876 false,
18877 allow_preview,
18878 MultibufferSelectionMode::First,
18879 window,
18880 cx,
18881 );
18882 Navigated::Yes
18883 })
18884 }))
18885 }
18886
18887 /// Opens a multibuffer with the given project locations in it.
18888 pub fn open_locations_in_multibuffer(
18889 workspace: &mut Workspace,
18890 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
18891 title: String,
18892 split: bool,
18893 allow_preview: bool,
18894 multibuffer_selection_mode: MultibufferSelectionMode,
18895 window: &mut Window,
18896 cx: &mut Context<Workspace>,
18897 ) -> Option<(Entity<Editor>, Entity<Pane>)> {
18898 if locations.is_empty() {
18899 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
18900 return None;
18901 }
18902
18903 let capability = workspace.project().read(cx).capability();
18904 let mut ranges = <Vec<Range<Anchor>>>::new();
18905
18906 // a key to find existing multibuffer editors with the same set of locations
18907 // to prevent us from opening more and more multibuffer tabs for searches and the like
18908 let mut key = (title.clone(), vec![]);
18909 let excerpt_buffer = cx.new(|cx| {
18910 let key = &mut key.1;
18911 let mut multibuffer = MultiBuffer::new(capability);
18912 for (buffer, mut ranges_for_buffer) in locations {
18913 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
18914 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
18915 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
18916 PathKey::for_buffer(&buffer, cx),
18917 buffer.clone(),
18918 ranges_for_buffer,
18919 multibuffer_context_lines(cx),
18920 cx,
18921 );
18922 ranges.extend(new_ranges)
18923 }
18924
18925 multibuffer.with_title(title)
18926 });
18927 let existing = workspace.active_pane().update(cx, |pane, cx| {
18928 pane.items()
18929 .filter_map(|item| item.downcast::<Editor>())
18930 .find(|editor| {
18931 editor
18932 .read(cx)
18933 .lookup_key
18934 .as_ref()
18935 .and_then(|it| {
18936 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
18937 })
18938 .is_some_and(|it| *it == key)
18939 })
18940 });
18941 let was_existing = existing.is_some();
18942 let editor = existing.unwrap_or_else(|| {
18943 cx.new(|cx| {
18944 let mut editor = Editor::for_multibuffer(
18945 excerpt_buffer,
18946 Some(workspace.project().clone()),
18947 window,
18948 cx,
18949 );
18950 editor.lookup_key = Some(Box::new(key));
18951 editor
18952 })
18953 });
18954 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
18955 MultibufferSelectionMode::First => {
18956 if let Some(first_range) = ranges.first() {
18957 editor.change_selections(
18958 SelectionEffects::no_scroll(),
18959 window,
18960 cx,
18961 |selections| {
18962 selections.clear_disjoint();
18963 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
18964 },
18965 );
18966 }
18967 editor.highlight_background(
18968 HighlightKey::Editor,
18969 &ranges,
18970 |_, theme| theme.colors().editor_highlighted_line_background,
18971 cx,
18972 );
18973 }
18974 MultibufferSelectionMode::All => {
18975 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
18976 selections.clear_disjoint();
18977 selections.select_anchor_ranges(ranges);
18978 });
18979 }
18980 });
18981
18982 let item = Box::new(editor.clone());
18983
18984 let pane = if split {
18985 workspace.adjacent_pane(window, cx)
18986 } else {
18987 workspace.active_pane().clone()
18988 };
18989 let activate_pane = split;
18990
18991 let mut destination_index = None;
18992 pane.update(cx, |pane, cx| {
18993 if allow_preview && !was_existing {
18994 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
18995 }
18996 if was_existing && !allow_preview {
18997 pane.unpreview_item_if_preview(item.item_id());
18998 }
18999 pane.add_item(item, activate_pane, true, destination_index, window, cx);
19000 });
19001
19002 Some((editor, pane))
19003 }
19004
19005 pub fn rename(
19006 &mut self,
19007 _: &Rename,
19008 window: &mut Window,
19009 cx: &mut Context<Self>,
19010 ) -> Option<Task<Result<()>>> {
19011 use language::ToOffset as _;
19012
19013 let provider = self.semantics_provider.clone()?;
19014 let selection = self.selections.newest_anchor().clone();
19015 let (cursor_buffer, cursor_buffer_position) = self
19016 .buffer
19017 .read(cx)
19018 .text_anchor_for_position(selection.head(), cx)?;
19019 let (tail_buffer, cursor_buffer_position_end) = self
19020 .buffer
19021 .read(cx)
19022 .text_anchor_for_position(selection.tail(), cx)?;
19023 if tail_buffer != cursor_buffer {
19024 return None;
19025 }
19026
19027 let snapshot = cursor_buffer.read(cx).snapshot();
19028 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
19029 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
19030 let prepare_rename = provider
19031 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
19032 .unwrap_or_else(|| Task::ready(Ok(None)));
19033 drop(snapshot);
19034
19035 Some(cx.spawn_in(window, async move |this, cx| {
19036 let rename_range = if let Some(range) = prepare_rename.await? {
19037 Some(range)
19038 } else {
19039 this.update(cx, |this, cx| {
19040 let buffer = this.buffer.read(cx).snapshot(cx);
19041 let mut buffer_highlights = this
19042 .document_highlights_for_position(selection.head(), &buffer)
19043 .filter(|highlight| {
19044 highlight.start.excerpt_id == selection.head().excerpt_id
19045 && highlight.end.excerpt_id == selection.head().excerpt_id
19046 });
19047 buffer_highlights
19048 .next()
19049 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
19050 })?
19051 };
19052 if let Some(rename_range) = rename_range {
19053 this.update_in(cx, |this, window, cx| {
19054 let snapshot = cursor_buffer.read(cx).snapshot();
19055 let rename_buffer_range = rename_range.to_offset(&snapshot);
19056 let cursor_offset_in_rename_range =
19057 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
19058 let cursor_offset_in_rename_range_end =
19059 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
19060
19061 this.take_rename(false, window, cx);
19062 let buffer = this.buffer.read(cx).read(cx);
19063 let cursor_offset = selection.head().to_offset(&buffer);
19064 let rename_start =
19065 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
19066 let rename_end = rename_start + rename_buffer_range.len();
19067 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
19068 let mut old_highlight_id = None;
19069 let old_name: Arc<str> = buffer
19070 .chunks(rename_start..rename_end, true)
19071 .map(|chunk| {
19072 if old_highlight_id.is_none() {
19073 old_highlight_id = chunk.syntax_highlight_id;
19074 }
19075 chunk.text
19076 })
19077 .collect::<String>()
19078 .into();
19079
19080 drop(buffer);
19081
19082 // Position the selection in the rename editor so that it matches the current selection.
19083 this.show_local_selections = false;
19084 let rename_editor = cx.new(|cx| {
19085 let mut editor = Editor::single_line(window, cx);
19086 editor.buffer.update(cx, |buffer, cx| {
19087 buffer.edit(
19088 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
19089 None,
19090 cx,
19091 )
19092 });
19093 let cursor_offset_in_rename_range =
19094 MultiBufferOffset(cursor_offset_in_rename_range);
19095 let cursor_offset_in_rename_range_end =
19096 MultiBufferOffset(cursor_offset_in_rename_range_end);
19097 let rename_selection_range = match cursor_offset_in_rename_range
19098 .cmp(&cursor_offset_in_rename_range_end)
19099 {
19100 Ordering::Equal => {
19101 editor.select_all(&SelectAll, window, cx);
19102 return editor;
19103 }
19104 Ordering::Less => {
19105 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
19106 }
19107 Ordering::Greater => {
19108 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
19109 }
19110 };
19111 if rename_selection_range.end.0 > old_name.len() {
19112 editor.select_all(&SelectAll, window, cx);
19113 } else {
19114 editor.change_selections(Default::default(), window, cx, |s| {
19115 s.select_ranges([rename_selection_range]);
19116 });
19117 }
19118 editor
19119 });
19120 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
19121 if e == &EditorEvent::Focused {
19122 cx.emit(EditorEvent::FocusedIn)
19123 }
19124 })
19125 .detach();
19126
19127 let write_highlights =
19128 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
19129 let read_highlights =
19130 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
19131 let ranges = write_highlights
19132 .iter()
19133 .flat_map(|(_, ranges)| ranges.iter())
19134 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
19135 .cloned()
19136 .collect();
19137
19138 this.highlight_text(
19139 HighlightKey::Rename,
19140 ranges,
19141 HighlightStyle {
19142 fade_out: Some(0.6),
19143 ..Default::default()
19144 },
19145 cx,
19146 );
19147 let rename_focus_handle = rename_editor.focus_handle(cx);
19148 window.focus(&rename_focus_handle, cx);
19149 let block_id = this.insert_blocks(
19150 [BlockProperties {
19151 style: BlockStyle::Flex,
19152 placement: BlockPlacement::Below(range.start),
19153 height: Some(1),
19154 render: Arc::new({
19155 let rename_editor = rename_editor.clone();
19156 move |cx: &mut BlockContext| {
19157 let mut text_style = cx.editor_style.text.clone();
19158 if let Some(highlight_style) = old_highlight_id
19159 .and_then(|h| h.style(&cx.editor_style.syntax))
19160 {
19161 text_style = text_style.highlight(highlight_style);
19162 }
19163 div()
19164 .block_mouse_except_scroll()
19165 .pl(cx.anchor_x)
19166 .child(EditorElement::new(
19167 &rename_editor,
19168 EditorStyle {
19169 background: cx.theme().system().transparent,
19170 local_player: cx.editor_style.local_player,
19171 text: text_style,
19172 scrollbar_width: cx.editor_style.scrollbar_width,
19173 syntax: cx.editor_style.syntax.clone(),
19174 status: cx.editor_style.status.clone(),
19175 inlay_hints_style: HighlightStyle {
19176 font_weight: Some(FontWeight::BOLD),
19177 ..make_inlay_hints_style(cx.app)
19178 },
19179 edit_prediction_styles: make_suggestion_styles(
19180 cx.app,
19181 ),
19182 ..EditorStyle::default()
19183 },
19184 ))
19185 .into_any_element()
19186 }
19187 }),
19188 priority: 0,
19189 }],
19190 Some(Autoscroll::fit()),
19191 cx,
19192 )[0];
19193 this.pending_rename = Some(RenameState {
19194 range,
19195 old_name,
19196 editor: rename_editor,
19197 block_id,
19198 });
19199 })?;
19200 }
19201
19202 Ok(())
19203 }))
19204 }
19205
19206 pub fn confirm_rename(
19207 &mut self,
19208 _: &ConfirmRename,
19209 window: &mut Window,
19210 cx: &mut Context<Self>,
19211 ) -> Option<Task<Result<()>>> {
19212 let rename = self.take_rename(false, window, cx)?;
19213 let workspace = self.workspace()?.downgrade();
19214 let (buffer, start) = self
19215 .buffer
19216 .read(cx)
19217 .text_anchor_for_position(rename.range.start, cx)?;
19218 let (end_buffer, _) = self
19219 .buffer
19220 .read(cx)
19221 .text_anchor_for_position(rename.range.end, cx)?;
19222 if buffer != end_buffer {
19223 return None;
19224 }
19225
19226 let old_name = rename.old_name;
19227 let new_name = rename.editor.read(cx).text(cx);
19228
19229 let rename = self.semantics_provider.as_ref()?.perform_rename(
19230 &buffer,
19231 start,
19232 new_name.clone(),
19233 cx,
19234 )?;
19235
19236 Some(cx.spawn_in(window, async move |editor, cx| {
19237 let project_transaction = rename.await?;
19238 Self::open_project_transaction(
19239 &editor,
19240 workspace,
19241 project_transaction,
19242 format!("Rename: {} → {}", old_name, new_name),
19243 cx,
19244 )
19245 .await?;
19246
19247 editor.update(cx, |editor, cx| {
19248 editor.refresh_document_highlights(cx);
19249 })?;
19250 Ok(())
19251 }))
19252 }
19253
19254 fn take_rename(
19255 &mut self,
19256 moving_cursor: bool,
19257 window: &mut Window,
19258 cx: &mut Context<Self>,
19259 ) -> Option<RenameState> {
19260 let rename = self.pending_rename.take()?;
19261 if rename.editor.focus_handle(cx).is_focused(window) {
19262 window.focus(&self.focus_handle, cx);
19263 }
19264
19265 self.remove_blocks(
19266 [rename.block_id].into_iter().collect(),
19267 Some(Autoscroll::fit()),
19268 cx,
19269 );
19270 self.clear_highlights(HighlightKey::Rename, cx);
19271 self.show_local_selections = true;
19272
19273 if moving_cursor {
19274 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
19275 editor
19276 .selections
19277 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
19278 .head()
19279 });
19280
19281 // Update the selection to match the position of the selection inside
19282 // the rename editor.
19283 let snapshot = self.buffer.read(cx).read(cx);
19284 let rename_range = rename.range.to_offset(&snapshot);
19285 let cursor_in_editor = snapshot
19286 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
19287 .min(rename_range.end);
19288 drop(snapshot);
19289
19290 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19291 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
19292 });
19293 } else {
19294 self.refresh_document_highlights(cx);
19295 }
19296
19297 Some(rename)
19298 }
19299
19300 pub fn pending_rename(&self) -> Option<&RenameState> {
19301 self.pending_rename.as_ref()
19302 }
19303
19304 fn format(
19305 &mut self,
19306 _: &Format,
19307 window: &mut Window,
19308 cx: &mut Context<Self>,
19309 ) -> Option<Task<Result<()>>> {
19310 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19311
19312 let project = match &self.project {
19313 Some(project) => project.clone(),
19314 None => return None,
19315 };
19316
19317 Some(self.perform_format(
19318 project,
19319 FormatTrigger::Manual,
19320 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
19321 window,
19322 cx,
19323 ))
19324 }
19325
19326 fn format_selections(
19327 &mut self,
19328 _: &FormatSelections,
19329 window: &mut Window,
19330 cx: &mut Context<Self>,
19331 ) -> Option<Task<Result<()>>> {
19332 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19333
19334 let project = match &self.project {
19335 Some(project) => project.clone(),
19336 None => return None,
19337 };
19338
19339 let ranges = self
19340 .selections
19341 .all_adjusted(&self.display_snapshot(cx))
19342 .into_iter()
19343 .map(|selection| selection.range())
19344 .collect_vec();
19345
19346 Some(self.perform_format(
19347 project,
19348 FormatTrigger::Manual,
19349 FormatTarget::Ranges(ranges),
19350 window,
19351 cx,
19352 ))
19353 }
19354
19355 fn perform_format(
19356 &mut self,
19357 project: Entity<Project>,
19358 trigger: FormatTrigger,
19359 target: FormatTarget,
19360 window: &mut Window,
19361 cx: &mut Context<Self>,
19362 ) -> Task<Result<()>> {
19363 let buffer = self.buffer.clone();
19364 let (buffers, target) = match target {
19365 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
19366 FormatTarget::Ranges(selection_ranges) => {
19367 let multi_buffer = buffer.read(cx);
19368 let snapshot = multi_buffer.read(cx);
19369 let mut buffers = HashSet::default();
19370 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
19371 BTreeMap::new();
19372 for selection_range in selection_ranges {
19373 for (buffer, buffer_range, _) in
19374 snapshot.range_to_buffer_ranges(selection_range.start..=selection_range.end)
19375 {
19376 let buffer_id = buffer.remote_id();
19377 let start = buffer.anchor_before(buffer_range.start);
19378 let end = buffer.anchor_after(buffer_range.end);
19379 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
19380 buffer_id_to_ranges
19381 .entry(buffer_id)
19382 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
19383 .or_insert_with(|| vec![start..end]);
19384 }
19385 }
19386 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
19387 }
19388 };
19389
19390 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
19391 let selections_prev = transaction_id_prev
19392 .and_then(|transaction_id_prev| {
19393 // default to selections as they were after the last edit, if we have them,
19394 // instead of how they are now.
19395 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
19396 // will take you back to where you made the last edit, instead of staying where you scrolled
19397 self.selection_history
19398 .transaction(transaction_id_prev)
19399 .map(|t| t.0.clone())
19400 })
19401 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
19402
19403 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
19404 let format = project.update(cx, |project, cx| {
19405 project.format(buffers, target, true, trigger, cx)
19406 });
19407
19408 cx.spawn_in(window, async move |editor, cx| {
19409 let transaction = futures::select_biased! {
19410 transaction = format.log_err().fuse() => transaction,
19411 () = timeout => {
19412 log::warn!("timed out waiting for formatting");
19413 None
19414 }
19415 };
19416
19417 buffer.update(cx, |buffer, cx| {
19418 if let Some(transaction) = transaction
19419 && !buffer.is_singleton()
19420 {
19421 buffer.push_transaction(&transaction.0, cx);
19422 }
19423 cx.notify();
19424 });
19425
19426 if let Some(transaction_id_now) =
19427 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))
19428 {
19429 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
19430 if has_new_transaction {
19431 editor
19432 .update(cx, |editor, _| {
19433 editor
19434 .selection_history
19435 .insert_transaction(transaction_id_now, selections_prev);
19436 })
19437 .ok();
19438 }
19439 }
19440
19441 Ok(())
19442 })
19443 }
19444
19445 fn organize_imports(
19446 &mut self,
19447 _: &OrganizeImports,
19448 window: &mut Window,
19449 cx: &mut Context<Self>,
19450 ) -> Option<Task<Result<()>>> {
19451 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19452 let project = match &self.project {
19453 Some(project) => project.clone(),
19454 None => return None,
19455 };
19456 Some(self.perform_code_action_kind(
19457 project,
19458 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
19459 window,
19460 cx,
19461 ))
19462 }
19463
19464 fn perform_code_action_kind(
19465 &mut self,
19466 project: Entity<Project>,
19467 kind: CodeActionKind,
19468 window: &mut Window,
19469 cx: &mut Context<Self>,
19470 ) -> Task<Result<()>> {
19471 let buffer = self.buffer.clone();
19472 let buffers = buffer.read(cx).all_buffers();
19473 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
19474 let apply_action = project.update(cx, |project, cx| {
19475 project.apply_code_action_kind(buffers, kind, true, cx)
19476 });
19477 cx.spawn_in(window, async move |_, cx| {
19478 let transaction = futures::select_biased! {
19479 () = timeout => {
19480 log::warn!("timed out waiting for executing code action");
19481 None
19482 }
19483 transaction = apply_action.log_err().fuse() => transaction,
19484 };
19485 buffer.update(cx, |buffer, cx| {
19486 // check if we need this
19487 if let Some(transaction) = transaction
19488 && !buffer.is_singleton()
19489 {
19490 buffer.push_transaction(&transaction.0, cx);
19491 }
19492 cx.notify();
19493 });
19494 Ok(())
19495 })
19496 }
19497
19498 pub fn restart_language_server(
19499 &mut self,
19500 _: &RestartLanguageServer,
19501 _: &mut Window,
19502 cx: &mut Context<Self>,
19503 ) {
19504 if let Some(project) = self.project.clone() {
19505 self.buffer.update(cx, |multi_buffer, cx| {
19506 project.update(cx, |project, cx| {
19507 project.restart_language_servers_for_buffers(
19508 multi_buffer.all_buffers().into_iter().collect(),
19509 HashSet::default(),
19510 cx,
19511 );
19512 });
19513 })
19514 }
19515 }
19516
19517 pub fn stop_language_server(
19518 &mut self,
19519 _: &StopLanguageServer,
19520 _: &mut Window,
19521 cx: &mut Context<Self>,
19522 ) {
19523 if let Some(project) = self.project.clone() {
19524 self.buffer.update(cx, |multi_buffer, cx| {
19525 project.update(cx, |project, cx| {
19526 project.stop_language_servers_for_buffers(
19527 multi_buffer.all_buffers().into_iter().collect(),
19528 HashSet::default(),
19529 cx,
19530 );
19531 });
19532 });
19533 }
19534 }
19535
19536 fn cancel_language_server_work(
19537 workspace: &mut Workspace,
19538 _: &actions::CancelLanguageServerWork,
19539 _: &mut Window,
19540 cx: &mut Context<Workspace>,
19541 ) {
19542 let project = workspace.project();
19543 let buffers = workspace
19544 .active_item(cx)
19545 .and_then(|item| item.act_as::<Editor>(cx))
19546 .map_or(HashSet::default(), |editor| {
19547 editor.read(cx).buffer.read(cx).all_buffers()
19548 });
19549 project.update(cx, |project, cx| {
19550 project.cancel_language_server_work_for_buffers(buffers, cx);
19551 });
19552 }
19553
19554 fn show_character_palette(
19555 &mut self,
19556 _: &ShowCharacterPalette,
19557 window: &mut Window,
19558 _: &mut Context<Self>,
19559 ) {
19560 window.show_character_palette();
19561 }
19562
19563 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
19564 if !self.diagnostics_enabled() {
19565 return;
19566 }
19567
19568 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
19569 let buffer = self.buffer.read(cx).snapshot(cx);
19570 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
19571 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
19572 let is_valid = buffer
19573 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
19574 .any(|entry| {
19575 entry.diagnostic.is_primary
19576 && !entry.range.is_empty()
19577 && entry.range.start == primary_range_start
19578 && entry.diagnostic.message == active_diagnostics.active_message
19579 });
19580
19581 if !is_valid {
19582 self.dismiss_diagnostics(cx);
19583 }
19584 }
19585 }
19586
19587 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
19588 match &self.active_diagnostics {
19589 ActiveDiagnostic::Group(group) => Some(group),
19590 _ => None,
19591 }
19592 }
19593
19594 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
19595 if !self.diagnostics_enabled() {
19596 return;
19597 }
19598 self.dismiss_diagnostics(cx);
19599 self.active_diagnostics = ActiveDiagnostic::All;
19600 }
19601
19602 fn activate_diagnostics(
19603 &mut self,
19604 buffer_id: BufferId,
19605 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
19606 window: &mut Window,
19607 cx: &mut Context<Self>,
19608 ) {
19609 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19610 return;
19611 }
19612 self.dismiss_diagnostics(cx);
19613 let snapshot = self.snapshot(window, cx);
19614 let buffer = self.buffer.read(cx).snapshot(cx);
19615 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
19616 return;
19617 };
19618
19619 let diagnostic_group = buffer
19620 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
19621 .collect::<Vec<_>>();
19622
19623 let language_registry = self
19624 .project()
19625 .map(|project| project.read(cx).languages().clone());
19626
19627 let blocks = renderer.render_group(
19628 diagnostic_group,
19629 buffer_id,
19630 snapshot,
19631 cx.weak_entity(),
19632 language_registry,
19633 cx,
19634 );
19635
19636 let blocks = self.display_map.update(cx, |display_map, cx| {
19637 display_map.insert_blocks(blocks, cx).into_iter().collect()
19638 });
19639 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
19640 active_range: buffer.anchor_before(diagnostic.range.start)
19641 ..buffer.anchor_after(diagnostic.range.end),
19642 active_message: diagnostic.diagnostic.message.clone(),
19643 group_id: diagnostic.diagnostic.group_id,
19644 blocks,
19645 });
19646 cx.notify();
19647 }
19648
19649 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
19650 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19651 return;
19652 };
19653
19654 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
19655 if let ActiveDiagnostic::Group(group) = prev {
19656 self.display_map.update(cx, |display_map, cx| {
19657 display_map.remove_blocks(group.blocks, cx);
19658 });
19659 cx.notify();
19660 }
19661 }
19662
19663 /// Disable inline diagnostics rendering for this editor.
19664 pub fn disable_inline_diagnostics(&mut self) {
19665 self.inline_diagnostics_enabled = false;
19666 self.inline_diagnostics_update = Task::ready(());
19667 self.inline_diagnostics.clear();
19668 }
19669
19670 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
19671 self.diagnostics_enabled = false;
19672 self.dismiss_diagnostics(cx);
19673 self.inline_diagnostics_update = Task::ready(());
19674 self.inline_diagnostics.clear();
19675 }
19676
19677 pub fn disable_word_completions(&mut self) {
19678 self.word_completions_enabled = false;
19679 }
19680
19681 pub fn diagnostics_enabled(&self) -> bool {
19682 self.diagnostics_enabled && self.lsp_data_enabled()
19683 }
19684
19685 pub fn inline_diagnostics_enabled(&self) -> bool {
19686 self.inline_diagnostics_enabled && self.diagnostics_enabled()
19687 }
19688
19689 pub fn show_inline_diagnostics(&self) -> bool {
19690 self.show_inline_diagnostics
19691 }
19692
19693 pub fn toggle_inline_diagnostics(
19694 &mut self,
19695 _: &ToggleInlineDiagnostics,
19696 window: &mut Window,
19697 cx: &mut Context<Editor>,
19698 ) {
19699 self.show_inline_diagnostics = !self.show_inline_diagnostics;
19700 self.refresh_inline_diagnostics(false, window, cx);
19701 }
19702
19703 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
19704 self.diagnostics_max_severity = severity;
19705 self.display_map.update(cx, |display_map, _| {
19706 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
19707 });
19708 }
19709
19710 pub fn toggle_diagnostics(
19711 &mut self,
19712 _: &ToggleDiagnostics,
19713 window: &mut Window,
19714 cx: &mut Context<Editor>,
19715 ) {
19716 if !self.diagnostics_enabled() {
19717 return;
19718 }
19719
19720 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19721 EditorSettings::get_global(cx)
19722 .diagnostics_max_severity
19723 .filter(|severity| severity != &DiagnosticSeverity::Off)
19724 .unwrap_or(DiagnosticSeverity::Hint)
19725 } else {
19726 DiagnosticSeverity::Off
19727 };
19728 self.set_max_diagnostics_severity(new_severity, cx);
19729 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19730 self.active_diagnostics = ActiveDiagnostic::None;
19731 self.inline_diagnostics_update = Task::ready(());
19732 self.inline_diagnostics.clear();
19733 } else {
19734 self.refresh_inline_diagnostics(false, window, cx);
19735 }
19736
19737 cx.notify();
19738 }
19739
19740 pub fn toggle_minimap(
19741 &mut self,
19742 _: &ToggleMinimap,
19743 window: &mut Window,
19744 cx: &mut Context<Editor>,
19745 ) {
19746 if self.supports_minimap(cx) {
19747 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
19748 }
19749 }
19750
19751 fn refresh_inline_diagnostics(
19752 &mut self,
19753 debounce: bool,
19754 window: &mut Window,
19755 cx: &mut Context<Self>,
19756 ) {
19757 let max_severity = ProjectSettings::get_global(cx)
19758 .diagnostics
19759 .inline
19760 .max_severity
19761 .unwrap_or(self.diagnostics_max_severity);
19762
19763 if !self.inline_diagnostics_enabled()
19764 || !self.diagnostics_enabled()
19765 || !self.show_inline_diagnostics
19766 || max_severity == DiagnosticSeverity::Off
19767 {
19768 self.inline_diagnostics_update = Task::ready(());
19769 self.inline_diagnostics.clear();
19770 return;
19771 }
19772
19773 let debounce_ms = ProjectSettings::get_global(cx)
19774 .diagnostics
19775 .inline
19776 .update_debounce_ms;
19777 let debounce = if debounce && debounce_ms > 0 {
19778 Some(Duration::from_millis(debounce_ms))
19779 } else {
19780 None
19781 };
19782 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
19783 if let Some(debounce) = debounce {
19784 cx.background_executor().timer(debounce).await;
19785 }
19786 let Some(snapshot) = editor.upgrade().map(|editor| {
19787 editor.update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
19788 }) else {
19789 return;
19790 };
19791
19792 let new_inline_diagnostics = cx
19793 .background_spawn(async move {
19794 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
19795 for diagnostic_entry in
19796 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
19797 {
19798 let message = diagnostic_entry
19799 .diagnostic
19800 .message
19801 .split_once('\n')
19802 .map(|(line, _)| line)
19803 .map(SharedString::new)
19804 .unwrap_or_else(|| {
19805 SharedString::new(&*diagnostic_entry.diagnostic.message)
19806 });
19807 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
19808 let (Ok(i) | Err(i)) = inline_diagnostics
19809 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
19810 inline_diagnostics.insert(
19811 i,
19812 (
19813 start_anchor,
19814 InlineDiagnostic {
19815 message,
19816 group_id: diagnostic_entry.diagnostic.group_id,
19817 start: diagnostic_entry.range.start.to_point(&snapshot),
19818 is_primary: diagnostic_entry.diagnostic.is_primary,
19819 severity: diagnostic_entry.diagnostic.severity,
19820 },
19821 ),
19822 );
19823 }
19824 inline_diagnostics
19825 })
19826 .await;
19827
19828 editor
19829 .update(cx, |editor, cx| {
19830 editor.inline_diagnostics = new_inline_diagnostics;
19831 cx.notify();
19832 })
19833 .ok();
19834 });
19835 }
19836
19837 fn pull_diagnostics(
19838 &mut self,
19839 buffer_id: BufferId,
19840 _window: &Window,
19841 cx: &mut Context<Self>,
19842 ) -> Option<()> {
19843 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
19844 // skip any LSP updates for it.
19845
19846 if self.active_diagnostics == ActiveDiagnostic::All || !self.diagnostics_enabled() {
19847 return None;
19848 }
19849 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
19850 .diagnostics
19851 .lsp_pull_diagnostics;
19852 if !pull_diagnostics_settings.enabled {
19853 return None;
19854 }
19855 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
19856 let project = self.project()?.downgrade();
19857 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
19858
19859 self.pull_diagnostics_task = cx.spawn(async move |_, cx| {
19860 cx.background_executor().timer(debounce).await;
19861 if let Ok(task) = project.update(cx, |project, cx| {
19862 project.lsp_store().update(cx, |lsp_store, cx| {
19863 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
19864 })
19865 }) {
19866 task.await.log_err();
19867 }
19868 project
19869 .update(cx, |project, cx| {
19870 project.lsp_store().update(cx, |lsp_store, cx| {
19871 lsp_store.pull_document_diagnostics_for_buffer_edit(buffer_id, cx);
19872 })
19873 })
19874 .log_err();
19875 });
19876
19877 Some(())
19878 }
19879
19880 pub fn set_selections_from_remote(
19881 &mut self,
19882 selections: Vec<Selection<Anchor>>,
19883 pending_selection: Option<Selection<Anchor>>,
19884 window: &mut Window,
19885 cx: &mut Context<Self>,
19886 ) {
19887 let old_cursor_position = self.selections.newest_anchor().head();
19888 self.selections
19889 .change_with(&self.display_snapshot(cx), |s| {
19890 s.select_anchors(selections);
19891 if let Some(pending_selection) = pending_selection {
19892 s.set_pending(pending_selection, SelectMode::Character);
19893 } else {
19894 s.clear_pending();
19895 }
19896 });
19897 self.selections_did_change(
19898 false,
19899 &old_cursor_position,
19900 SelectionEffects::default(),
19901 window,
19902 cx,
19903 );
19904 }
19905
19906 pub fn transact(
19907 &mut self,
19908 window: &mut Window,
19909 cx: &mut Context<Self>,
19910 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
19911 ) -> Option<TransactionId> {
19912 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
19913 this.start_transaction_at(Instant::now(), window, cx);
19914 update(this, window, cx);
19915 this.end_transaction_at(Instant::now(), cx)
19916 })
19917 }
19918
19919 pub fn start_transaction_at(
19920 &mut self,
19921 now: Instant,
19922 window: &mut Window,
19923 cx: &mut Context<Self>,
19924 ) -> Option<TransactionId> {
19925 self.end_selection(window, cx);
19926 if let Some(tx_id) = self
19927 .buffer
19928 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
19929 {
19930 self.selection_history
19931 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
19932 cx.emit(EditorEvent::TransactionBegun {
19933 transaction_id: tx_id,
19934 });
19935 Some(tx_id)
19936 } else {
19937 None
19938 }
19939 }
19940
19941 pub fn end_transaction_at(
19942 &mut self,
19943 now: Instant,
19944 cx: &mut Context<Self>,
19945 ) -> Option<TransactionId> {
19946 if let Some(transaction_id) = self
19947 .buffer
19948 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
19949 {
19950 if let Some((_, end_selections)) =
19951 self.selection_history.transaction_mut(transaction_id)
19952 {
19953 *end_selections = Some(self.selections.disjoint_anchors_arc());
19954 } else {
19955 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
19956 }
19957
19958 cx.emit(EditorEvent::Edited { transaction_id });
19959 Some(transaction_id)
19960 } else {
19961 None
19962 }
19963 }
19964
19965 pub fn modify_transaction_selection_history(
19966 &mut self,
19967 transaction_id: TransactionId,
19968 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
19969 ) -> bool {
19970 self.selection_history
19971 .transaction_mut(transaction_id)
19972 .map(modify)
19973 .is_some()
19974 }
19975
19976 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
19977 if self.selection_mark_mode {
19978 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19979 s.move_with(&mut |_, sel| {
19980 sel.collapse_to(sel.head(), SelectionGoal::None);
19981 });
19982 })
19983 }
19984 self.selection_mark_mode = true;
19985 cx.notify();
19986 }
19987
19988 pub fn swap_selection_ends(
19989 &mut self,
19990 _: &actions::SwapSelectionEnds,
19991 window: &mut Window,
19992 cx: &mut Context<Self>,
19993 ) {
19994 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19995 s.move_with(&mut |_, sel| {
19996 if sel.start != sel.end {
19997 sel.reversed = !sel.reversed
19998 }
19999 });
20000 });
20001 self.request_autoscroll(Autoscroll::newest(), cx);
20002 cx.notify();
20003 }
20004
20005 pub fn toggle_focus(
20006 workspace: &mut Workspace,
20007 _: &actions::ToggleFocus,
20008 window: &mut Window,
20009 cx: &mut Context<Workspace>,
20010 ) {
20011 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
20012 return;
20013 };
20014 workspace.activate_item(&item, true, true, window, cx);
20015 }
20016
20017 pub fn toggle_fold(
20018 &mut self,
20019 _: &actions::ToggleFold,
20020 window: &mut Window,
20021 cx: &mut Context<Self>,
20022 ) {
20023 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
20024 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20025 let selection = self.selections.newest::<Point>(&display_map);
20026
20027 let range = if selection.is_empty() {
20028 let point = selection.head().to_display_point(&display_map);
20029 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
20030 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
20031 .to_point(&display_map);
20032 start..end
20033 } else {
20034 selection.range()
20035 };
20036 if display_map.folds_in_range(range).next().is_some() {
20037 self.unfold_lines(&Default::default(), window, cx)
20038 } else {
20039 self.fold(&Default::default(), window, cx)
20040 }
20041 } else {
20042 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20043 let buffer_ids: HashSet<_> = self
20044 .selections
20045 .disjoint_anchor_ranges()
20046 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20047 .collect();
20048
20049 let should_unfold = buffer_ids
20050 .iter()
20051 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
20052
20053 for buffer_id in buffer_ids {
20054 if should_unfold {
20055 self.unfold_buffer(buffer_id, cx);
20056 } else {
20057 self.fold_buffer(buffer_id, cx);
20058 }
20059 }
20060 }
20061 }
20062
20063 pub fn toggle_fold_recursive(
20064 &mut self,
20065 _: &actions::ToggleFoldRecursive,
20066 window: &mut Window,
20067 cx: &mut Context<Self>,
20068 ) {
20069 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20070
20071 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20072 let range = if selection.is_empty() {
20073 let point = selection.head().to_display_point(&display_map);
20074 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
20075 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
20076 .to_point(&display_map);
20077 start..end
20078 } else {
20079 selection.range()
20080 };
20081 if display_map.folds_in_range(range).next().is_some() {
20082 self.unfold_recursive(&Default::default(), window, cx)
20083 } else {
20084 self.fold_recursive(&Default::default(), window, cx)
20085 }
20086 }
20087
20088 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
20089 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
20090 let mut to_fold = Vec::new();
20091 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20092 let selections = self.selections.all_adjusted(&display_map);
20093
20094 for selection in selections {
20095 let range = selection.range().sorted();
20096 let buffer_start_row = range.start.row;
20097
20098 if range.start.row != range.end.row {
20099 let mut found = false;
20100 let mut row = range.start.row;
20101 while row <= range.end.row {
20102 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
20103 {
20104 found = true;
20105 row = crease.range().end.row + 1;
20106 to_fold.push(crease);
20107 } else {
20108 row += 1
20109 }
20110 }
20111 if found {
20112 continue;
20113 }
20114 }
20115
20116 for row in (0..=range.start.row).rev() {
20117 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
20118 && crease.range().end.row >= buffer_start_row
20119 {
20120 to_fold.push(crease);
20121 if row <= range.start.row {
20122 break;
20123 }
20124 }
20125 }
20126 }
20127
20128 self.fold_creases(to_fold, true, window, cx);
20129 } else {
20130 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20131 let buffer_ids = self
20132 .selections
20133 .disjoint_anchor_ranges()
20134 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20135 .collect::<HashSet<_>>();
20136 for buffer_id in buffer_ids {
20137 self.fold_buffer(buffer_id, cx);
20138 }
20139 }
20140 }
20141
20142 pub fn toggle_fold_all(
20143 &mut self,
20144 _: &actions::ToggleFoldAll,
20145 window: &mut Window,
20146 cx: &mut Context<Self>,
20147 ) {
20148 let has_folds = if self.buffer.read(cx).is_singleton() {
20149 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20150 let has_folds = display_map
20151 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
20152 .next()
20153 .is_some();
20154 has_folds
20155 } else {
20156 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
20157 let has_folds = buffer_ids
20158 .iter()
20159 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
20160 has_folds
20161 };
20162
20163 if has_folds {
20164 self.unfold_all(&actions::UnfoldAll, window, cx);
20165 } else {
20166 self.fold_all(&actions::FoldAll, window, cx);
20167 }
20168 }
20169
20170 fn fold_at_level(
20171 &mut self,
20172 fold_at: &FoldAtLevel,
20173 window: &mut Window,
20174 cx: &mut Context<Self>,
20175 ) {
20176 if !self.buffer.read(cx).is_singleton() {
20177 return;
20178 }
20179
20180 let fold_at_level = fold_at.0;
20181 let snapshot = self.buffer.read(cx).snapshot(cx);
20182 let mut to_fold = Vec::new();
20183 let mut stack = vec![(0, snapshot.max_row().0, 1)];
20184
20185 let row_ranges_to_keep: Vec<Range<u32>> = self
20186 .selections
20187 .all::<Point>(&self.display_snapshot(cx))
20188 .into_iter()
20189 .map(|sel| sel.start.row..sel.end.row)
20190 .collect();
20191
20192 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
20193 while start_row < end_row {
20194 match self
20195 .snapshot(window, cx)
20196 .crease_for_buffer_row(MultiBufferRow(start_row))
20197 {
20198 Some(crease) => {
20199 let nested_start_row = crease.range().start.row + 1;
20200 let nested_end_row = crease.range().end.row;
20201
20202 if current_level < fold_at_level {
20203 stack.push((nested_start_row, nested_end_row, current_level + 1));
20204 } else if current_level == fold_at_level {
20205 // Fold iff there is no selection completely contained within the fold region
20206 if !row_ranges_to_keep.iter().any(|selection| {
20207 selection.end >= nested_start_row
20208 && selection.start <= nested_end_row
20209 }) {
20210 to_fold.push(crease);
20211 }
20212 }
20213
20214 start_row = nested_end_row + 1;
20215 }
20216 None => start_row += 1,
20217 }
20218 }
20219 }
20220
20221 self.fold_creases(to_fold, true, window, cx);
20222 }
20223
20224 pub fn fold_at_level_1(
20225 &mut self,
20226 _: &actions::FoldAtLevel1,
20227 window: &mut Window,
20228 cx: &mut Context<Self>,
20229 ) {
20230 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
20231 }
20232
20233 pub fn fold_at_level_2(
20234 &mut self,
20235 _: &actions::FoldAtLevel2,
20236 window: &mut Window,
20237 cx: &mut Context<Self>,
20238 ) {
20239 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
20240 }
20241
20242 pub fn fold_at_level_3(
20243 &mut self,
20244 _: &actions::FoldAtLevel3,
20245 window: &mut Window,
20246 cx: &mut Context<Self>,
20247 ) {
20248 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
20249 }
20250
20251 pub fn fold_at_level_4(
20252 &mut self,
20253 _: &actions::FoldAtLevel4,
20254 window: &mut Window,
20255 cx: &mut Context<Self>,
20256 ) {
20257 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
20258 }
20259
20260 pub fn fold_at_level_5(
20261 &mut self,
20262 _: &actions::FoldAtLevel5,
20263 window: &mut Window,
20264 cx: &mut Context<Self>,
20265 ) {
20266 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
20267 }
20268
20269 pub fn fold_at_level_6(
20270 &mut self,
20271 _: &actions::FoldAtLevel6,
20272 window: &mut Window,
20273 cx: &mut Context<Self>,
20274 ) {
20275 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
20276 }
20277
20278 pub fn fold_at_level_7(
20279 &mut self,
20280 _: &actions::FoldAtLevel7,
20281 window: &mut Window,
20282 cx: &mut Context<Self>,
20283 ) {
20284 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
20285 }
20286
20287 pub fn fold_at_level_8(
20288 &mut self,
20289 _: &actions::FoldAtLevel8,
20290 window: &mut Window,
20291 cx: &mut Context<Self>,
20292 ) {
20293 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
20294 }
20295
20296 pub fn fold_at_level_9(
20297 &mut self,
20298 _: &actions::FoldAtLevel9,
20299 window: &mut Window,
20300 cx: &mut Context<Self>,
20301 ) {
20302 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
20303 }
20304
20305 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
20306 if self.buffer.read(cx).is_singleton() {
20307 let mut fold_ranges = Vec::new();
20308 let snapshot = self.buffer.read(cx).snapshot(cx);
20309
20310 for row in 0..snapshot.max_row().0 {
20311 if let Some(foldable_range) = self
20312 .snapshot(window, cx)
20313 .crease_for_buffer_row(MultiBufferRow(row))
20314 {
20315 fold_ranges.push(foldable_range);
20316 }
20317 }
20318
20319 self.fold_creases(fold_ranges, true, window, cx);
20320 } else {
20321 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
20322 editor
20323 .update_in(cx, |editor, _, cx| {
20324 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
20325 editor.fold_buffer(buffer_id, cx);
20326 }
20327 })
20328 .ok();
20329 });
20330 }
20331 }
20332
20333 pub fn fold_function_bodies(
20334 &mut self,
20335 _: &actions::FoldFunctionBodies,
20336 window: &mut Window,
20337 cx: &mut Context<Self>,
20338 ) {
20339 let snapshot = self.buffer.read(cx).snapshot(cx);
20340
20341 let ranges = snapshot
20342 .text_object_ranges(
20343 MultiBufferOffset(0)..snapshot.len(),
20344 TreeSitterOptions::default(),
20345 )
20346 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
20347 .collect::<Vec<_>>();
20348
20349 let creases = ranges
20350 .into_iter()
20351 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
20352 .collect();
20353
20354 self.fold_creases(creases, true, window, cx);
20355 }
20356
20357 pub fn fold_recursive(
20358 &mut self,
20359 _: &actions::FoldRecursive,
20360 window: &mut Window,
20361 cx: &mut Context<Self>,
20362 ) {
20363 let mut to_fold = Vec::new();
20364 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20365 let selections = self.selections.all_adjusted(&display_map);
20366
20367 for selection in selections {
20368 let range = selection.range().sorted();
20369 let buffer_start_row = range.start.row;
20370
20371 if range.start.row != range.end.row {
20372 let mut found = false;
20373 for row in range.start.row..=range.end.row {
20374 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
20375 found = true;
20376 to_fold.push(crease);
20377 }
20378 }
20379 if found {
20380 continue;
20381 }
20382 }
20383
20384 for row in (0..=range.start.row).rev() {
20385 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
20386 if crease.range().end.row >= buffer_start_row {
20387 to_fold.push(crease);
20388 } else {
20389 break;
20390 }
20391 }
20392 }
20393 }
20394
20395 self.fold_creases(to_fold, true, window, cx);
20396 }
20397
20398 pub fn fold_at(
20399 &mut self,
20400 buffer_row: MultiBufferRow,
20401 window: &mut Window,
20402 cx: &mut Context<Self>,
20403 ) {
20404 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20405
20406 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
20407 let autoscroll = self
20408 .selections
20409 .all::<Point>(&display_map)
20410 .iter()
20411 .any(|selection| crease.range().overlaps(&selection.range()));
20412
20413 self.fold_creases(vec![crease], autoscroll, window, cx);
20414 }
20415 }
20416
20417 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
20418 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
20419 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20420 let buffer = display_map.buffer_snapshot();
20421 let selections = self.selections.all::<Point>(&display_map);
20422 let ranges = selections
20423 .iter()
20424 .map(|s| {
20425 let range = s.display_range(&display_map).sorted();
20426 let mut start = range.start.to_point(&display_map);
20427 let mut end = range.end.to_point(&display_map);
20428 start.column = 0;
20429 end.column = buffer.line_len(MultiBufferRow(end.row));
20430 start..end
20431 })
20432 .collect::<Vec<_>>();
20433
20434 self.unfold_ranges(&ranges, true, true, cx);
20435 } else {
20436 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20437 let buffer_ids = self
20438 .selections
20439 .disjoint_anchor_ranges()
20440 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20441 .collect::<HashSet<_>>();
20442 for buffer_id in buffer_ids {
20443 self.unfold_buffer(buffer_id, cx);
20444 }
20445 }
20446 }
20447
20448 pub fn unfold_recursive(
20449 &mut self,
20450 _: &UnfoldRecursive,
20451 _window: &mut Window,
20452 cx: &mut Context<Self>,
20453 ) {
20454 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20455 let selections = self.selections.all::<Point>(&display_map);
20456 let ranges = selections
20457 .iter()
20458 .map(|s| {
20459 let mut range = s.display_range(&display_map).sorted();
20460 *range.start.column_mut() = 0;
20461 *range.end.column_mut() = display_map.line_len(range.end.row());
20462 let start = range.start.to_point(&display_map);
20463 let end = range.end.to_point(&display_map);
20464 start..end
20465 })
20466 .collect::<Vec<_>>();
20467
20468 self.unfold_ranges(&ranges, true, true, cx);
20469 }
20470
20471 pub fn unfold_at(
20472 &mut self,
20473 buffer_row: MultiBufferRow,
20474 _window: &mut Window,
20475 cx: &mut Context<Self>,
20476 ) {
20477 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20478
20479 let intersection_range = Point::new(buffer_row.0, 0)
20480 ..Point::new(
20481 buffer_row.0,
20482 display_map.buffer_snapshot().line_len(buffer_row),
20483 );
20484
20485 let autoscroll = self
20486 .selections
20487 .all::<Point>(&display_map)
20488 .iter()
20489 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
20490
20491 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
20492 }
20493
20494 pub fn unfold_all(
20495 &mut self,
20496 _: &actions::UnfoldAll,
20497 _window: &mut Window,
20498 cx: &mut Context<Self>,
20499 ) {
20500 if self.buffer.read(cx).is_singleton() {
20501 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20502 self.unfold_ranges(
20503 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
20504 true,
20505 true,
20506 cx,
20507 );
20508 } else {
20509 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
20510 editor
20511 .update(cx, |editor, cx| {
20512 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
20513 editor.unfold_buffer(buffer_id, cx);
20514 }
20515 })
20516 .ok();
20517 });
20518 }
20519 }
20520
20521 pub fn fold_selected_ranges(
20522 &mut self,
20523 _: &FoldSelectedRanges,
20524 window: &mut Window,
20525 cx: &mut Context<Self>,
20526 ) {
20527 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20528 let selections = self.selections.all_adjusted(&display_map);
20529 let ranges = selections
20530 .into_iter()
20531 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
20532 .collect::<Vec<_>>();
20533 self.fold_creases(ranges, true, window, cx);
20534 }
20535
20536 pub fn fold_ranges<T: ToOffset + Clone>(
20537 &mut self,
20538 ranges: Vec<Range<T>>,
20539 auto_scroll: bool,
20540 window: &mut Window,
20541 cx: &mut Context<Self>,
20542 ) {
20543 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20544 let ranges = ranges
20545 .into_iter()
20546 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
20547 .collect::<Vec<_>>();
20548 self.fold_creases(ranges, auto_scroll, window, cx);
20549 }
20550
20551 pub fn fold_creases<T: ToOffset + Clone>(
20552 &mut self,
20553 creases: Vec<Crease<T>>,
20554 auto_scroll: bool,
20555 window: &mut Window,
20556 cx: &mut Context<Self>,
20557 ) {
20558 if creases.is_empty() {
20559 return;
20560 }
20561
20562 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
20563
20564 if auto_scroll {
20565 self.request_autoscroll(Autoscroll::fit(), cx);
20566 }
20567
20568 cx.notify();
20569
20570 self.scrollbar_marker_state.dirty = true;
20571 self.update_data_on_scroll(window, cx);
20572 self.folds_did_change(cx);
20573 }
20574
20575 /// Removes any folds whose ranges intersect any of the given ranges.
20576 pub fn unfold_ranges<T: ToOffset + Clone>(
20577 &mut self,
20578 ranges: &[Range<T>],
20579 inclusive: bool,
20580 auto_scroll: bool,
20581 cx: &mut Context<Self>,
20582 ) {
20583 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20584 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx);
20585 });
20586 self.folds_did_change(cx);
20587 }
20588
20589 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20590 self.fold_buffers([buffer_id], cx);
20591 }
20592
20593 pub fn fold_buffers(
20594 &mut self,
20595 buffer_ids: impl IntoIterator<Item = BufferId>,
20596 cx: &mut Context<Self>,
20597 ) {
20598 if self.buffer().read(cx).is_singleton() {
20599 return;
20600 }
20601
20602 let ids_to_fold: Vec<BufferId> = buffer_ids
20603 .into_iter()
20604 .filter(|id| !self.is_buffer_folded(*id, cx))
20605 .collect();
20606
20607 if ids_to_fold.is_empty() {
20608 return;
20609 }
20610
20611 let mut all_folded_excerpt_ids = Vec::new();
20612 for buffer_id in &ids_to_fold {
20613 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(*buffer_id, cx);
20614 all_folded_excerpt_ids.extend(folded_excerpts.into_iter().map(|(id, _, _)| id));
20615 }
20616
20617 self.display_map.update(cx, |display_map, cx| {
20618 display_map.fold_buffers(ids_to_fold.clone(), cx)
20619 });
20620
20621 let snapshot = self.display_snapshot(cx);
20622 self.selections.change_with(&snapshot, |selections| {
20623 for buffer_id in ids_to_fold {
20624 selections.remove_selections_from_buffer(buffer_id);
20625 }
20626 });
20627
20628 cx.emit(EditorEvent::BufferFoldToggled {
20629 ids: all_folded_excerpt_ids,
20630 folded: true,
20631 });
20632 cx.notify();
20633 }
20634
20635 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20636 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
20637 return;
20638 }
20639 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
20640 self.display_map.update(cx, |display_map, cx| {
20641 display_map.unfold_buffers([buffer_id], cx);
20642 });
20643 cx.emit(EditorEvent::BufferFoldToggled {
20644 ids: unfolded_excerpts.iter().map(|&(id, _, _)| id).collect(),
20645 folded: false,
20646 });
20647 cx.notify();
20648 }
20649
20650 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
20651 self.display_map.read(cx).is_buffer_folded(buffer)
20652 }
20653
20654 pub fn has_any_buffer_folded(&self, cx: &App) -> bool {
20655 if self.buffer().read(cx).is_singleton() {
20656 return false;
20657 }
20658 !self.folded_buffers(cx).is_empty()
20659 }
20660
20661 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
20662 self.display_map.read(cx).folded_buffers()
20663 }
20664
20665 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20666 self.display_map.update(cx, |display_map, cx| {
20667 display_map.disable_header_for_buffer(buffer_id, cx);
20668 });
20669 cx.notify();
20670 }
20671
20672 /// Removes any folds with the given ranges.
20673 pub fn remove_folds_with_type<T: ToOffset + Clone>(
20674 &mut self,
20675 ranges: &[Range<T>],
20676 type_id: TypeId,
20677 auto_scroll: bool,
20678 cx: &mut Context<Self>,
20679 ) {
20680 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20681 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
20682 });
20683 self.folds_did_change(cx);
20684 }
20685
20686 fn remove_folds_with<T: ToOffset + Clone>(
20687 &mut self,
20688 ranges: &[Range<T>],
20689 auto_scroll: bool,
20690 cx: &mut Context<Self>,
20691 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
20692 ) {
20693 if ranges.is_empty() {
20694 return;
20695 }
20696
20697 let mut buffers_affected = HashSet::default();
20698 let multi_buffer = self.buffer().read(cx);
20699 for range in ranges {
20700 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
20701 buffers_affected.insert(buffer.read(cx).remote_id());
20702 };
20703 }
20704
20705 self.display_map.update(cx, update);
20706
20707 if auto_scroll {
20708 self.request_autoscroll(Autoscroll::fit(), cx);
20709 }
20710
20711 cx.notify();
20712 self.scrollbar_marker_state.dirty = true;
20713 self.active_indent_guides_state.dirty = true;
20714 }
20715
20716 pub fn update_renderer_widths(
20717 &mut self,
20718 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
20719 cx: &mut Context<Self>,
20720 ) -> bool {
20721 self.display_map
20722 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
20723 }
20724
20725 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
20726 self.display_map.read(cx).fold_placeholder.clone()
20727 }
20728
20729 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
20730 self.buffer.update(cx, |buffer, cx| {
20731 buffer.set_all_diff_hunks_expanded(cx);
20732 });
20733 }
20734
20735 pub fn expand_all_diff_hunks(
20736 &mut self,
20737 _: &ExpandAllDiffHunks,
20738 _window: &mut Window,
20739 cx: &mut Context<Self>,
20740 ) {
20741 self.buffer.update(cx, |buffer, cx| {
20742 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20743 });
20744 }
20745
20746 pub fn collapse_all_diff_hunks(
20747 &mut self,
20748 _: &CollapseAllDiffHunks,
20749 _window: &mut Window,
20750 cx: &mut Context<Self>,
20751 ) {
20752 self.buffer.update(cx, |buffer, cx| {
20753 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20754 });
20755 }
20756
20757 pub fn toggle_selected_diff_hunks(
20758 &mut self,
20759 _: &ToggleSelectedDiffHunks,
20760 _window: &mut Window,
20761 cx: &mut Context<Self>,
20762 ) {
20763 let ranges: Vec<_> = self
20764 .selections
20765 .disjoint_anchors()
20766 .iter()
20767 .map(|s| s.range())
20768 .collect();
20769 self.toggle_diff_hunks_in_ranges(ranges, cx);
20770 }
20771
20772 pub fn diff_hunks_in_ranges<'a>(
20773 &'a self,
20774 ranges: &'a [Range<Anchor>],
20775 buffer: &'a MultiBufferSnapshot,
20776 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
20777 ranges.iter().flat_map(move |range| {
20778 let end_excerpt_id = range.end.excerpt_id;
20779 let range = range.to_point(buffer);
20780 let mut peek_end = range.end;
20781 if range.end.row < buffer.max_row().0 {
20782 peek_end = Point::new(range.end.row + 1, 0);
20783 }
20784 buffer
20785 .diff_hunks_in_range(range.start..peek_end)
20786 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
20787 })
20788 }
20789
20790 pub fn has_stageable_diff_hunks_in_ranges(
20791 &self,
20792 ranges: &[Range<Anchor>],
20793 snapshot: &MultiBufferSnapshot,
20794 ) -> bool {
20795 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
20796 hunks.any(|hunk| hunk.status().has_secondary_hunk())
20797 }
20798
20799 pub fn toggle_staged_selected_diff_hunks(
20800 &mut self,
20801 _: &::git::ToggleStaged,
20802 _: &mut Window,
20803 cx: &mut Context<Self>,
20804 ) {
20805 let snapshot = self.buffer.read(cx).snapshot(cx);
20806 let ranges: Vec<_> = self
20807 .selections
20808 .disjoint_anchors()
20809 .iter()
20810 .map(|s| s.range())
20811 .collect();
20812 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
20813 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20814 }
20815
20816 pub fn set_render_diff_hunk_controls(
20817 &mut self,
20818 render_diff_hunk_controls: RenderDiffHunkControlsFn,
20819 cx: &mut Context<Self>,
20820 ) {
20821 self.render_diff_hunk_controls = render_diff_hunk_controls;
20822 cx.notify();
20823 }
20824
20825 pub fn stage_and_next(
20826 &mut self,
20827 _: &::git::StageAndNext,
20828 window: &mut Window,
20829 cx: &mut Context<Self>,
20830 ) {
20831 self.do_stage_or_unstage_and_next(true, window, cx);
20832 }
20833
20834 pub fn unstage_and_next(
20835 &mut self,
20836 _: &::git::UnstageAndNext,
20837 window: &mut Window,
20838 cx: &mut Context<Self>,
20839 ) {
20840 self.do_stage_or_unstage_and_next(false, window, cx);
20841 }
20842
20843 pub fn stage_or_unstage_diff_hunks(
20844 &mut self,
20845 stage: bool,
20846 ranges: Vec<Range<Anchor>>,
20847 cx: &mut Context<Self>,
20848 ) {
20849 if self.delegate_stage_and_restore {
20850 let snapshot = self.buffer.read(cx).snapshot(cx);
20851 let hunks: Vec<_> = self.diff_hunks_in_ranges(&ranges, &snapshot).collect();
20852 if !hunks.is_empty() {
20853 cx.emit(EditorEvent::StageOrUnstageRequested { stage, hunks });
20854 }
20855 return;
20856 }
20857 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
20858 cx.spawn(async move |this, cx| {
20859 task.await?;
20860 this.update(cx, |this, cx| {
20861 let snapshot = this.buffer.read(cx).snapshot(cx);
20862 let chunk_by = this
20863 .diff_hunks_in_ranges(&ranges, &snapshot)
20864 .chunk_by(|hunk| hunk.buffer_id);
20865 for (buffer_id, hunks) in &chunk_by {
20866 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
20867 }
20868 })
20869 })
20870 .detach_and_log_err(cx);
20871 }
20872
20873 fn save_buffers_for_ranges_if_needed(
20874 &mut self,
20875 ranges: &[Range<Anchor>],
20876 cx: &mut Context<Editor>,
20877 ) -> Task<Result<()>> {
20878 let multibuffer = self.buffer.read(cx);
20879 let snapshot = multibuffer.read(cx);
20880 let buffer_ids: HashSet<_> = ranges
20881 .iter()
20882 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
20883 .collect();
20884 drop(snapshot);
20885
20886 let mut buffers = HashSet::default();
20887 for buffer_id in buffer_ids {
20888 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
20889 let buffer = buffer_entity.read(cx);
20890 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
20891 {
20892 buffers.insert(buffer_entity);
20893 }
20894 }
20895 }
20896
20897 if let Some(project) = &self.project {
20898 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
20899 } else {
20900 Task::ready(Ok(()))
20901 }
20902 }
20903
20904 fn do_stage_or_unstage_and_next(
20905 &mut self,
20906 stage: bool,
20907 window: &mut Window,
20908 cx: &mut Context<Self>,
20909 ) {
20910 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
20911
20912 if ranges.iter().any(|range| range.start != range.end) {
20913 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20914 return;
20915 }
20916
20917 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20918
20919 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
20920 let wrap_around = !all_diff_hunks_expanded;
20921 let snapshot = self.snapshot(window, cx);
20922 let position = self
20923 .selections
20924 .newest::<Point>(&snapshot.display_snapshot)
20925 .head();
20926
20927 self.go_to_hunk_before_or_after_position(
20928 &snapshot,
20929 position,
20930 Direction::Next,
20931 wrap_around,
20932 window,
20933 cx,
20934 );
20935 }
20936
20937 pub(crate) fn do_stage_or_unstage(
20938 &self,
20939 stage: bool,
20940 buffer_id: BufferId,
20941 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
20942 cx: &mut App,
20943 ) -> Option<()> {
20944 let project = self.project()?;
20945 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
20946 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
20947 let buffer_snapshot = buffer.read(cx).snapshot();
20948 let file_exists = buffer_snapshot
20949 .file()
20950 .is_some_and(|file| file.disk_state().exists());
20951 diff.update(cx, |diff, cx| {
20952 diff.stage_or_unstage_hunks(
20953 stage,
20954 &hunks
20955 .map(|hunk| buffer_diff::DiffHunk {
20956 buffer_range: hunk.buffer_range,
20957 // We don't need to pass in word diffs here because they're only used for rendering and
20958 // this function changes internal state
20959 base_word_diffs: Vec::default(),
20960 buffer_word_diffs: Vec::default(),
20961 diff_base_byte_range: hunk.diff_base_byte_range.start.0
20962 ..hunk.diff_base_byte_range.end.0,
20963 secondary_status: hunk.status.secondary,
20964 range: Point::zero()..Point::zero(), // unused
20965 })
20966 .collect::<Vec<_>>(),
20967 &buffer_snapshot,
20968 file_exists,
20969 cx,
20970 )
20971 });
20972 None
20973 }
20974
20975 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
20976 let ranges: Vec<_> = self
20977 .selections
20978 .disjoint_anchors()
20979 .iter()
20980 .map(|s| s.range())
20981 .collect();
20982 self.buffer
20983 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
20984 }
20985
20986 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
20987 self.buffer.update(cx, |buffer, cx| {
20988 let ranges = vec![Anchor::min()..Anchor::max()];
20989 if !buffer.all_diff_hunks_expanded()
20990 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
20991 {
20992 buffer.collapse_diff_hunks(ranges, cx);
20993 true
20994 } else {
20995 false
20996 }
20997 })
20998 }
20999
21000 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
21001 if self.buffer.read(cx).all_diff_hunks_expanded() {
21002 return true;
21003 }
21004 let ranges = vec![Anchor::min()..Anchor::max()];
21005 self.buffer
21006 .read(cx)
21007 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
21008 }
21009
21010 fn toggle_diff_hunks_in_ranges(
21011 &mut self,
21012 ranges: Vec<Range<Anchor>>,
21013 cx: &mut Context<Editor>,
21014 ) {
21015 self.buffer.update(cx, |buffer, cx| {
21016 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
21017 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
21018 })
21019 }
21020
21021 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
21022 self.buffer.update(cx, |buffer, cx| {
21023 buffer.toggle_single_diff_hunk(range, cx);
21024 })
21025 }
21026
21027 pub(crate) fn apply_all_diff_hunks(
21028 &mut self,
21029 _: &ApplyAllDiffHunks,
21030 window: &mut Window,
21031 cx: &mut Context<Self>,
21032 ) {
21033 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
21034
21035 let buffers = self.buffer.read(cx).all_buffers();
21036 for branch_buffer in buffers {
21037 branch_buffer.update(cx, |branch_buffer, cx| {
21038 branch_buffer.merge_into_base(Vec::new(), cx);
21039 });
21040 }
21041
21042 if let Some(project) = self.project.clone() {
21043 self.save(
21044 SaveOptions {
21045 format: true,
21046 autosave: false,
21047 },
21048 project,
21049 window,
21050 cx,
21051 )
21052 .detach_and_log_err(cx);
21053 }
21054 }
21055
21056 pub(crate) fn apply_selected_diff_hunks(
21057 &mut self,
21058 _: &ApplyDiffHunk,
21059 window: &mut Window,
21060 cx: &mut Context<Self>,
21061 ) {
21062 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
21063 let snapshot = self.snapshot(window, cx);
21064 let hunks = snapshot.hunks_for_ranges(
21065 self.selections
21066 .all(&snapshot.display_snapshot)
21067 .into_iter()
21068 .map(|selection| selection.range()),
21069 );
21070 let mut ranges_by_buffer = HashMap::default();
21071 self.transact(window, cx, |editor, _window, cx| {
21072 for hunk in hunks {
21073 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
21074 ranges_by_buffer
21075 .entry(buffer.clone())
21076 .or_insert_with(Vec::new)
21077 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
21078 }
21079 }
21080
21081 for (buffer, ranges) in ranges_by_buffer {
21082 buffer.update(cx, |buffer, cx| {
21083 buffer.merge_into_base(ranges, cx);
21084 });
21085 }
21086 });
21087
21088 if let Some(project) = self.project.clone() {
21089 self.save(
21090 SaveOptions {
21091 format: true,
21092 autosave: false,
21093 },
21094 project,
21095 window,
21096 cx,
21097 )
21098 .detach_and_log_err(cx);
21099 }
21100 }
21101
21102 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
21103 if hovered != self.gutter_hovered {
21104 self.gutter_hovered = hovered;
21105 cx.notify();
21106 }
21107 }
21108
21109 pub fn insert_blocks(
21110 &mut self,
21111 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
21112 autoscroll: Option<Autoscroll>,
21113 cx: &mut Context<Self>,
21114 ) -> Vec<CustomBlockId> {
21115 let blocks = self
21116 .display_map
21117 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
21118 if let Some(autoscroll) = autoscroll {
21119 self.request_autoscroll(autoscroll, cx);
21120 }
21121 cx.notify();
21122 blocks
21123 }
21124
21125 pub fn resize_blocks(
21126 &mut self,
21127 heights: HashMap<CustomBlockId, u32>,
21128 autoscroll: Option<Autoscroll>,
21129 cx: &mut Context<Self>,
21130 ) {
21131 self.display_map
21132 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
21133 if let Some(autoscroll) = autoscroll {
21134 self.request_autoscroll(autoscroll, cx);
21135 }
21136 cx.notify();
21137 }
21138
21139 pub fn replace_blocks(
21140 &mut self,
21141 renderers: HashMap<CustomBlockId, RenderBlock>,
21142 autoscroll: Option<Autoscroll>,
21143 cx: &mut Context<Self>,
21144 ) {
21145 self.display_map
21146 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
21147 if let Some(autoscroll) = autoscroll {
21148 self.request_autoscroll(autoscroll, cx);
21149 }
21150 cx.notify();
21151 }
21152
21153 pub fn remove_blocks(
21154 &mut self,
21155 block_ids: HashSet<CustomBlockId>,
21156 autoscroll: Option<Autoscroll>,
21157 cx: &mut Context<Self>,
21158 ) {
21159 self.display_map.update(cx, |display_map, cx| {
21160 display_map.remove_blocks(block_ids, cx)
21161 });
21162 if let Some(autoscroll) = autoscroll {
21163 self.request_autoscroll(autoscroll, cx);
21164 }
21165 cx.notify();
21166 }
21167
21168 pub fn row_for_block(
21169 &self,
21170 block_id: CustomBlockId,
21171 cx: &mut Context<Self>,
21172 ) -> Option<DisplayRow> {
21173 self.display_map
21174 .update(cx, |map, cx| map.row_for_block(block_id, cx))
21175 }
21176
21177 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
21178 self.focused_block = Some(focused_block);
21179 }
21180
21181 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
21182 self.focused_block.take()
21183 }
21184
21185 pub fn insert_creases(
21186 &mut self,
21187 creases: impl IntoIterator<Item = Crease<Anchor>>,
21188 cx: &mut Context<Self>,
21189 ) -> Vec<CreaseId> {
21190 self.display_map
21191 .update(cx, |map, cx| map.insert_creases(creases, cx))
21192 }
21193
21194 pub fn remove_creases(
21195 &mut self,
21196 ids: impl IntoIterator<Item = CreaseId>,
21197 cx: &mut Context<Self>,
21198 ) -> Vec<(CreaseId, Range<Anchor>)> {
21199 self.display_map
21200 .update(cx, |map, cx| map.remove_creases(ids, cx))
21201 }
21202
21203 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
21204 self.display_map
21205 .update(cx, |map, cx| map.snapshot(cx))
21206 .longest_row()
21207 }
21208
21209 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
21210 self.display_map
21211 .update(cx, |map, cx| map.snapshot(cx))
21212 .max_point()
21213 }
21214
21215 pub fn text(&self, cx: &App) -> String {
21216 self.buffer.read(cx).read(cx).text()
21217 }
21218
21219 pub fn is_empty(&self, cx: &App) -> bool {
21220 self.buffer.read(cx).read(cx).is_empty()
21221 }
21222
21223 pub fn text_option(&self, cx: &App) -> Option<String> {
21224 let text = self.text(cx);
21225 let text = text.trim();
21226
21227 if text.is_empty() {
21228 return None;
21229 }
21230
21231 Some(text.to_string())
21232 }
21233
21234 pub fn set_text(
21235 &mut self,
21236 text: impl Into<Arc<str>>,
21237 window: &mut Window,
21238 cx: &mut Context<Self>,
21239 ) {
21240 self.transact(window, cx, |this, _, cx| {
21241 this.buffer
21242 .read(cx)
21243 .as_singleton()
21244 .expect("you can only call set_text on editors for singleton buffers")
21245 .update(cx, |buffer, cx| buffer.set_text(text, cx));
21246 });
21247 }
21248
21249 pub fn display_text(&self, cx: &mut App) -> String {
21250 self.display_map
21251 .update(cx, |map, cx| map.snapshot(cx))
21252 .text()
21253 }
21254
21255 fn create_minimap(
21256 &self,
21257 minimap_settings: MinimapSettings,
21258 window: &mut Window,
21259 cx: &mut Context<Self>,
21260 ) -> Option<Entity<Self>> {
21261 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
21262 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
21263 }
21264
21265 fn initialize_new_minimap(
21266 &self,
21267 minimap_settings: MinimapSettings,
21268 window: &mut Window,
21269 cx: &mut Context<Self>,
21270 ) -> Entity<Self> {
21271 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
21272 const MINIMAP_FONT_FAMILY: SharedString = SharedString::new_static(".ZedMono");
21273
21274 let mut minimap = Editor::new_internal(
21275 EditorMode::Minimap {
21276 parent: cx.weak_entity(),
21277 },
21278 self.buffer.clone(),
21279 None,
21280 Some(self.display_map.clone()),
21281 window,
21282 cx,
21283 );
21284 let my_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
21285 let minimap_snapshot = minimap.display_map.update(cx, |map, cx| map.snapshot(cx));
21286 minimap.scroll_manager.clone_state(
21287 &self.scroll_manager,
21288 &my_snapshot,
21289 &minimap_snapshot,
21290 cx,
21291 );
21292 minimap.set_text_style_refinement(TextStyleRefinement {
21293 font_size: Some(MINIMAP_FONT_SIZE),
21294 font_weight: Some(MINIMAP_FONT_WEIGHT),
21295 font_family: Some(MINIMAP_FONT_FAMILY),
21296 ..Default::default()
21297 });
21298 minimap.update_minimap_configuration(minimap_settings, cx);
21299 cx.new(|_| minimap)
21300 }
21301
21302 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
21303 let current_line_highlight = minimap_settings
21304 .current_line_highlight
21305 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
21306 self.set_current_line_highlight(Some(current_line_highlight));
21307 }
21308
21309 pub fn minimap(&self) -> Option<&Entity<Self>> {
21310 self.minimap
21311 .as_ref()
21312 .filter(|_| self.minimap_visibility.visible())
21313 }
21314
21315 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
21316 let mut wrap_guides = smallvec![];
21317
21318 if self.show_wrap_guides == Some(false) {
21319 return wrap_guides;
21320 }
21321
21322 let settings = self.buffer.read(cx).language_settings(cx);
21323 if settings.show_wrap_guides {
21324 match self.soft_wrap_mode(cx) {
21325 SoftWrap::Column(soft_wrap) => {
21326 wrap_guides.push((soft_wrap as usize, true));
21327 }
21328 SoftWrap::Bounded(soft_wrap) => {
21329 wrap_guides.push((soft_wrap as usize, true));
21330 }
21331 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
21332 }
21333 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
21334 }
21335
21336 wrap_guides
21337 }
21338
21339 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
21340 let settings = self.buffer.read(cx).language_settings(cx);
21341 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
21342 match mode {
21343 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
21344 SoftWrap::None
21345 }
21346 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
21347 language_settings::SoftWrap::PreferredLineLength => {
21348 SoftWrap::Column(settings.preferred_line_length)
21349 }
21350 language_settings::SoftWrap::Bounded => {
21351 SoftWrap::Bounded(settings.preferred_line_length)
21352 }
21353 }
21354 }
21355
21356 pub fn set_soft_wrap_mode(
21357 &mut self,
21358 mode: language_settings::SoftWrap,
21359 cx: &mut Context<Self>,
21360 ) {
21361 self.soft_wrap_mode_override = Some(mode);
21362 cx.notify();
21363 }
21364
21365 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
21366 self.hard_wrap = hard_wrap;
21367 cx.notify();
21368 }
21369
21370 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
21371 self.text_style_refinement = Some(style);
21372 }
21373
21374 /// called by the Element so we know what style we were most recently rendered with.
21375 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
21376 // We intentionally do not inform the display map about the minimap style
21377 // so that wrapping is not recalculated and stays consistent for the editor
21378 // and its linked minimap.
21379 if !self.mode.is_minimap() {
21380 let font = style.text.font();
21381 let font_size = style.text.font_size.to_pixels(window.rem_size());
21382 let display_map = self
21383 .placeholder_display_map
21384 .as_ref()
21385 .filter(|_| self.is_empty(cx))
21386 .unwrap_or(&self.display_map);
21387
21388 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
21389 }
21390 self.style = Some(style);
21391 }
21392
21393 pub fn style(&mut self, cx: &App) -> &EditorStyle {
21394 if self.style.is_none() {
21395 self.style = Some(self.create_style(cx));
21396 }
21397 self.style.as_ref().unwrap()
21398 }
21399
21400 // Called by the element. This method is not designed to be called outside of the editor
21401 // element's layout code because it does not notify when rewrapping is computed synchronously.
21402 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
21403 if self.is_empty(cx) {
21404 self.placeholder_display_map
21405 .as_ref()
21406 .map_or(false, |display_map| {
21407 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
21408 })
21409 } else {
21410 self.display_map
21411 .update(cx, |map, cx| map.set_wrap_width(width, cx))
21412 }
21413 }
21414
21415 pub fn set_soft_wrap(&mut self) {
21416 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
21417 }
21418
21419 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
21420 if self.soft_wrap_mode_override.is_some() {
21421 self.soft_wrap_mode_override.take();
21422 } else {
21423 let soft_wrap = match self.soft_wrap_mode(cx) {
21424 SoftWrap::GitDiff => return,
21425 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
21426 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
21427 language_settings::SoftWrap::None
21428 }
21429 };
21430 self.soft_wrap_mode_override = Some(soft_wrap);
21431 }
21432 cx.notify();
21433 }
21434
21435 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
21436 let Some(workspace) = self.workspace() else {
21437 return;
21438 };
21439 let fs = workspace.read(cx).app_state().fs.clone();
21440 let current_show = TabBarSettings::get_global(cx).show;
21441 update_settings_file(fs, cx, move |setting, _| {
21442 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
21443 });
21444 }
21445
21446 pub fn toggle_indent_guides(
21447 &mut self,
21448 _: &ToggleIndentGuides,
21449 _: &mut Window,
21450 cx: &mut Context<Self>,
21451 ) {
21452 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
21453 self.buffer
21454 .read(cx)
21455 .language_settings(cx)
21456 .indent_guides
21457 .enabled
21458 });
21459 self.show_indent_guides = Some(!currently_enabled);
21460 cx.notify();
21461 }
21462
21463 fn should_show_indent_guides(&self) -> Option<bool> {
21464 self.show_indent_guides
21465 }
21466
21467 pub fn disable_indent_guides_for_buffer(
21468 &mut self,
21469 buffer_id: BufferId,
21470 cx: &mut Context<Self>,
21471 ) {
21472 self.buffers_with_disabled_indent_guides.insert(buffer_id);
21473 cx.notify();
21474 }
21475
21476 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
21477 self.buffers_with_disabled_indent_guides
21478 .contains(&buffer_id)
21479 }
21480
21481 pub fn toggle_line_numbers(
21482 &mut self,
21483 _: &ToggleLineNumbers,
21484 _: &mut Window,
21485 cx: &mut Context<Self>,
21486 ) {
21487 let mut editor_settings = EditorSettings::get_global(cx).clone();
21488 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
21489 EditorSettings::override_global(editor_settings, cx);
21490 }
21491
21492 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
21493 if let Some(show_line_numbers) = self.show_line_numbers {
21494 return show_line_numbers;
21495 }
21496 EditorSettings::get_global(cx).gutter.line_numbers
21497 }
21498
21499 pub fn relative_line_numbers(&self, cx: &App) -> RelativeLineNumbers {
21500 match (
21501 self.use_relative_line_numbers,
21502 EditorSettings::get_global(cx).relative_line_numbers,
21503 ) {
21504 (None, setting) => setting,
21505 (Some(false), _) => RelativeLineNumbers::Disabled,
21506 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
21507 (Some(true), _) => RelativeLineNumbers::Enabled,
21508 }
21509 }
21510
21511 pub fn toggle_relative_line_numbers(
21512 &mut self,
21513 _: &ToggleRelativeLineNumbers,
21514 _: &mut Window,
21515 cx: &mut Context<Self>,
21516 ) {
21517 let is_relative = self.relative_line_numbers(cx);
21518 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
21519 }
21520
21521 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
21522 self.use_relative_line_numbers = is_relative;
21523 cx.notify();
21524 }
21525
21526 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
21527 self.show_gutter = show_gutter;
21528 cx.notify();
21529 }
21530
21531 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
21532 self.show_scrollbars = ScrollbarAxes {
21533 horizontal: show,
21534 vertical: show,
21535 };
21536 cx.notify();
21537 }
21538
21539 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21540 self.show_scrollbars.vertical = show;
21541 cx.notify();
21542 }
21543
21544 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21545 self.show_scrollbars.horizontal = show;
21546 cx.notify();
21547 }
21548
21549 pub fn set_minimap_visibility(
21550 &mut self,
21551 minimap_visibility: MinimapVisibility,
21552 window: &mut Window,
21553 cx: &mut Context<Self>,
21554 ) {
21555 if self.minimap_visibility != minimap_visibility {
21556 if minimap_visibility.visible() && self.minimap.is_none() {
21557 let minimap_settings = EditorSettings::get_global(cx).minimap;
21558 self.minimap =
21559 self.create_minimap(minimap_settings.with_show_override(), window, cx);
21560 }
21561 self.minimap_visibility = minimap_visibility;
21562 cx.notify();
21563 }
21564 }
21565
21566 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21567 self.set_show_scrollbars(false, cx);
21568 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
21569 }
21570
21571 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21572 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
21573 }
21574
21575 /// Normally the text in full mode and auto height editors is padded on the
21576 /// left side by roughly half a character width for improved hit testing.
21577 ///
21578 /// Use this method to disable this for cases where this is not wanted (e.g.
21579 /// if you want to align the editor text with some other text above or below)
21580 /// or if you want to add this padding to single-line editors.
21581 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
21582 self.offset_content = offset_content;
21583 cx.notify();
21584 }
21585
21586 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
21587 self.show_line_numbers = Some(show_line_numbers);
21588 cx.notify();
21589 }
21590
21591 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
21592 self.disable_expand_excerpt_buttons = true;
21593 cx.notify();
21594 }
21595
21596 pub fn set_number_deleted_lines(&mut self, number: bool, cx: &mut Context<Self>) {
21597 self.number_deleted_lines = number;
21598 cx.notify();
21599 }
21600
21601 pub fn set_delegate_expand_excerpts(&mut self, delegate: bool) {
21602 self.delegate_expand_excerpts = delegate;
21603 }
21604
21605 pub fn set_delegate_stage_and_restore(&mut self, delegate: bool) {
21606 self.delegate_stage_and_restore = delegate;
21607 }
21608
21609 pub fn set_delegate_open_excerpts(&mut self, delegate: bool) {
21610 self.delegate_open_excerpts = delegate;
21611 }
21612
21613 pub fn set_on_local_selections_changed(
21614 &mut self,
21615 callback: Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
21616 ) {
21617 self.on_local_selections_changed = callback;
21618 }
21619
21620 pub fn set_suppress_selection_callback(&mut self, suppress: bool) {
21621 self.suppress_selection_callback = suppress;
21622 }
21623
21624 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
21625 self.show_git_diff_gutter = Some(show_git_diff_gutter);
21626 cx.notify();
21627 }
21628
21629 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
21630 self.show_code_actions = Some(show_code_actions);
21631 cx.notify();
21632 }
21633
21634 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
21635 self.show_runnables = Some(show_runnables);
21636 cx.notify();
21637 }
21638
21639 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
21640 self.show_breakpoints = Some(show_breakpoints);
21641 cx.notify();
21642 }
21643
21644 pub fn set_show_diff_review_button(&mut self, show: bool, cx: &mut Context<Self>) {
21645 self.show_diff_review_button = show;
21646 cx.notify();
21647 }
21648
21649 pub fn show_diff_review_button(&self) -> bool {
21650 self.show_diff_review_button
21651 }
21652
21653 pub fn render_diff_review_button(
21654 &self,
21655 display_row: DisplayRow,
21656 width: Pixels,
21657 cx: &mut Context<Self>,
21658 ) -> impl IntoElement {
21659 let text_color = cx.theme().colors().text;
21660 let icon_color = cx.theme().colors().icon_accent;
21661
21662 h_flex()
21663 .id("diff_review_button")
21664 .cursor_pointer()
21665 .w(width - px(1.))
21666 .h(relative(0.9))
21667 .justify_center()
21668 .rounded_sm()
21669 .border_1()
21670 .border_color(text_color.opacity(0.1))
21671 .bg(text_color.opacity(0.15))
21672 .hover(|s| {
21673 s.bg(icon_color.opacity(0.4))
21674 .border_color(icon_color.opacity(0.5))
21675 })
21676 .child(Icon::new(IconName::Plus).size(IconSize::Small))
21677 .tooltip(Tooltip::text("Add Review (drag to select multiple lines)"))
21678 .on_mouse_down(
21679 gpui::MouseButton::Left,
21680 cx.listener(move |editor, _event: &gpui::MouseDownEvent, window, cx| {
21681 editor.start_diff_review_drag(display_row, window, cx);
21682 }),
21683 )
21684 }
21685
21686 pub fn start_diff_review_drag(
21687 &mut self,
21688 display_row: DisplayRow,
21689 window: &mut Window,
21690 cx: &mut Context<Self>,
21691 ) {
21692 let snapshot = self.snapshot(window, cx);
21693 let point = snapshot
21694 .display_snapshot
21695 .display_point_to_point(DisplayPoint::new(display_row, 0), Bias::Left);
21696 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21697 self.diff_review_drag_state = Some(DiffReviewDragState {
21698 start_anchor: anchor,
21699 current_anchor: anchor,
21700 });
21701 cx.notify();
21702 }
21703
21704 pub fn update_diff_review_drag(
21705 &mut self,
21706 display_row: DisplayRow,
21707 window: &mut Window,
21708 cx: &mut Context<Self>,
21709 ) {
21710 if self.diff_review_drag_state.is_none() {
21711 return;
21712 }
21713 let snapshot = self.snapshot(window, cx);
21714 let point = snapshot
21715 .display_snapshot
21716 .display_point_to_point(display_row.as_display_point(), Bias::Left);
21717 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21718 if let Some(drag_state) = &mut self.diff_review_drag_state {
21719 drag_state.current_anchor = anchor;
21720 cx.notify();
21721 }
21722 }
21723
21724 pub fn end_diff_review_drag(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21725 if let Some(drag_state) = self.diff_review_drag_state.take() {
21726 let snapshot = self.snapshot(window, cx);
21727 let range = drag_state.row_range(&snapshot.display_snapshot);
21728 self.show_diff_review_overlay(*range.start()..*range.end(), window, cx);
21729 }
21730 cx.notify();
21731 }
21732
21733 pub fn cancel_diff_review_drag(&mut self, cx: &mut Context<Self>) {
21734 self.diff_review_drag_state = None;
21735 cx.notify();
21736 }
21737
21738 /// Calculates the appropriate block height for the diff review overlay.
21739 /// Height is in lines: 2 for input row, 1 for header when comments exist,
21740 /// and 2 lines per comment when expanded.
21741 fn calculate_overlay_height(
21742 &self,
21743 hunk_key: &DiffHunkKey,
21744 comments_expanded: bool,
21745 snapshot: &MultiBufferSnapshot,
21746 ) -> u32 {
21747 let comment_count = self.hunk_comment_count(hunk_key, snapshot);
21748 let base_height: u32 = 2; // Input row with avatar and buttons
21749
21750 if comment_count == 0 {
21751 base_height
21752 } else if comments_expanded {
21753 // Header (1 line) + 2 lines per comment
21754 base_height + 1 + (comment_count as u32 * 2)
21755 } else {
21756 // Just header when collapsed
21757 base_height + 1
21758 }
21759 }
21760
21761 pub fn show_diff_review_overlay(
21762 &mut self,
21763 display_range: Range<DisplayRow>,
21764 window: &mut Window,
21765 cx: &mut Context<Self>,
21766 ) {
21767 let Range { start, end } = display_range.sorted();
21768
21769 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21770 let editor_snapshot = self.snapshot(window, cx);
21771
21772 // Convert display rows to multibuffer points
21773 let start_point = editor_snapshot
21774 .display_snapshot
21775 .display_point_to_point(start.as_display_point(), Bias::Left);
21776 let end_point = editor_snapshot
21777 .display_snapshot
21778 .display_point_to_point(end.as_display_point(), Bias::Left);
21779 let end_multi_buffer_row = MultiBufferRow(end_point.row);
21780
21781 // Create anchor range for the selected lines (start of first line to end of last line)
21782 let line_end = Point::new(
21783 end_point.row,
21784 buffer_snapshot.line_len(end_multi_buffer_row),
21785 );
21786 let anchor_range =
21787 buffer_snapshot.anchor_after(start_point)..buffer_snapshot.anchor_before(line_end);
21788
21789 // Compute the hunk key for this display row
21790 let file_path = buffer_snapshot
21791 .file_at(start_point)
21792 .map(|file: &Arc<dyn language::File>| file.path().clone())
21793 .unwrap_or_else(|| Arc::from(util::rel_path::RelPath::empty()));
21794 let hunk_start_anchor = buffer_snapshot.anchor_before(start_point);
21795 let new_hunk_key = DiffHunkKey {
21796 file_path,
21797 hunk_start_anchor,
21798 };
21799
21800 // Check if we already have an overlay for this hunk
21801 if let Some(existing_overlay) = self.diff_review_overlays.iter().find(|overlay| {
21802 Self::hunk_keys_match(&overlay.hunk_key, &new_hunk_key, &buffer_snapshot)
21803 }) {
21804 // Just focus the existing overlay's prompt editor
21805 let focus_handle = existing_overlay.prompt_editor.focus_handle(cx);
21806 window.focus(&focus_handle, cx);
21807 return;
21808 }
21809
21810 // Dismiss overlays that have no comments for their hunks
21811 self.dismiss_overlays_without_comments(cx);
21812
21813 // Get the current user's avatar URI from the project's user_store
21814 let user_avatar_uri = self.project.as_ref().and_then(|project| {
21815 let user_store = project.read(cx).user_store();
21816 user_store
21817 .read(cx)
21818 .current_user()
21819 .map(|user| user.avatar_uri.clone())
21820 });
21821
21822 // Create anchor at the end of the last row so the block appears immediately below it
21823 // Use multibuffer coordinates for anchor creation
21824 let line_len = buffer_snapshot.line_len(end_multi_buffer_row);
21825 let anchor = buffer_snapshot.anchor_after(Point::new(end_multi_buffer_row.0, line_len));
21826
21827 // Use the hunk key we already computed
21828 let hunk_key = new_hunk_key;
21829
21830 // Create the prompt editor for the review input
21831 let prompt_editor = cx.new(|cx| {
21832 let mut editor = Editor::single_line(window, cx);
21833 editor.set_placeholder_text("Add a review comment...", window, cx);
21834 editor
21835 });
21836
21837 // Register the Newline action on the prompt editor to submit the review
21838 let parent_editor = cx.entity().downgrade();
21839 let subscription = prompt_editor.update(cx, |prompt_editor, _cx| {
21840 prompt_editor.register_action({
21841 let parent_editor = parent_editor.clone();
21842 move |_: &crate::actions::Newline, window, cx| {
21843 if let Some(editor) = parent_editor.upgrade() {
21844 editor.update(cx, |editor, cx| {
21845 editor.submit_diff_review_comment(window, cx);
21846 });
21847 }
21848 }
21849 })
21850 });
21851
21852 // Calculate initial height based on existing comments for this hunk
21853 let initial_height = self.calculate_overlay_height(&hunk_key, true, &buffer_snapshot);
21854
21855 // Create the overlay block
21856 let prompt_editor_for_render = prompt_editor.clone();
21857 let hunk_key_for_render = hunk_key.clone();
21858 let editor_handle = cx.entity().downgrade();
21859 let block = BlockProperties {
21860 style: BlockStyle::Sticky,
21861 placement: BlockPlacement::Below(anchor),
21862 height: Some(initial_height),
21863 render: Arc::new(move |cx| {
21864 Self::render_diff_review_overlay(
21865 &prompt_editor_for_render,
21866 &hunk_key_for_render,
21867 &editor_handle,
21868 cx,
21869 )
21870 }),
21871 priority: 0,
21872 };
21873
21874 let block_ids = self.insert_blocks([block], None, cx);
21875 let Some(block_id) = block_ids.into_iter().next() else {
21876 log::error!("Failed to insert diff review overlay block");
21877 return;
21878 };
21879
21880 self.diff_review_overlays.push(DiffReviewOverlay {
21881 anchor_range,
21882 block_id,
21883 prompt_editor: prompt_editor.clone(),
21884 hunk_key,
21885 comments_expanded: true,
21886 inline_edit_editors: HashMap::default(),
21887 inline_edit_subscriptions: HashMap::default(),
21888 user_avatar_uri,
21889 _subscription: subscription,
21890 });
21891
21892 // Focus the prompt editor
21893 let focus_handle = prompt_editor.focus_handle(cx);
21894 window.focus(&focus_handle, cx);
21895
21896 cx.notify();
21897 }
21898
21899 /// Dismisses all diff review overlays.
21900 pub fn dismiss_all_diff_review_overlays(&mut self, cx: &mut Context<Self>) {
21901 if self.diff_review_overlays.is_empty() {
21902 return;
21903 }
21904 let block_ids: HashSet<_> = self
21905 .diff_review_overlays
21906 .drain(..)
21907 .map(|overlay| overlay.block_id)
21908 .collect();
21909 self.remove_blocks(block_ids, None, cx);
21910 cx.notify();
21911 }
21912
21913 /// Dismisses overlays that have no comments stored for their hunks.
21914 /// Keeps overlays that have at least one comment.
21915 fn dismiss_overlays_without_comments(&mut self, cx: &mut Context<Self>) {
21916 let snapshot = self.buffer.read(cx).snapshot(cx);
21917
21918 // First, compute which overlays have comments (to avoid borrow issues with retain)
21919 let overlays_with_comments: Vec<bool> = self
21920 .diff_review_overlays
21921 .iter()
21922 .map(|overlay| self.hunk_comment_count(&overlay.hunk_key, &snapshot) > 0)
21923 .collect();
21924
21925 // Now collect block IDs to remove and retain overlays
21926 let mut block_ids_to_remove = HashSet::default();
21927 let mut index = 0;
21928 self.diff_review_overlays.retain(|overlay| {
21929 let has_comments = overlays_with_comments[index];
21930 index += 1;
21931 if !has_comments {
21932 block_ids_to_remove.insert(overlay.block_id);
21933 }
21934 has_comments
21935 });
21936
21937 if !block_ids_to_remove.is_empty() {
21938 self.remove_blocks(block_ids_to_remove, None, cx);
21939 cx.notify();
21940 }
21941 }
21942
21943 /// Refreshes the diff review overlay block to update its height and render function.
21944 /// Uses resize_blocks and replace_blocks to avoid visual flicker from remove+insert.
21945 fn refresh_diff_review_overlay_height(
21946 &mut self,
21947 hunk_key: &DiffHunkKey,
21948 _window: &mut Window,
21949 cx: &mut Context<Self>,
21950 ) {
21951 // Extract all needed data from overlay first to avoid borrow conflicts
21952 let snapshot = self.buffer.read(cx).snapshot(cx);
21953 let (comments_expanded, block_id, prompt_editor) = {
21954 let Some(overlay) = self
21955 .diff_review_overlays
21956 .iter()
21957 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
21958 else {
21959 return;
21960 };
21961
21962 (
21963 overlay.comments_expanded,
21964 overlay.block_id,
21965 overlay.prompt_editor.clone(),
21966 )
21967 };
21968
21969 // Calculate new height
21970 let snapshot = self.buffer.read(cx).snapshot(cx);
21971 let new_height = self.calculate_overlay_height(hunk_key, comments_expanded, &snapshot);
21972
21973 // Update the block height using resize_blocks (avoids flicker)
21974 let mut heights = HashMap::default();
21975 heights.insert(block_id, new_height);
21976 self.resize_blocks(heights, None, cx);
21977
21978 // Update the render function using replace_blocks (avoids flicker)
21979 let hunk_key_for_render = hunk_key.clone();
21980 let editor_handle = cx.entity().downgrade();
21981 let render: Arc<dyn Fn(&mut BlockContext) -> AnyElement + Send + Sync> =
21982 Arc::new(move |cx| {
21983 Self::render_diff_review_overlay(
21984 &prompt_editor,
21985 &hunk_key_for_render,
21986 &editor_handle,
21987 cx,
21988 )
21989 });
21990
21991 let mut renderers = HashMap::default();
21992 renderers.insert(block_id, render);
21993 self.replace_blocks(renderers, None, cx);
21994 }
21995
21996 /// Action handler for SubmitDiffReviewComment.
21997 pub fn submit_diff_review_comment_action(
21998 &mut self,
21999 _: &SubmitDiffReviewComment,
22000 window: &mut Window,
22001 cx: &mut Context<Self>,
22002 ) {
22003 self.submit_diff_review_comment(window, cx);
22004 }
22005
22006 /// Stores the diff review comment locally.
22007 /// Comments are stored per-hunk and can later be batch-submitted to the Agent panel.
22008 pub fn submit_diff_review_comment(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22009 // Find the overlay that currently has focus
22010 let overlay_index = self
22011 .diff_review_overlays
22012 .iter()
22013 .position(|overlay| overlay.prompt_editor.focus_handle(cx).is_focused(window));
22014 let Some(overlay_index) = overlay_index else {
22015 return;
22016 };
22017 let overlay = &self.diff_review_overlays[overlay_index];
22018
22019 let comment_text = overlay.prompt_editor.read(cx).text(cx).trim().to_string();
22020 if comment_text.is_empty() {
22021 return;
22022 }
22023
22024 let anchor_range = overlay.anchor_range.clone();
22025 let hunk_key = overlay.hunk_key.clone();
22026
22027 self.add_review_comment(hunk_key.clone(), comment_text, anchor_range, cx);
22028
22029 // Clear the prompt editor but keep the overlay open
22030 if let Some(overlay) = self.diff_review_overlays.get(overlay_index) {
22031 overlay.prompt_editor.update(cx, |editor, cx| {
22032 editor.clear(window, cx);
22033 });
22034 }
22035
22036 // Refresh the overlay to update the block height for the new comment
22037 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22038
22039 cx.notify();
22040 }
22041
22042 /// Returns the prompt editor for the diff review overlay, if one is active.
22043 /// This is primarily used for testing.
22044 pub fn diff_review_prompt_editor(&self) -> Option<&Entity<Editor>> {
22045 self.diff_review_overlays
22046 .first()
22047 .map(|overlay| &overlay.prompt_editor)
22048 }
22049
22050 /// Returns the line range for the first diff review overlay, if one is active.
22051 /// Returns (start_row, end_row) as physical line numbers in the underlying file.
22052 pub fn diff_review_line_range(&self, cx: &App) -> Option<(u32, u32)> {
22053 let overlay = self.diff_review_overlays.first()?;
22054 let snapshot = self.buffer.read(cx).snapshot(cx);
22055 let start_point = overlay.anchor_range.start.to_point(&snapshot);
22056 let end_point = overlay.anchor_range.end.to_point(&snapshot);
22057 let start_row = snapshot
22058 .point_to_buffer_point(start_point)
22059 .map(|(_, p, _)| p.row)
22060 .unwrap_or(start_point.row);
22061 let end_row = snapshot
22062 .point_to_buffer_point(end_point)
22063 .map(|(_, p, _)| p.row)
22064 .unwrap_or(end_point.row);
22065 Some((start_row, end_row))
22066 }
22067
22068 /// Sets whether the comments section is expanded in the diff review overlay.
22069 /// This is primarily used for testing.
22070 pub fn set_diff_review_comments_expanded(&mut self, expanded: bool, cx: &mut Context<Self>) {
22071 for overlay in &mut self.diff_review_overlays {
22072 overlay.comments_expanded = expanded;
22073 }
22074 cx.notify();
22075 }
22076
22077 /// Compares two DiffHunkKeys for equality by resolving their anchors.
22078 fn hunk_keys_match(a: &DiffHunkKey, b: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> bool {
22079 a.file_path == b.file_path
22080 && a.hunk_start_anchor.to_point(snapshot) == b.hunk_start_anchor.to_point(snapshot)
22081 }
22082
22083 /// Returns comments for a specific hunk, ordered by creation time.
22084 pub fn comments_for_hunk<'a>(
22085 &'a self,
22086 key: &DiffHunkKey,
22087 snapshot: &MultiBufferSnapshot,
22088 ) -> &'a [StoredReviewComment] {
22089 let key_point = key.hunk_start_anchor.to_point(snapshot);
22090 self.stored_review_comments
22091 .iter()
22092 .find(|(k, _)| {
22093 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
22094 })
22095 .map(|(_, comments)| comments.as_slice())
22096 .unwrap_or(&[])
22097 }
22098
22099 /// Returns the total count of stored review comments across all hunks.
22100 pub fn total_review_comment_count(&self) -> usize {
22101 self.stored_review_comments
22102 .iter()
22103 .map(|(_, v)| v.len())
22104 .sum()
22105 }
22106
22107 /// Returns the count of comments for a specific hunk.
22108 pub fn hunk_comment_count(&self, key: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> usize {
22109 let key_point = key.hunk_start_anchor.to_point(snapshot);
22110 self.stored_review_comments
22111 .iter()
22112 .find(|(k, _)| {
22113 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
22114 })
22115 .map(|(_, v)| v.len())
22116 .unwrap_or(0)
22117 }
22118
22119 /// Adds a new review comment to a specific hunk.
22120 pub fn add_review_comment(
22121 &mut self,
22122 hunk_key: DiffHunkKey,
22123 comment: String,
22124 anchor_range: Range<Anchor>,
22125 cx: &mut Context<Self>,
22126 ) -> usize {
22127 let id = self.next_review_comment_id;
22128 self.next_review_comment_id += 1;
22129
22130 let stored_comment = StoredReviewComment::new(id, comment, anchor_range);
22131
22132 let snapshot = self.buffer.read(cx).snapshot(cx);
22133 let key_point = hunk_key.hunk_start_anchor.to_point(&snapshot);
22134
22135 // Find existing entry for this hunk or add a new one
22136 if let Some((_, comments)) = self.stored_review_comments.iter_mut().find(|(k, _)| {
22137 k.file_path == hunk_key.file_path
22138 && k.hunk_start_anchor.to_point(&snapshot) == key_point
22139 }) {
22140 comments.push(stored_comment);
22141 } else {
22142 self.stored_review_comments
22143 .push((hunk_key, vec![stored_comment]));
22144 }
22145
22146 cx.emit(EditorEvent::ReviewCommentsChanged {
22147 total_count: self.total_review_comment_count(),
22148 });
22149 cx.notify();
22150 id
22151 }
22152
22153 /// Removes a review comment by ID from any hunk.
22154 pub fn remove_review_comment(&mut self, id: usize, cx: &mut Context<Self>) -> bool {
22155 for (_, comments) in self.stored_review_comments.iter_mut() {
22156 if let Some(index) = comments.iter().position(|c| c.id == id) {
22157 comments.remove(index);
22158 cx.emit(EditorEvent::ReviewCommentsChanged {
22159 total_count: self.total_review_comment_count(),
22160 });
22161 cx.notify();
22162 return true;
22163 }
22164 }
22165 false
22166 }
22167
22168 /// Updates a review comment's text by ID.
22169 pub fn update_review_comment(
22170 &mut self,
22171 id: usize,
22172 new_comment: String,
22173 cx: &mut Context<Self>,
22174 ) -> bool {
22175 for (_, comments) in self.stored_review_comments.iter_mut() {
22176 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
22177 comment.comment = new_comment;
22178 comment.is_editing = false;
22179 cx.emit(EditorEvent::ReviewCommentsChanged {
22180 total_count: self.total_review_comment_count(),
22181 });
22182 cx.notify();
22183 return true;
22184 }
22185 }
22186 false
22187 }
22188
22189 /// Sets a comment's editing state.
22190 pub fn set_comment_editing(&mut self, id: usize, is_editing: bool, cx: &mut Context<Self>) {
22191 for (_, comments) in self.stored_review_comments.iter_mut() {
22192 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
22193 comment.is_editing = is_editing;
22194 cx.notify();
22195 return;
22196 }
22197 }
22198 }
22199
22200 /// Takes all stored comments from all hunks, clearing the storage.
22201 /// Returns a Vec of (hunk_key, comments) pairs.
22202 pub fn take_all_review_comments(
22203 &mut self,
22204 cx: &mut Context<Self>,
22205 ) -> Vec<(DiffHunkKey, Vec<StoredReviewComment>)> {
22206 // Dismiss all overlays when taking comments (e.g., when sending to agent)
22207 self.dismiss_all_diff_review_overlays(cx);
22208 let comments = std::mem::take(&mut self.stored_review_comments);
22209 // Reset the ID counter since all comments have been taken
22210 self.next_review_comment_id = 0;
22211 cx.emit(EditorEvent::ReviewCommentsChanged { total_count: 0 });
22212 cx.notify();
22213 comments
22214 }
22215
22216 /// Removes review comments whose anchors are no longer valid or whose
22217 /// associated diff hunks no longer exist.
22218 ///
22219 /// This should be called when the buffer changes to prevent orphaned comments
22220 /// from accumulating.
22221 pub fn cleanup_orphaned_review_comments(&mut self, cx: &mut Context<Self>) {
22222 let snapshot = self.buffer.read(cx).snapshot(cx);
22223 let original_count = self.total_review_comment_count();
22224
22225 // Remove comments with invalid hunk anchors
22226 self.stored_review_comments
22227 .retain(|(hunk_key, _)| hunk_key.hunk_start_anchor.is_valid(&snapshot));
22228
22229 // Also clean up individual comments with invalid anchor ranges
22230 for (_, comments) in &mut self.stored_review_comments {
22231 comments.retain(|comment| {
22232 comment.range.start.is_valid(&snapshot) && comment.range.end.is_valid(&snapshot)
22233 });
22234 }
22235
22236 // Remove empty hunk entries
22237 self.stored_review_comments
22238 .retain(|(_, comments)| !comments.is_empty());
22239
22240 let new_count = self.total_review_comment_count();
22241 if new_count != original_count {
22242 cx.emit(EditorEvent::ReviewCommentsChanged {
22243 total_count: new_count,
22244 });
22245 cx.notify();
22246 }
22247 }
22248
22249 /// Toggles the expanded state of the comments section in the overlay.
22250 pub fn toggle_review_comments_expanded(
22251 &mut self,
22252 _: &ToggleReviewCommentsExpanded,
22253 window: &mut Window,
22254 cx: &mut Context<Self>,
22255 ) {
22256 // Find the overlay that currently has focus, or use the first one
22257 let overlay_info = self.diff_review_overlays.iter_mut().find_map(|overlay| {
22258 if overlay.prompt_editor.focus_handle(cx).is_focused(window) {
22259 overlay.comments_expanded = !overlay.comments_expanded;
22260 Some(overlay.hunk_key.clone())
22261 } else {
22262 None
22263 }
22264 });
22265
22266 // If no focused overlay found, toggle the first one
22267 let hunk_key = overlay_info.or_else(|| {
22268 self.diff_review_overlays.first_mut().map(|overlay| {
22269 overlay.comments_expanded = !overlay.comments_expanded;
22270 overlay.hunk_key.clone()
22271 })
22272 });
22273
22274 if let Some(hunk_key) = hunk_key {
22275 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22276 cx.notify();
22277 }
22278 }
22279
22280 /// Handles the EditReviewComment action - sets a comment into editing mode.
22281 pub fn edit_review_comment(
22282 &mut self,
22283 action: &EditReviewComment,
22284 window: &mut Window,
22285 cx: &mut Context<Self>,
22286 ) {
22287 let comment_id = action.id;
22288
22289 // Set the comment to editing mode
22290 self.set_comment_editing(comment_id, true, cx);
22291
22292 // Find the overlay that contains this comment and create an inline editor if needed
22293 // First, find which hunk this comment belongs to
22294 let hunk_key = self
22295 .stored_review_comments
22296 .iter()
22297 .find_map(|(key, comments)| {
22298 if comments.iter().any(|c| c.id == comment_id) {
22299 Some(key.clone())
22300 } else {
22301 None
22302 }
22303 });
22304
22305 let snapshot = self.buffer.read(cx).snapshot(cx);
22306 if let Some(hunk_key) = hunk_key {
22307 if let Some(overlay) = self
22308 .diff_review_overlays
22309 .iter_mut()
22310 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22311 {
22312 if let std::collections::hash_map::Entry::Vacant(entry) =
22313 overlay.inline_edit_editors.entry(comment_id)
22314 {
22315 // Find the comment text
22316 let comment_text = self
22317 .stored_review_comments
22318 .iter()
22319 .flat_map(|(_, comments)| comments)
22320 .find(|c| c.id == comment_id)
22321 .map(|c| c.comment.clone())
22322 .unwrap_or_default();
22323
22324 // Create inline editor
22325 let parent_editor = cx.entity().downgrade();
22326 let inline_editor = cx.new(|cx| {
22327 let mut editor = Editor::single_line(window, cx);
22328 editor.set_text(&*comment_text, window, cx);
22329 // Select all text for easy replacement
22330 editor.select_all(&crate::actions::SelectAll, window, cx);
22331 editor
22332 });
22333
22334 // Register the Newline action to confirm the edit
22335 let subscription = inline_editor.update(cx, |inline_editor, _cx| {
22336 inline_editor.register_action({
22337 let parent_editor = parent_editor.clone();
22338 move |_: &crate::actions::Newline, window, cx| {
22339 if let Some(editor) = parent_editor.upgrade() {
22340 editor.update(cx, |editor, cx| {
22341 editor.confirm_edit_review_comment(comment_id, window, cx);
22342 });
22343 }
22344 }
22345 })
22346 });
22347
22348 // Store the subscription to keep the action handler alive
22349 overlay
22350 .inline_edit_subscriptions
22351 .insert(comment_id, subscription);
22352
22353 // Focus the inline editor
22354 let focus_handle = inline_editor.focus_handle(cx);
22355 window.focus(&focus_handle, cx);
22356
22357 entry.insert(inline_editor);
22358 }
22359 }
22360 }
22361
22362 cx.notify();
22363 }
22364
22365 /// Confirms an inline edit of a review comment.
22366 pub fn confirm_edit_review_comment(
22367 &mut self,
22368 comment_id: usize,
22369 _window: &mut Window,
22370 cx: &mut Context<Self>,
22371 ) {
22372 // Get the new text from the inline editor
22373 // Find the overlay containing this comment's inline editor
22374 let snapshot = self.buffer.read(cx).snapshot(cx);
22375 let hunk_key = self
22376 .stored_review_comments
22377 .iter()
22378 .find_map(|(key, comments)| {
22379 if comments.iter().any(|c| c.id == comment_id) {
22380 Some(key.clone())
22381 } else {
22382 None
22383 }
22384 });
22385
22386 let new_text = hunk_key
22387 .as_ref()
22388 .and_then(|hunk_key| {
22389 self.diff_review_overlays
22390 .iter()
22391 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
22392 })
22393 .as_ref()
22394 .and_then(|overlay| overlay.inline_edit_editors.get(&comment_id))
22395 .map(|editor| editor.read(cx).text(cx).trim().to_string());
22396
22397 if let Some(new_text) = new_text {
22398 if !new_text.is_empty() {
22399 self.update_review_comment(comment_id, new_text, cx);
22400 }
22401 }
22402
22403 // Remove the inline editor and its subscription
22404 if let Some(hunk_key) = hunk_key {
22405 if let Some(overlay) = self
22406 .diff_review_overlays
22407 .iter_mut()
22408 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22409 {
22410 overlay.inline_edit_editors.remove(&comment_id);
22411 overlay.inline_edit_subscriptions.remove(&comment_id);
22412 }
22413 }
22414
22415 // Clear editing state
22416 self.set_comment_editing(comment_id, false, cx);
22417 }
22418
22419 /// Cancels an inline edit of a review comment.
22420 pub fn cancel_edit_review_comment(
22421 &mut self,
22422 comment_id: usize,
22423 _window: &mut Window,
22424 cx: &mut Context<Self>,
22425 ) {
22426 // Find which hunk this comment belongs to
22427 let hunk_key = self
22428 .stored_review_comments
22429 .iter()
22430 .find_map(|(key, comments)| {
22431 if comments.iter().any(|c| c.id == comment_id) {
22432 Some(key.clone())
22433 } else {
22434 None
22435 }
22436 });
22437
22438 // Remove the inline editor and its subscription
22439 if let Some(hunk_key) = hunk_key {
22440 let snapshot = self.buffer.read(cx).snapshot(cx);
22441 if let Some(overlay) = self
22442 .diff_review_overlays
22443 .iter_mut()
22444 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22445 {
22446 overlay.inline_edit_editors.remove(&comment_id);
22447 overlay.inline_edit_subscriptions.remove(&comment_id);
22448 }
22449 }
22450
22451 // Clear editing state
22452 self.set_comment_editing(comment_id, false, cx);
22453 }
22454
22455 /// Action handler for ConfirmEditReviewComment.
22456 pub fn confirm_edit_review_comment_action(
22457 &mut self,
22458 action: &ConfirmEditReviewComment,
22459 window: &mut Window,
22460 cx: &mut Context<Self>,
22461 ) {
22462 self.confirm_edit_review_comment(action.id, window, cx);
22463 }
22464
22465 /// Action handler for CancelEditReviewComment.
22466 pub fn cancel_edit_review_comment_action(
22467 &mut self,
22468 action: &CancelEditReviewComment,
22469 window: &mut Window,
22470 cx: &mut Context<Self>,
22471 ) {
22472 self.cancel_edit_review_comment(action.id, window, cx);
22473 }
22474
22475 /// Handles the DeleteReviewComment action - removes a comment.
22476 pub fn delete_review_comment(
22477 &mut self,
22478 action: &DeleteReviewComment,
22479 window: &mut Window,
22480 cx: &mut Context<Self>,
22481 ) {
22482 // Get the hunk key before removing the comment
22483 // Find the hunk key from the comment itself
22484 let comment_id = action.id;
22485 let hunk_key = self
22486 .stored_review_comments
22487 .iter()
22488 .find_map(|(key, comments)| {
22489 if comments.iter().any(|c| c.id == comment_id) {
22490 Some(key.clone())
22491 } else {
22492 None
22493 }
22494 });
22495
22496 // Also get it from the overlay for refresh purposes
22497 let overlay_hunk_key = self
22498 .diff_review_overlays
22499 .first()
22500 .map(|o| o.hunk_key.clone());
22501
22502 self.remove_review_comment(action.id, cx);
22503
22504 // Refresh the overlay height after removing a comment
22505 if let Some(hunk_key) = hunk_key.or(overlay_hunk_key) {
22506 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22507 }
22508 }
22509
22510 fn render_diff_review_overlay(
22511 prompt_editor: &Entity<Editor>,
22512 hunk_key: &DiffHunkKey,
22513 editor_handle: &WeakEntity<Editor>,
22514 cx: &mut BlockContext,
22515 ) -> AnyElement {
22516 fn format_line_ranges(ranges: &[(u32, u32)]) -> Option<String> {
22517 if ranges.is_empty() {
22518 return None;
22519 }
22520 let formatted: Vec<String> = ranges
22521 .iter()
22522 .map(|(start, end)| {
22523 let start_line = start + 1;
22524 let end_line = end + 1;
22525 if start_line == end_line {
22526 format!("Line {start_line}")
22527 } else {
22528 format!("Lines {start_line}-{end_line}")
22529 }
22530 })
22531 .collect();
22532 // Don't show label for single line in single excerpt
22533 if ranges.len() == 1 && ranges[0].0 == ranges[0].1 {
22534 return None;
22535 }
22536 Some(formatted.join(" ⋯ "))
22537 }
22538
22539 let theme = cx.theme();
22540 let colors = theme.colors();
22541
22542 let (comments, comments_expanded, inline_editors, user_avatar_uri, line_ranges) =
22543 editor_handle
22544 .upgrade()
22545 .map(|editor| {
22546 let editor = editor.read(cx);
22547 let snapshot = editor.buffer().read(cx).snapshot(cx);
22548 let comments = editor.comments_for_hunk(hunk_key, &snapshot).to_vec();
22549 let (expanded, editors, avatar_uri, line_ranges) = editor
22550 .diff_review_overlays
22551 .iter()
22552 .find(|overlay| {
22553 Editor::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot)
22554 })
22555 .map(|o| {
22556 let start_point = o.anchor_range.start.to_point(&snapshot);
22557 let end_point = o.anchor_range.end.to_point(&snapshot);
22558 // Get line ranges per excerpt to detect discontinuities
22559 let buffer_ranges =
22560 snapshot.range_to_buffer_ranges(start_point..end_point);
22561 let ranges: Vec<(u32, u32)> = buffer_ranges
22562 .iter()
22563 .map(|(buffer, range, _)| {
22564 let start = buffer.offset_to_point(range.start.0).row;
22565 let end = buffer.offset_to_point(range.end.0).row;
22566 (start, end)
22567 })
22568 .collect();
22569 (
22570 o.comments_expanded,
22571 o.inline_edit_editors.clone(),
22572 o.user_avatar_uri.clone(),
22573 if ranges.is_empty() {
22574 None
22575 } else {
22576 Some(ranges)
22577 },
22578 )
22579 })
22580 .unwrap_or((true, HashMap::default(), None, None));
22581 (comments, expanded, editors, avatar_uri, line_ranges)
22582 })
22583 .unwrap_or((Vec::new(), true, HashMap::default(), None, None));
22584
22585 let comment_count = comments.len();
22586 let avatar_size = px(20.);
22587 let action_icon_size = IconSize::XSmall;
22588
22589 v_flex()
22590 .w_full()
22591 .bg(colors.editor_background)
22592 .border_b_1()
22593 .border_color(colors.border)
22594 .px_2()
22595 .pb_2()
22596 .gap_2()
22597 // Line range indicator (only shown for multi-line selections or multiple excerpts)
22598 .when_some(line_ranges, |el, ranges| {
22599 let label = format_line_ranges(&ranges);
22600 if let Some(label) = label {
22601 el.child(
22602 h_flex()
22603 .w_full()
22604 .px_2()
22605 .child(Label::new(label).size(LabelSize::Small).color(Color::Muted)),
22606 )
22607 } else {
22608 el
22609 }
22610 })
22611 // Top row: editable input with user's avatar
22612 .child(
22613 h_flex()
22614 .w_full()
22615 .items_center()
22616 .gap_2()
22617 .px_2()
22618 .py_1p5()
22619 .rounded_md()
22620 .bg(colors.surface_background)
22621 .child(
22622 div()
22623 .size(avatar_size)
22624 .flex_shrink_0()
22625 .rounded_full()
22626 .overflow_hidden()
22627 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22628 Avatar::new(avatar_uri.clone())
22629 .size(avatar_size)
22630 .into_any_element()
22631 } else {
22632 Icon::new(IconName::Person)
22633 .size(IconSize::Small)
22634 .color(ui::Color::Muted)
22635 .into_any_element()
22636 }),
22637 )
22638 .child(
22639 div()
22640 .flex_1()
22641 .border_1()
22642 .border_color(colors.border)
22643 .rounded_md()
22644 .bg(colors.editor_background)
22645 .px_2()
22646 .py_1()
22647 .child(prompt_editor.clone()),
22648 )
22649 .child(
22650 h_flex()
22651 .flex_shrink_0()
22652 .gap_1()
22653 .child(
22654 IconButton::new("diff-review-close", IconName::Close)
22655 .icon_color(ui::Color::Muted)
22656 .icon_size(action_icon_size)
22657 .tooltip(Tooltip::text("Close"))
22658 .on_click(|_, window, cx| {
22659 window
22660 .dispatch_action(Box::new(crate::actions::Cancel), cx);
22661 }),
22662 )
22663 .child(
22664 IconButton::new("diff-review-add", IconName::Return)
22665 .icon_color(ui::Color::Muted)
22666 .icon_size(action_icon_size)
22667 .tooltip(Tooltip::text("Add comment"))
22668 .on_click(|_, window, cx| {
22669 window.dispatch_action(
22670 Box::new(crate::actions::SubmitDiffReviewComment),
22671 cx,
22672 );
22673 }),
22674 ),
22675 ),
22676 )
22677 // Expandable comments section (only shown when there are comments)
22678 .when(comment_count > 0, |el| {
22679 el.child(Self::render_comments_section(
22680 comments,
22681 comments_expanded,
22682 inline_editors,
22683 user_avatar_uri,
22684 avatar_size,
22685 action_icon_size,
22686 colors,
22687 ))
22688 })
22689 .into_any_element()
22690 }
22691
22692 fn render_comments_section(
22693 comments: Vec<StoredReviewComment>,
22694 expanded: bool,
22695 inline_editors: HashMap<usize, Entity<Editor>>,
22696 user_avatar_uri: Option<SharedUri>,
22697 avatar_size: Pixels,
22698 action_icon_size: IconSize,
22699 colors: &theme::ThemeColors,
22700 ) -> impl IntoElement {
22701 let comment_count = comments.len();
22702
22703 v_flex()
22704 .w_full()
22705 .gap_1()
22706 // Header with expand/collapse toggle
22707 .child(
22708 h_flex()
22709 .id("review-comments-header")
22710 .w_full()
22711 .items_center()
22712 .gap_1()
22713 .px_2()
22714 .py_1()
22715 .cursor_pointer()
22716 .rounded_md()
22717 .hover(|style| style.bg(colors.ghost_element_hover))
22718 .on_click(|_, window: &mut Window, cx| {
22719 window.dispatch_action(
22720 Box::new(crate::actions::ToggleReviewCommentsExpanded),
22721 cx,
22722 );
22723 })
22724 .child(
22725 Icon::new(if expanded {
22726 IconName::ChevronDown
22727 } else {
22728 IconName::ChevronRight
22729 })
22730 .size(IconSize::Small)
22731 .color(ui::Color::Muted),
22732 )
22733 .child(
22734 Label::new(format!(
22735 "{} Comment{}",
22736 comment_count,
22737 if comment_count == 1 { "" } else { "s" }
22738 ))
22739 .size(LabelSize::Small)
22740 .color(Color::Muted),
22741 ),
22742 )
22743 // Comments list (when expanded)
22744 .when(expanded, |el| {
22745 el.children(comments.into_iter().map(|comment| {
22746 let inline_editor = inline_editors.get(&comment.id).cloned();
22747 Self::render_comment_row(
22748 comment,
22749 inline_editor,
22750 user_avatar_uri.clone(),
22751 avatar_size,
22752 action_icon_size,
22753 colors,
22754 )
22755 }))
22756 })
22757 }
22758
22759 fn render_comment_row(
22760 comment: StoredReviewComment,
22761 inline_editor: Option<Entity<Editor>>,
22762 user_avatar_uri: Option<SharedUri>,
22763 avatar_size: Pixels,
22764 action_icon_size: IconSize,
22765 colors: &theme::ThemeColors,
22766 ) -> impl IntoElement {
22767 let comment_id = comment.id;
22768 let is_editing = inline_editor.is_some();
22769
22770 h_flex()
22771 .w_full()
22772 .items_center()
22773 .gap_2()
22774 .px_2()
22775 .py_1p5()
22776 .rounded_md()
22777 .bg(colors.surface_background)
22778 .child(
22779 div()
22780 .size(avatar_size)
22781 .flex_shrink_0()
22782 .rounded_full()
22783 .overflow_hidden()
22784 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22785 Avatar::new(avatar_uri.clone())
22786 .size(avatar_size)
22787 .into_any_element()
22788 } else {
22789 Icon::new(IconName::Person)
22790 .size(IconSize::Small)
22791 .color(ui::Color::Muted)
22792 .into_any_element()
22793 }),
22794 )
22795 .child(if let Some(editor) = inline_editor {
22796 // Inline edit mode: show an editable text field
22797 div()
22798 .flex_1()
22799 .border_1()
22800 .border_color(colors.border)
22801 .rounded_md()
22802 .bg(colors.editor_background)
22803 .px_2()
22804 .py_1()
22805 .child(editor)
22806 .into_any_element()
22807 } else {
22808 // Display mode: show the comment text
22809 div()
22810 .flex_1()
22811 .text_sm()
22812 .text_color(colors.text)
22813 .child(comment.comment)
22814 .into_any_element()
22815 })
22816 .child(if is_editing {
22817 // Editing mode: show close and confirm buttons
22818 h_flex()
22819 .gap_1()
22820 .child(
22821 IconButton::new(
22822 format!("diff-review-cancel-edit-{comment_id}"),
22823 IconName::Close,
22824 )
22825 .icon_color(ui::Color::Muted)
22826 .icon_size(action_icon_size)
22827 .tooltip(Tooltip::text("Cancel"))
22828 .on_click(move |_, window, cx| {
22829 window.dispatch_action(
22830 Box::new(crate::actions::CancelEditReviewComment {
22831 id: comment_id,
22832 }),
22833 cx,
22834 );
22835 }),
22836 )
22837 .child(
22838 IconButton::new(
22839 format!("diff-review-confirm-edit-{comment_id}"),
22840 IconName::Return,
22841 )
22842 .icon_color(ui::Color::Muted)
22843 .icon_size(action_icon_size)
22844 .tooltip(Tooltip::text("Confirm"))
22845 .on_click(move |_, window, cx| {
22846 window.dispatch_action(
22847 Box::new(crate::actions::ConfirmEditReviewComment {
22848 id: comment_id,
22849 }),
22850 cx,
22851 );
22852 }),
22853 )
22854 .into_any_element()
22855 } else {
22856 // Display mode: no action buttons for now (edit/delete not yet implemented)
22857 gpui::Empty.into_any_element()
22858 })
22859 }
22860
22861 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
22862 if self.display_map.read(cx).masked != masked {
22863 self.display_map.update(cx, |map, _| map.masked = masked);
22864 }
22865 cx.notify()
22866 }
22867
22868 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
22869 self.show_wrap_guides = Some(show_wrap_guides);
22870 cx.notify();
22871 }
22872
22873 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
22874 self.show_indent_guides = Some(show_indent_guides);
22875 cx.notify();
22876 }
22877
22878 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
22879 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
22880 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
22881 && let Some(dir) = file.abs_path(cx).parent()
22882 {
22883 return Some(dir.to_owned());
22884 }
22885 }
22886
22887 None
22888 }
22889
22890 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
22891 self.active_excerpt(cx)?
22892 .1
22893 .read(cx)
22894 .file()
22895 .and_then(|f| f.as_local())
22896 }
22897
22898 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
22899 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22900 let buffer = buffer.read(cx);
22901 if let Some(project_path) = buffer.project_path(cx) {
22902 let project = self.project()?.read(cx);
22903 project.absolute_path(&project_path, cx)
22904 } else {
22905 buffer
22906 .file()
22907 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
22908 }
22909 })
22910 }
22911
22912 pub fn reveal_in_finder(
22913 &mut self,
22914 _: &RevealInFileManager,
22915 _window: &mut Window,
22916 cx: &mut Context<Self>,
22917 ) {
22918 if let Some(path) = self.target_file_abs_path(cx) {
22919 if let Some(project) = self.project() {
22920 project.update(cx, |project, cx| project.reveal_path(&path, cx));
22921 } else {
22922 cx.reveal_path(&path);
22923 }
22924 }
22925 }
22926
22927 pub fn copy_path(
22928 &mut self,
22929 _: &zed_actions::workspace::CopyPath,
22930 _window: &mut Window,
22931 cx: &mut Context<Self>,
22932 ) {
22933 if let Some(path) = self.target_file_abs_path(cx)
22934 && let Some(path) = path.to_str()
22935 {
22936 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22937 } else {
22938 cx.propagate();
22939 }
22940 }
22941
22942 pub fn copy_relative_path(
22943 &mut self,
22944 _: &zed_actions::workspace::CopyRelativePath,
22945 _window: &mut Window,
22946 cx: &mut Context<Self>,
22947 ) {
22948 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22949 let project = self.project()?.read(cx);
22950 let path = buffer.read(cx).file()?.path();
22951 let path = path.display(project.path_style(cx));
22952 Some(path)
22953 }) {
22954 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22955 } else {
22956 cx.propagate();
22957 }
22958 }
22959
22960 /// Returns the project path for the editor's buffer, if any buffer is
22961 /// opened in the editor.
22962 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
22963 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
22964 buffer.read(cx).project_path(cx)
22965 } else {
22966 None
22967 }
22968 }
22969
22970 // Returns true if the editor handled a go-to-line request
22971 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
22972 maybe!({
22973 let breakpoint_store = self.breakpoint_store.as_ref()?;
22974
22975 let (active_stack_frame, debug_line_pane_id) = {
22976 let store = breakpoint_store.read(cx);
22977 let active_stack_frame = store.active_position().cloned();
22978 let debug_line_pane_id = store.active_debug_line_pane_id();
22979 (active_stack_frame, debug_line_pane_id)
22980 };
22981
22982 let Some(active_stack_frame) = active_stack_frame else {
22983 self.clear_row_highlights::<ActiveDebugLine>();
22984 return None;
22985 };
22986
22987 if let Some(debug_line_pane_id) = debug_line_pane_id {
22988 if let Some(workspace) = self
22989 .workspace
22990 .as_ref()
22991 .and_then(|(workspace, _)| workspace.upgrade())
22992 {
22993 let editor_pane_id = workspace
22994 .read(cx)
22995 .pane_for_item_id(cx.entity_id())
22996 .map(|pane| pane.entity_id());
22997
22998 if editor_pane_id.is_some_and(|id| id != debug_line_pane_id) {
22999 self.clear_row_highlights::<ActiveDebugLine>();
23000 return None;
23001 }
23002 }
23003 }
23004
23005 let position = active_stack_frame.position;
23006 let buffer_id = position.buffer_id?;
23007 let snapshot = self
23008 .project
23009 .as_ref()?
23010 .read(cx)
23011 .buffer_for_id(buffer_id, cx)?
23012 .read(cx)
23013 .snapshot();
23014
23015 let mut handled = false;
23016 for (id, _, ExcerptRange { context, .. }) in
23017 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
23018 {
23019 if context.start.cmp(&position, &snapshot).is_ge()
23020 || context.end.cmp(&position, &snapshot).is_lt()
23021 {
23022 continue;
23023 }
23024 let snapshot = self.buffer.read(cx).snapshot(cx);
23025 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
23026
23027 handled = true;
23028 self.clear_row_highlights::<ActiveDebugLine>();
23029
23030 self.go_to_line::<ActiveDebugLine>(
23031 multibuffer_anchor,
23032 Some(cx.theme().colors().editor_debugger_active_line_background),
23033 window,
23034 cx,
23035 );
23036
23037 cx.notify();
23038 }
23039
23040 handled.then_some(())
23041 })
23042 .is_some()
23043 }
23044
23045 pub fn copy_file_name_without_extension(
23046 &mut self,
23047 _: &CopyFileNameWithoutExtension,
23048 _: &mut Window,
23049 cx: &mut Context<Self>,
23050 ) {
23051 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
23052 let file = buffer.read(cx).file()?;
23053 file.path().file_stem()
23054 }) {
23055 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
23056 }
23057 }
23058
23059 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
23060 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
23061 let file = buffer.read(cx).file()?;
23062 Some(file.file_name(cx))
23063 }) {
23064 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
23065 }
23066 }
23067
23068 pub fn toggle_git_blame(
23069 &mut self,
23070 _: &::git::Blame,
23071 window: &mut Window,
23072 cx: &mut Context<Self>,
23073 ) {
23074 self.show_git_blame_gutter = !self.show_git_blame_gutter;
23075
23076 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
23077 self.start_git_blame(true, window, cx);
23078 }
23079
23080 cx.notify();
23081 }
23082
23083 pub fn toggle_git_blame_inline(
23084 &mut self,
23085 _: &ToggleGitBlameInline,
23086 window: &mut Window,
23087 cx: &mut Context<Self>,
23088 ) {
23089 self.toggle_git_blame_inline_internal(true, window, cx);
23090 cx.notify();
23091 }
23092
23093 pub fn open_git_blame_commit(
23094 &mut self,
23095 _: &OpenGitBlameCommit,
23096 window: &mut Window,
23097 cx: &mut Context<Self>,
23098 ) {
23099 self.open_git_blame_commit_internal(window, cx);
23100 }
23101
23102 fn open_git_blame_commit_internal(
23103 &mut self,
23104 window: &mut Window,
23105 cx: &mut Context<Self>,
23106 ) -> Option<()> {
23107 let blame = self.blame.as_ref()?;
23108 let snapshot = self.snapshot(window, cx);
23109 let cursor = self
23110 .selections
23111 .newest::<Point>(&snapshot.display_snapshot)
23112 .head();
23113 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
23114 let (_, blame_entry) = blame
23115 .update(cx, |blame, cx| {
23116 blame
23117 .blame_for_rows(
23118 &[RowInfo {
23119 buffer_id: Some(buffer.remote_id()),
23120 buffer_row: Some(point.row),
23121 ..Default::default()
23122 }],
23123 cx,
23124 )
23125 .next()
23126 })
23127 .flatten()?;
23128 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23129 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
23130 let workspace = self.workspace()?.downgrade();
23131 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
23132 None
23133 }
23134
23135 pub fn git_blame_inline_enabled(&self) -> bool {
23136 self.git_blame_inline_enabled
23137 }
23138
23139 pub fn toggle_selection_menu(
23140 &mut self,
23141 _: &ToggleSelectionMenu,
23142 _: &mut Window,
23143 cx: &mut Context<Self>,
23144 ) {
23145 self.show_selection_menu = self
23146 .show_selection_menu
23147 .map(|show_selections_menu| !show_selections_menu)
23148 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
23149
23150 cx.notify();
23151 }
23152
23153 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
23154 self.show_selection_menu
23155 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
23156 }
23157
23158 fn start_git_blame(
23159 &mut self,
23160 user_triggered: bool,
23161 window: &mut Window,
23162 cx: &mut Context<Self>,
23163 ) {
23164 if let Some(project) = self.project() {
23165 if let Some(buffer) = self.buffer().read(cx).as_singleton()
23166 && buffer.read(cx).file().is_none()
23167 {
23168 return;
23169 }
23170
23171 let focused = self.focus_handle(cx).contains_focused(window, cx);
23172
23173 let project = project.clone();
23174 let blame = cx
23175 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
23176 self.blame_subscription =
23177 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
23178 self.blame = Some(blame);
23179 }
23180 }
23181
23182 fn toggle_git_blame_inline_internal(
23183 &mut self,
23184 user_triggered: bool,
23185 window: &mut Window,
23186 cx: &mut Context<Self>,
23187 ) {
23188 if self.git_blame_inline_enabled {
23189 self.git_blame_inline_enabled = false;
23190 self.show_git_blame_inline = false;
23191 self.show_git_blame_inline_delay_task.take();
23192 } else {
23193 self.git_blame_inline_enabled = true;
23194 self.start_git_blame_inline(user_triggered, window, cx);
23195 }
23196
23197 cx.notify();
23198 }
23199
23200 fn start_git_blame_inline(
23201 &mut self,
23202 user_triggered: bool,
23203 window: &mut Window,
23204 cx: &mut Context<Self>,
23205 ) {
23206 self.start_git_blame(user_triggered, window, cx);
23207
23208 if ProjectSettings::get_global(cx)
23209 .git
23210 .inline_blame_delay()
23211 .is_some()
23212 {
23213 self.start_inline_blame_timer(window, cx);
23214 } else {
23215 self.show_git_blame_inline = true
23216 }
23217 }
23218
23219 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
23220 self.blame.as_ref()
23221 }
23222
23223 pub fn show_git_blame_gutter(&self) -> bool {
23224 self.show_git_blame_gutter
23225 }
23226
23227 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
23228 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
23229 }
23230
23231 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
23232 self.show_git_blame_inline
23233 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
23234 && !self.newest_selection_head_on_empty_line(cx)
23235 && self.has_blame_entries(cx)
23236 }
23237
23238 fn has_blame_entries(&self, cx: &App) -> bool {
23239 self.blame()
23240 .is_some_and(|blame| blame.read(cx).has_generated_entries())
23241 }
23242
23243 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
23244 let cursor_anchor = self.selections.newest_anchor().head();
23245
23246 let snapshot = self.buffer.read(cx).snapshot(cx);
23247 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
23248
23249 snapshot.line_len(buffer_row) == 0
23250 }
23251
23252 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
23253 let buffer_and_selection = maybe!({
23254 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
23255 let selection_range = selection.range();
23256
23257 let multi_buffer = self.buffer().read(cx);
23258 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
23259 let buffer_ranges = multi_buffer_snapshot
23260 .range_to_buffer_ranges(selection_range.start..=selection_range.end);
23261
23262 let (buffer, range, _) = if selection.reversed {
23263 buffer_ranges.first()
23264 } else {
23265 buffer_ranges.last()
23266 }?;
23267
23268 let buffer_range = range.to_point(buffer);
23269
23270 let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else {
23271 return Some((
23272 multi_buffer.buffer(buffer.remote_id()).unwrap(),
23273 buffer_range.start.row..buffer_range.end.row,
23274 ));
23275 };
23276
23277 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
23278 let start =
23279 buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.start, buffer);
23280 let end =
23281 buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.end, buffer);
23282
23283 Some((
23284 multi_buffer.buffer(buffer.remote_id()).unwrap(),
23285 start.row..end.row,
23286 ))
23287 });
23288
23289 let Some((buffer, selection)) = buffer_and_selection else {
23290 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
23291 };
23292
23293 let Some(project) = self.project() else {
23294 return Task::ready(Err(anyhow!("editor does not have project")));
23295 };
23296
23297 project.update(cx, |project, cx| {
23298 project.get_permalink_to_line(&buffer, selection, cx)
23299 })
23300 }
23301
23302 pub fn copy_permalink_to_line(
23303 &mut self,
23304 _: &CopyPermalinkToLine,
23305 window: &mut Window,
23306 cx: &mut Context<Self>,
23307 ) {
23308 let permalink_task = self.get_permalink_to_line(cx);
23309 let workspace = self.workspace();
23310
23311 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
23312 Ok(permalink) => {
23313 cx.update(|_, cx| {
23314 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
23315 })
23316 .ok();
23317 }
23318 Err(err) => {
23319 let message = format!("Failed to copy permalink: {err}");
23320
23321 anyhow::Result::<()>::Err(err).log_err();
23322
23323 if let Some(workspace) = workspace {
23324 workspace
23325 .update_in(cx, |workspace, _, cx| {
23326 struct CopyPermalinkToLine;
23327
23328 workspace.show_toast(
23329 Toast::new(
23330 NotificationId::unique::<CopyPermalinkToLine>(),
23331 message,
23332 ),
23333 cx,
23334 )
23335 })
23336 .ok();
23337 }
23338 }
23339 })
23340 .detach();
23341 }
23342
23343 pub fn copy_file_location(
23344 &mut self,
23345 _: &CopyFileLocation,
23346 _: &mut Window,
23347 cx: &mut Context<Self>,
23348 ) {
23349 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
23350
23351 let start_line = selection.start.row + 1;
23352 let end_line = selection.end.row + 1;
23353
23354 let end_line = if selection.end.column == 0 && end_line > start_line {
23355 end_line - 1
23356 } else {
23357 end_line
23358 };
23359
23360 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
23361 let project = self.project()?.read(cx);
23362 let file = buffer.read(cx).file()?;
23363 let path = file.path().display(project.path_style(cx));
23364
23365 let location = if start_line == end_line {
23366 format!("{path}:{start_line}")
23367 } else {
23368 format!("{path}:{start_line}-{end_line}")
23369 };
23370 Some(location)
23371 }) {
23372 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
23373 }
23374 }
23375
23376 pub fn open_permalink_to_line(
23377 &mut self,
23378 _: &OpenPermalinkToLine,
23379 window: &mut Window,
23380 cx: &mut Context<Self>,
23381 ) {
23382 let permalink_task = self.get_permalink_to_line(cx);
23383 let workspace = self.workspace();
23384
23385 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
23386 Ok(permalink) => {
23387 cx.update(|_, cx| {
23388 cx.open_url(permalink.as_ref());
23389 })
23390 .ok();
23391 }
23392 Err(err) => {
23393 let message = format!("Failed to open permalink: {err}");
23394
23395 anyhow::Result::<()>::Err(err).log_err();
23396
23397 if let Some(workspace) = workspace {
23398 workspace.update(cx, |workspace, cx| {
23399 struct OpenPermalinkToLine;
23400
23401 workspace.show_toast(
23402 Toast::new(NotificationId::unique::<OpenPermalinkToLine>(), message),
23403 cx,
23404 )
23405 });
23406 }
23407 }
23408 })
23409 .detach();
23410 }
23411
23412 pub fn insert_uuid_v4(
23413 &mut self,
23414 _: &InsertUuidV4,
23415 window: &mut Window,
23416 cx: &mut Context<Self>,
23417 ) {
23418 self.insert_uuid(UuidVersion::V4, window, cx);
23419 }
23420
23421 pub fn insert_uuid_v7(
23422 &mut self,
23423 _: &InsertUuidV7,
23424 window: &mut Window,
23425 cx: &mut Context<Self>,
23426 ) {
23427 self.insert_uuid(UuidVersion::V7, window, cx);
23428 }
23429
23430 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
23431 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
23432 self.transact(window, cx, |this, window, cx| {
23433 let edits = this
23434 .selections
23435 .all::<Point>(&this.display_snapshot(cx))
23436 .into_iter()
23437 .map(|selection| {
23438 let uuid = match version {
23439 UuidVersion::V4 => uuid::Uuid::new_v4(),
23440 UuidVersion::V7 => uuid::Uuid::now_v7(),
23441 };
23442
23443 (selection.range(), uuid.to_string())
23444 });
23445 this.edit(edits, cx);
23446 this.refresh_edit_prediction(true, false, window, cx);
23447 });
23448 }
23449
23450 pub fn open_selections_in_multibuffer(
23451 &mut self,
23452 _: &OpenSelectionsInMultibuffer,
23453 window: &mut Window,
23454 cx: &mut Context<Self>,
23455 ) {
23456 let multibuffer = self.buffer.read(cx);
23457
23458 let Some(buffer) = multibuffer.as_singleton() else {
23459 return;
23460 };
23461
23462 let Some(workspace) = self.workspace() else {
23463 return;
23464 };
23465
23466 let title = multibuffer.title(cx).to_string();
23467
23468 let locations = self
23469 .selections
23470 .all_anchors(&self.display_snapshot(cx))
23471 .iter()
23472 .map(|selection| {
23473 (
23474 buffer.clone(),
23475 (selection.start.text_anchor..selection.end.text_anchor)
23476 .to_point(buffer.read(cx)),
23477 )
23478 })
23479 .into_group_map();
23480
23481 cx.spawn_in(window, async move |_, cx| {
23482 workspace.update_in(cx, |workspace, window, cx| {
23483 Self::open_locations_in_multibuffer(
23484 workspace,
23485 locations,
23486 format!("Selections for '{title}'"),
23487 false,
23488 false,
23489 MultibufferSelectionMode::All,
23490 window,
23491 cx,
23492 );
23493 })
23494 })
23495 .detach();
23496 }
23497
23498 /// Adds a row highlight for the given range. If a row has multiple highlights, the
23499 /// last highlight added will be used.
23500 ///
23501 /// If the range ends at the beginning of a line, then that line will not be highlighted.
23502 pub fn highlight_rows<T: 'static>(
23503 &mut self,
23504 range: Range<Anchor>,
23505 color: Hsla,
23506 options: RowHighlightOptions,
23507 cx: &mut Context<Self>,
23508 ) {
23509 let snapshot = self.buffer().read(cx).snapshot(cx);
23510 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23511 let ix = row_highlights.binary_search_by(|highlight| {
23512 Ordering::Equal
23513 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
23514 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
23515 });
23516
23517 if let Err(mut ix) = ix {
23518 let index = post_inc(&mut self.highlight_order);
23519
23520 // If this range intersects with the preceding highlight, then merge it with
23521 // the preceding highlight. Otherwise insert a new highlight.
23522 let mut merged = false;
23523 if ix > 0 {
23524 let prev_highlight = &mut row_highlights[ix - 1];
23525 if prev_highlight
23526 .range
23527 .end
23528 .cmp(&range.start, &snapshot)
23529 .is_ge()
23530 {
23531 ix -= 1;
23532 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
23533 prev_highlight.range.end = range.end;
23534 }
23535 merged = true;
23536 prev_highlight.index = index;
23537 prev_highlight.color = color;
23538 prev_highlight.options = options;
23539 }
23540 }
23541
23542 if !merged {
23543 row_highlights.insert(
23544 ix,
23545 RowHighlight {
23546 range,
23547 index,
23548 color,
23549 options,
23550 type_id: TypeId::of::<T>(),
23551 },
23552 );
23553 }
23554
23555 // If any of the following highlights intersect with this one, merge them.
23556 while let Some(next_highlight) = row_highlights.get(ix + 1) {
23557 let highlight = &row_highlights[ix];
23558 if next_highlight
23559 .range
23560 .start
23561 .cmp(&highlight.range.end, &snapshot)
23562 .is_le()
23563 {
23564 if next_highlight
23565 .range
23566 .end
23567 .cmp(&highlight.range.end, &snapshot)
23568 .is_gt()
23569 {
23570 row_highlights[ix].range.end = next_highlight.range.end;
23571 }
23572 row_highlights.remove(ix + 1);
23573 } else {
23574 break;
23575 }
23576 }
23577 }
23578 }
23579
23580 /// Remove any highlighted row ranges of the given type that intersect the
23581 /// given ranges.
23582 pub fn remove_highlighted_rows<T: 'static>(
23583 &mut self,
23584 ranges_to_remove: Vec<Range<Anchor>>,
23585 cx: &mut Context<Self>,
23586 ) {
23587 let snapshot = self.buffer().read(cx).snapshot(cx);
23588 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23589 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23590 row_highlights.retain(|highlight| {
23591 while let Some(range_to_remove) = ranges_to_remove.peek() {
23592 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
23593 Ordering::Less | Ordering::Equal => {
23594 ranges_to_remove.next();
23595 }
23596 Ordering::Greater => {
23597 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
23598 Ordering::Less | Ordering::Equal => {
23599 return false;
23600 }
23601 Ordering::Greater => break,
23602 }
23603 }
23604 }
23605 }
23606
23607 true
23608 })
23609 }
23610
23611 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
23612 pub fn clear_row_highlights<T: 'static>(&mut self) {
23613 self.highlighted_rows.remove(&TypeId::of::<T>());
23614 }
23615
23616 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
23617 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
23618 self.highlighted_rows
23619 .get(&TypeId::of::<T>())
23620 .map_or(&[] as &[_], |vec| vec.as_slice())
23621 .iter()
23622 .map(|highlight| (highlight.range.clone(), highlight.color))
23623 }
23624
23625 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
23626 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
23627 /// Allows to ignore certain kinds of highlights.
23628 pub fn highlighted_display_rows(
23629 &self,
23630 window: &mut Window,
23631 cx: &mut App,
23632 ) -> BTreeMap<DisplayRow, LineHighlight> {
23633 let snapshot = self.snapshot(window, cx);
23634 let mut used_highlight_orders = HashMap::default();
23635 self.highlighted_rows
23636 .iter()
23637 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
23638 .fold(
23639 BTreeMap::<DisplayRow, LineHighlight>::new(),
23640 |mut unique_rows, highlight| {
23641 let start = highlight.range.start.to_display_point(&snapshot);
23642 let end = highlight.range.end.to_display_point(&snapshot);
23643 let start_row = start.row().0;
23644 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
23645 {
23646 end.row().0.saturating_sub(1)
23647 } else {
23648 end.row().0
23649 };
23650 for row in start_row..=end_row {
23651 let used_index =
23652 used_highlight_orders.entry(row).or_insert(highlight.index);
23653 if highlight.index >= *used_index {
23654 *used_index = highlight.index;
23655 unique_rows.insert(
23656 DisplayRow(row),
23657 LineHighlight {
23658 include_gutter: highlight.options.include_gutter,
23659 border: None,
23660 background: highlight.color.into(),
23661 type_id: Some(highlight.type_id),
23662 },
23663 );
23664 }
23665 }
23666 unique_rows
23667 },
23668 )
23669 }
23670
23671 pub fn highlighted_display_row_for_autoscroll(
23672 &self,
23673 snapshot: &DisplaySnapshot,
23674 ) -> Option<DisplayRow> {
23675 self.highlighted_rows
23676 .values()
23677 .flat_map(|highlighted_rows| highlighted_rows.iter())
23678 .filter_map(|highlight| {
23679 if highlight.options.autoscroll {
23680 Some(highlight.range.start.to_display_point(snapshot).row())
23681 } else {
23682 None
23683 }
23684 })
23685 .min()
23686 }
23687
23688 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
23689 self.highlight_background(
23690 HighlightKey::SearchWithinRange,
23691 ranges,
23692 |_, colors| colors.colors().editor_document_highlight_read_background,
23693 cx,
23694 )
23695 }
23696
23697 pub fn set_breadcrumb_header(&mut self, new_header: String) {
23698 self.breadcrumb_header = Some(new_header);
23699 }
23700
23701 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
23702 self.clear_background_highlights(HighlightKey::SearchWithinRange, cx);
23703 }
23704
23705 pub fn highlight_background(
23706 &mut self,
23707 key: HighlightKey,
23708 ranges: &[Range<Anchor>],
23709 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
23710 cx: &mut Context<Self>,
23711 ) {
23712 self.background_highlights
23713 .insert(key, (Arc::new(color_fetcher), Arc::from(ranges)));
23714 self.scrollbar_marker_state.dirty = true;
23715 cx.notify();
23716 }
23717
23718 pub fn clear_background_highlights(
23719 &mut self,
23720 key: HighlightKey,
23721 cx: &mut Context<Self>,
23722 ) -> Option<BackgroundHighlight> {
23723 let text_highlights = self.background_highlights.remove(&key)?;
23724 if !text_highlights.1.is_empty() {
23725 self.scrollbar_marker_state.dirty = true;
23726 cx.notify();
23727 }
23728 Some(text_highlights)
23729 }
23730
23731 pub fn highlight_gutter<T: 'static>(
23732 &mut self,
23733 ranges: impl Into<Vec<Range<Anchor>>>,
23734 color_fetcher: fn(&App) -> Hsla,
23735 cx: &mut Context<Self>,
23736 ) {
23737 self.gutter_highlights
23738 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
23739 cx.notify();
23740 }
23741
23742 pub fn clear_gutter_highlights<T: 'static>(
23743 &mut self,
23744 cx: &mut Context<Self>,
23745 ) -> Option<GutterHighlight> {
23746 cx.notify();
23747 self.gutter_highlights.remove(&TypeId::of::<T>())
23748 }
23749
23750 pub fn insert_gutter_highlight<T: 'static>(
23751 &mut self,
23752 range: Range<Anchor>,
23753 color_fetcher: fn(&App) -> Hsla,
23754 cx: &mut Context<Self>,
23755 ) {
23756 let snapshot = self.buffer().read(cx).snapshot(cx);
23757 let mut highlights = self
23758 .gutter_highlights
23759 .remove(&TypeId::of::<T>())
23760 .map(|(_, highlights)| highlights)
23761 .unwrap_or_default();
23762 let ix = highlights.binary_search_by(|highlight| {
23763 Ordering::Equal
23764 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
23765 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
23766 });
23767 if let Err(ix) = ix {
23768 highlights.insert(ix, range);
23769 }
23770 self.gutter_highlights
23771 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
23772 }
23773
23774 pub fn remove_gutter_highlights<T: 'static>(
23775 &mut self,
23776 ranges_to_remove: Vec<Range<Anchor>>,
23777 cx: &mut Context<Self>,
23778 ) {
23779 let snapshot = self.buffer().read(cx).snapshot(cx);
23780 let Some((color_fetcher, mut gutter_highlights)) =
23781 self.gutter_highlights.remove(&TypeId::of::<T>())
23782 else {
23783 return;
23784 };
23785 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23786 gutter_highlights.retain(|highlight| {
23787 while let Some(range_to_remove) = ranges_to_remove.peek() {
23788 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
23789 Ordering::Less | Ordering::Equal => {
23790 ranges_to_remove.next();
23791 }
23792 Ordering::Greater => {
23793 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
23794 Ordering::Less | Ordering::Equal => {
23795 return false;
23796 }
23797 Ordering::Greater => break,
23798 }
23799 }
23800 }
23801 }
23802
23803 true
23804 });
23805 self.gutter_highlights
23806 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
23807 }
23808
23809 #[cfg(any(test, feature = "test-support"))]
23810 pub fn all_text_highlights(
23811 &self,
23812 window: &mut Window,
23813 cx: &mut Context<Self>,
23814 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
23815 let snapshot = self.snapshot(window, cx);
23816 self.display_map.update(cx, |display_map, _| {
23817 display_map
23818 .all_text_highlights()
23819 .map(|(_, highlight)| {
23820 let (style, ranges) = highlight.as_ref();
23821 (
23822 *style,
23823 ranges
23824 .iter()
23825 .map(|range| range.clone().to_display_points(&snapshot))
23826 .collect(),
23827 )
23828 })
23829 .collect()
23830 })
23831 }
23832
23833 #[cfg(any(test, feature = "test-support"))]
23834 pub fn all_text_background_highlights(
23835 &self,
23836 window: &mut Window,
23837 cx: &mut Context<Self>,
23838 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23839 let snapshot = self.snapshot(window, cx);
23840 let buffer = &snapshot.buffer_snapshot();
23841 let start = buffer.anchor_before(MultiBufferOffset(0));
23842 let end = buffer.anchor_after(buffer.len());
23843 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
23844 }
23845
23846 #[cfg(any(test, feature = "test-support"))]
23847 pub fn sorted_background_highlights_in_range(
23848 &self,
23849 search_range: Range<Anchor>,
23850 display_snapshot: &DisplaySnapshot,
23851 theme: &Theme,
23852 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23853 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
23854 res.sort_by(|a, b| {
23855 a.0.start
23856 .cmp(&b.0.start)
23857 .then_with(|| a.0.end.cmp(&b.0.end))
23858 .then_with(|| a.1.cmp(&b.1))
23859 });
23860 res
23861 }
23862
23863 #[cfg(any(test, feature = "test-support"))]
23864 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
23865 let snapshot = self.buffer().read(cx).snapshot(cx);
23866
23867 let highlights = self
23868 .background_highlights
23869 .get(&HighlightKey::BufferSearchHighlights);
23870
23871 if let Some((_color, ranges)) = highlights {
23872 ranges
23873 .iter()
23874 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
23875 .collect_vec()
23876 } else {
23877 vec![]
23878 }
23879 }
23880
23881 fn document_highlights_for_position<'a>(
23882 &'a self,
23883 position: Anchor,
23884 buffer: &'a MultiBufferSnapshot,
23885 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
23886 let read_highlights = self
23887 .background_highlights
23888 .get(&HighlightKey::DocumentHighlightRead)
23889 .map(|h| &h.1);
23890 let write_highlights = self
23891 .background_highlights
23892 .get(&HighlightKey::DocumentHighlightWrite)
23893 .map(|h| &h.1);
23894 let left_position = position.bias_left(buffer);
23895 let right_position = position.bias_right(buffer);
23896 read_highlights
23897 .into_iter()
23898 .chain(write_highlights)
23899 .flat_map(move |ranges| {
23900 let start_ix = match ranges.binary_search_by(|probe| {
23901 let cmp = probe.end.cmp(&left_position, buffer);
23902 if cmp.is_ge() {
23903 Ordering::Greater
23904 } else {
23905 Ordering::Less
23906 }
23907 }) {
23908 Ok(i) | Err(i) => i,
23909 };
23910
23911 ranges[start_ix..]
23912 .iter()
23913 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
23914 })
23915 }
23916
23917 pub fn has_background_highlights(&self, key: HighlightKey) -> bool {
23918 self.background_highlights
23919 .get(&key)
23920 .is_some_and(|(_, highlights)| !highlights.is_empty())
23921 }
23922
23923 /// Returns all background highlights for a given range.
23924 ///
23925 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
23926 pub fn background_highlights_in_range(
23927 &self,
23928 search_range: Range<Anchor>,
23929 display_snapshot: &DisplaySnapshot,
23930 theme: &Theme,
23931 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23932 let mut results = Vec::new();
23933 for (color_fetcher, ranges) in self.background_highlights.values() {
23934 let start_ix = match ranges.binary_search_by(|probe| {
23935 let cmp = probe
23936 .end
23937 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23938 if cmp.is_gt() {
23939 Ordering::Greater
23940 } else {
23941 Ordering::Less
23942 }
23943 }) {
23944 Ok(i) | Err(i) => i,
23945 };
23946 for (index, range) in ranges[start_ix..].iter().enumerate() {
23947 if range
23948 .start
23949 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23950 .is_ge()
23951 {
23952 break;
23953 }
23954
23955 let color = color_fetcher(&(start_ix + index), theme);
23956 let start = range.start.to_display_point(display_snapshot);
23957 let end = range.end.to_display_point(display_snapshot);
23958 results.push((start..end, color))
23959 }
23960 }
23961 results
23962 }
23963
23964 pub fn gutter_highlights_in_range(
23965 &self,
23966 search_range: Range<Anchor>,
23967 display_snapshot: &DisplaySnapshot,
23968 cx: &App,
23969 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23970 let mut results = Vec::new();
23971 for (color_fetcher, ranges) in self.gutter_highlights.values() {
23972 let color = color_fetcher(cx);
23973 let start_ix = match ranges.binary_search_by(|probe| {
23974 let cmp = probe
23975 .end
23976 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23977 if cmp.is_gt() {
23978 Ordering::Greater
23979 } else {
23980 Ordering::Less
23981 }
23982 }) {
23983 Ok(i) | Err(i) => i,
23984 };
23985 for range in &ranges[start_ix..] {
23986 if range
23987 .start
23988 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23989 .is_ge()
23990 {
23991 break;
23992 }
23993
23994 let start = range.start.to_display_point(display_snapshot);
23995 let end = range.end.to_display_point(display_snapshot);
23996 results.push((start..end, color))
23997 }
23998 }
23999 results
24000 }
24001
24002 /// Get the text ranges corresponding to the redaction query
24003 pub fn redacted_ranges(
24004 &self,
24005 search_range: Range<Anchor>,
24006 display_snapshot: &DisplaySnapshot,
24007 cx: &App,
24008 ) -> Vec<Range<DisplayPoint>> {
24009 display_snapshot
24010 .buffer_snapshot()
24011 .redacted_ranges(search_range, |file| {
24012 if let Some(file) = file {
24013 file.is_private()
24014 && EditorSettings::get(
24015 Some(SettingsLocation {
24016 worktree_id: file.worktree_id(cx),
24017 path: file.path().as_ref(),
24018 }),
24019 cx,
24020 )
24021 .redact_private_values
24022 } else {
24023 false
24024 }
24025 })
24026 .map(|range| {
24027 range.start.to_display_point(display_snapshot)
24028 ..range.end.to_display_point(display_snapshot)
24029 })
24030 .collect()
24031 }
24032
24033 pub fn highlight_text_key(
24034 &mut self,
24035 key: HighlightKey,
24036 ranges: Vec<Range<Anchor>>,
24037 style: HighlightStyle,
24038 merge: bool,
24039 cx: &mut Context<Self>,
24040 ) {
24041 self.display_map.update(cx, |map, cx| {
24042 map.highlight_text(key, ranges, style, merge, cx);
24043 });
24044 cx.notify();
24045 }
24046
24047 pub fn highlight_text(
24048 &mut self,
24049 key: HighlightKey,
24050 ranges: Vec<Range<Anchor>>,
24051 style: HighlightStyle,
24052 cx: &mut Context<Self>,
24053 ) {
24054 self.display_map.update(cx, |map, cx| {
24055 map.highlight_text(key, ranges, style, false, cx)
24056 });
24057 cx.notify();
24058 }
24059
24060 pub fn text_highlights<'a>(
24061 &'a self,
24062 key: HighlightKey,
24063 cx: &'a App,
24064 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
24065 self.display_map.read(cx).text_highlights(key)
24066 }
24067
24068 pub fn clear_highlights(&mut self, key: HighlightKey, cx: &mut Context<Self>) {
24069 let cleared = self
24070 .display_map
24071 .update(cx, |map, _| map.clear_highlights(key));
24072 if cleared {
24073 cx.notify();
24074 }
24075 }
24076
24077 pub fn clear_highlights_with(
24078 &mut self,
24079 f: &mut dyn FnMut(&HighlightKey) -> bool,
24080 cx: &mut Context<Self>,
24081 ) {
24082 let cleared = self
24083 .display_map
24084 .update(cx, |map, _| map.clear_highlights_with(f));
24085 if cleared {
24086 cx.notify();
24087 }
24088 }
24089
24090 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
24091 (self.read_only(cx) || self.blink_manager.read(cx).visible())
24092 && self.focus_handle.is_focused(window)
24093 }
24094
24095 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
24096 self.show_cursor_when_unfocused = is_enabled;
24097 cx.notify();
24098 }
24099
24100 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
24101 cx.notify();
24102 }
24103
24104 fn on_debug_session_event(
24105 &mut self,
24106 _session: Entity<Session>,
24107 event: &SessionEvent,
24108 cx: &mut Context<Self>,
24109 ) {
24110 if let SessionEvent::InvalidateInlineValue = event {
24111 self.refresh_inline_values(cx);
24112 }
24113 }
24114
24115 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
24116 let Some(semantics) = self.semantics_provider.clone() else {
24117 return;
24118 };
24119
24120 if !self.inline_value_cache.enabled {
24121 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
24122 self.splice_inlays(&inlays, Vec::new(), cx);
24123 return;
24124 }
24125
24126 let current_execution_position = self
24127 .highlighted_rows
24128 .get(&TypeId::of::<ActiveDebugLine>())
24129 .and_then(|lines| lines.last().map(|line| line.range.end));
24130
24131 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
24132 let inline_values = editor
24133 .update(cx, |editor, cx| {
24134 let Some(current_execution_position) = current_execution_position else {
24135 return Some(Task::ready(Ok(Vec::new())));
24136 };
24137
24138 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
24139 let snapshot = buffer.snapshot(cx);
24140
24141 let excerpt = snapshot.excerpt_containing(
24142 current_execution_position..current_execution_position,
24143 )?;
24144
24145 editor.buffer.read(cx).buffer(excerpt.buffer_id())
24146 })?;
24147
24148 if current_execution_position
24149 .text_anchor
24150 .buffer_id
24151 .is_some_and(|id| id != buffer.read(cx).remote_id())
24152 {
24153 return Some(Task::ready(Ok(Vec::new())));
24154 }
24155
24156 let range =
24157 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
24158
24159 semantics.inline_values(buffer, range, cx)
24160 })
24161 .ok()
24162 .flatten()?
24163 .await
24164 .context("refreshing debugger inlays")
24165 .log_err()?;
24166
24167 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
24168
24169 for (buffer_id, inline_value) in inline_values
24170 .into_iter()
24171 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
24172 {
24173 buffer_inline_values
24174 .entry(buffer_id)
24175 .or_default()
24176 .push(inline_value);
24177 }
24178
24179 editor
24180 .update(cx, |editor, cx| {
24181 let snapshot = editor.buffer.read(cx).snapshot(cx);
24182 let mut new_inlays = Vec::default();
24183
24184 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
24185 let buffer_id = buffer_snapshot.remote_id();
24186 buffer_inline_values
24187 .get(&buffer_id)
24188 .into_iter()
24189 .flatten()
24190 .for_each(|hint| {
24191 let inlay = Inlay::debugger(
24192 post_inc(&mut editor.next_inlay_id),
24193 Anchor::in_buffer(excerpt_id, hint.position),
24194 hint.text(),
24195 );
24196 if !inlay.text().chars().contains(&'\n') {
24197 new_inlays.push(inlay);
24198 }
24199 });
24200 }
24201
24202 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
24203 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
24204
24205 editor.splice_inlays(&inlay_ids, new_inlays, cx);
24206 })
24207 .ok()?;
24208 Some(())
24209 });
24210 }
24211
24212 fn on_buffer_event(
24213 &mut self,
24214 multibuffer: &Entity<MultiBuffer>,
24215 event: &multi_buffer::Event,
24216 window: &mut Window,
24217 cx: &mut Context<Self>,
24218 ) {
24219 match event {
24220 multi_buffer::Event::Edited {
24221 edited_buffer,
24222 is_local,
24223 } => {
24224 self.scrollbar_marker_state.dirty = true;
24225 self.active_indent_guides_state.dirty = true;
24226 self.refresh_active_diagnostics(cx);
24227 self.refresh_code_actions(window, cx);
24228 self.refresh_single_line_folds(window, cx);
24229 let snapshot = self.snapshot(window, cx);
24230 self.refresh_matching_bracket_highlights(&snapshot, cx);
24231 self.refresh_outline_symbols_at_cursor(cx);
24232 self.refresh_sticky_headers(&snapshot, cx);
24233 if *is_local && self.has_active_edit_prediction() {
24234 self.update_visible_edit_prediction(window, cx);
24235 }
24236
24237 // Clean up orphaned review comments after edits
24238 self.cleanup_orphaned_review_comments(cx);
24239
24240 if let Some(buffer) = edited_buffer {
24241 if buffer.read(cx).file().is_none() {
24242 cx.emit(EditorEvent::TitleChanged);
24243 }
24244
24245 if self.project.is_some() {
24246 let buffer_id = buffer.read(cx).remote_id();
24247 self.register_buffer(buffer_id, cx);
24248 self.update_lsp_data(Some(buffer_id), window, cx);
24249 self.refresh_inlay_hints(
24250 InlayHintRefreshReason::BufferEdited(buffer_id),
24251 cx,
24252 );
24253 }
24254 }
24255
24256 cx.emit(EditorEvent::BufferEdited);
24257 cx.emit(SearchEvent::MatchesInvalidated);
24258
24259 let Some(project) = &self.project else { return };
24260 let (telemetry, is_via_ssh) = {
24261 let project = project.read(cx);
24262 let telemetry = project.client().telemetry().clone();
24263 let is_via_ssh = project.is_via_remote_server();
24264 (telemetry, is_via_ssh)
24265 };
24266 telemetry.log_edit_event("editor", is_via_ssh);
24267 }
24268 multi_buffer::Event::ExcerptsAdded {
24269 buffer,
24270 predecessor,
24271 excerpts,
24272 } => {
24273 let buffer_id = buffer.read(cx).remote_id();
24274 if self.buffer.read(cx).diff_for(buffer_id).is_none()
24275 && let Some(project) = &self.project
24276 {
24277 update_uncommitted_diff_for_buffer(
24278 cx.entity(),
24279 project,
24280 [buffer.clone()],
24281 self.buffer.clone(),
24282 cx,
24283 )
24284 .detach();
24285 }
24286 self.semantic_token_state
24287 .invalidate_buffer(&buffer.read(cx).remote_id());
24288 self.update_lsp_data(Some(buffer_id), window, cx);
24289 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
24290 self.refresh_runnables(None, window, cx);
24291 self.colorize_brackets(false, cx);
24292 self.refresh_selected_text_highlights(&self.display_snapshot(cx), true, window, cx);
24293 cx.emit(EditorEvent::ExcerptsAdded {
24294 buffer: buffer.clone(),
24295 predecessor: *predecessor,
24296 excerpts: excerpts.clone(),
24297 });
24298 }
24299 multi_buffer::Event::ExcerptsRemoved {
24300 ids,
24301 removed_buffer_ids,
24302 } => {
24303 if let Some(inlay_hints) = &mut self.inlay_hints {
24304 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
24305 }
24306 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
24307 for buffer_id in removed_buffer_ids {
24308 self.registered_buffers.remove(buffer_id);
24309 self.clear_runnables(Some(*buffer_id));
24310 self.semantic_token_state.invalidate_buffer(buffer_id);
24311 self.display_map.update(cx, |display_map, cx| {
24312 display_map.invalidate_semantic_highlights(*buffer_id);
24313 display_map.clear_lsp_folding_ranges(*buffer_id, cx);
24314 });
24315 }
24316
24317 self.display_map.update(cx, |display_map, cx| {
24318 display_map.unfold_buffers(removed_buffer_ids.iter().copied(), cx);
24319 });
24320
24321 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24322 cx.emit(EditorEvent::ExcerptsRemoved {
24323 ids: ids.clone(),
24324 removed_buffer_ids: removed_buffer_ids.clone(),
24325 });
24326 }
24327 multi_buffer::Event::ExcerptsEdited {
24328 excerpt_ids,
24329 buffer_ids,
24330 } => {
24331 self.display_map.update(cx, |map, cx| {
24332 map.unfold_buffers(buffer_ids.iter().copied(), cx)
24333 });
24334 cx.emit(EditorEvent::ExcerptsEdited {
24335 ids: excerpt_ids.clone(),
24336 });
24337 }
24338 multi_buffer::Event::ExcerptsExpanded { ids } => {
24339 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
24340 self.refresh_document_highlights(cx);
24341 let snapshot = multibuffer.read(cx).snapshot(cx);
24342 for id in ids {
24343 self.bracket_fetched_tree_sitter_chunks.remove(id);
24344 if let Some(buffer) = snapshot.buffer_for_excerpt(*id) {
24345 self.semantic_token_state
24346 .invalidate_buffer(&buffer.remote_id());
24347 }
24348 }
24349 self.colorize_brackets(false, cx);
24350 self.update_lsp_data(None, window, cx);
24351 self.refresh_runnables(None, window, cx);
24352 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
24353 }
24354 multi_buffer::Event::Reparsed(buffer_id) => {
24355 self.refresh_runnables(Some(*buffer_id), window, cx);
24356 self.refresh_selected_text_highlights(&self.display_snapshot(cx), true, window, cx);
24357 self.colorize_brackets(true, cx);
24358 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24359
24360 cx.emit(EditorEvent::Reparsed(*buffer_id));
24361 }
24362 multi_buffer::Event::DiffHunksToggled => {
24363 self.refresh_runnables(None, window, cx);
24364 }
24365 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
24366 if !is_fresh_language {
24367 self.registered_buffers.remove(&buffer_id);
24368 }
24369 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24370 cx.emit(EditorEvent::Reparsed(*buffer_id));
24371 self.update_edit_prediction_settings(cx);
24372 cx.notify();
24373 }
24374 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
24375 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
24376 multi_buffer::Event::FileHandleChanged
24377 | multi_buffer::Event::Reloaded
24378 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
24379 multi_buffer::Event::DiagnosticsUpdated => {
24380 self.update_diagnostics_state(window, cx);
24381 }
24382 _ => {}
24383 };
24384 }
24385
24386 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
24387 if !self.diagnostics_enabled() {
24388 return;
24389 }
24390 self.refresh_active_diagnostics(cx);
24391 self.refresh_inline_diagnostics(true, window, cx);
24392 self.scrollbar_marker_state.dirty = true;
24393 cx.notify();
24394 }
24395
24396 pub fn start_temporary_diff_override(&mut self) {
24397 self.load_diff_task.take();
24398 self.temporary_diff_override = true;
24399 }
24400
24401 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
24402 self.temporary_diff_override = false;
24403 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
24404 self.buffer.update(cx, |buffer, cx| {
24405 buffer.set_all_diff_hunks_collapsed(cx);
24406 });
24407
24408 if let Some(project) = self.project.clone() {
24409 self.load_diff_task = Some(
24410 update_uncommitted_diff_for_buffer(
24411 cx.entity(),
24412 &project,
24413 self.buffer.read(cx).all_buffers(),
24414 self.buffer.clone(),
24415 cx,
24416 )
24417 .shared(),
24418 );
24419 }
24420 }
24421
24422 fn on_display_map_changed(
24423 &mut self,
24424 _: Entity<DisplayMap>,
24425 _: &mut Window,
24426 cx: &mut Context<Self>,
24427 ) {
24428 cx.notify();
24429 }
24430
24431 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
24432 if !self.mode.is_full() {
24433 return None;
24434 }
24435
24436 let theme_settings = theme::ThemeSettings::get_global(cx);
24437 let theme = cx.theme();
24438 let accent_colors = theme.accents().clone();
24439
24440 let accent_overrides = theme_settings
24441 .theme_overrides
24442 .get(theme.name.as_ref())
24443 .map(|theme_style| &theme_style.accents)
24444 .into_iter()
24445 .flatten()
24446 .chain(
24447 theme_settings
24448 .experimental_theme_overrides
24449 .as_ref()
24450 .map(|overrides| &overrides.accents)
24451 .into_iter()
24452 .flatten(),
24453 )
24454 .flat_map(|accent| accent.0.clone().map(SharedString::from))
24455 .collect();
24456
24457 Some(AccentData {
24458 colors: accent_colors,
24459 overrides: accent_overrides,
24460 })
24461 }
24462
24463 fn fetch_applicable_language_settings(
24464 &self,
24465 cx: &App,
24466 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
24467 if !self.mode.is_full() {
24468 return HashMap::default();
24469 }
24470
24471 self.buffer().read(cx).all_buffers().into_iter().fold(
24472 HashMap::default(),
24473 |mut acc, buffer| {
24474 let buffer = buffer.read(cx);
24475 let language = buffer.language().map(|language| language.name());
24476 if let hash_map::Entry::Vacant(v) = acc.entry(language) {
24477 v.insert(LanguageSettings::for_buffer(&buffer, cx).into_owned());
24478 }
24479 acc
24480 },
24481 )
24482 }
24483
24484 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24485 let new_language_settings = self.fetch_applicable_language_settings(cx);
24486 let language_settings_changed = new_language_settings != self.applicable_language_settings;
24487 self.applicable_language_settings = new_language_settings;
24488
24489 let new_accents = self.fetch_accent_data(cx);
24490 let accents_changed = new_accents != self.accent_data;
24491 self.accent_data = new_accents;
24492
24493 if self.diagnostics_enabled() {
24494 let new_severity = EditorSettings::get_global(cx)
24495 .diagnostics_max_severity
24496 .unwrap_or(DiagnosticSeverity::Hint);
24497 self.set_max_diagnostics_severity(new_severity, cx);
24498 }
24499 self.refresh_runnables(None, window, cx);
24500 self.update_edit_prediction_settings(cx);
24501 self.refresh_edit_prediction(true, false, window, cx);
24502 self.refresh_inline_values(cx);
24503
24504 let old_cursor_shape = self.cursor_shape;
24505 let old_show_breadcrumbs = self.show_breadcrumbs;
24506
24507 {
24508 let editor_settings = EditorSettings::get_global(cx);
24509 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
24510 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
24511 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
24512 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
24513 }
24514
24515 if old_cursor_shape != self.cursor_shape {
24516 cx.emit(EditorEvent::CursorShapeChanged);
24517 }
24518
24519 if old_show_breadcrumbs != self.show_breadcrumbs {
24520 cx.emit(EditorEvent::BreadcrumbsChanged);
24521 }
24522
24523 let (restore_unsaved_buffers, show_inline_diagnostics, inline_blame_enabled) = {
24524 let project_settings = ProjectSettings::get_global(cx);
24525 (
24526 project_settings.session.restore_unsaved_buffers,
24527 project_settings.diagnostics.inline.enabled,
24528 project_settings.git.inline_blame.enabled,
24529 )
24530 };
24531 self.buffer_serialization = self
24532 .should_serialize_buffer()
24533 .then(|| BufferSerialization::new(restore_unsaved_buffers));
24534
24535 if self.mode.is_full() {
24536 if self.show_inline_diagnostics != show_inline_diagnostics {
24537 self.show_inline_diagnostics = show_inline_diagnostics;
24538 self.refresh_inline_diagnostics(false, window, cx);
24539 }
24540
24541 if self.git_blame_inline_enabled != inline_blame_enabled {
24542 self.toggle_git_blame_inline_internal(false, window, cx);
24543 }
24544
24545 let minimap_settings = EditorSettings::get_global(cx).minimap;
24546 if self.minimap_visibility != MinimapVisibility::Disabled {
24547 if self.minimap_visibility.settings_visibility()
24548 != minimap_settings.minimap_enabled()
24549 {
24550 self.set_minimap_visibility(
24551 MinimapVisibility::for_mode(self.mode(), cx),
24552 window,
24553 cx,
24554 );
24555 } else if let Some(minimap_entity) = self.minimap.as_ref() {
24556 minimap_entity.update(cx, |minimap_editor, cx| {
24557 minimap_editor.update_minimap_configuration(minimap_settings, cx)
24558 })
24559 }
24560 }
24561
24562 if language_settings_changed || accents_changed {
24563 self.colorize_brackets(true, cx);
24564 }
24565
24566 if language_settings_changed {
24567 self.clear_disabled_lsp_folding_ranges(window, cx);
24568 self.refresh_document_symbols(None, cx);
24569 }
24570
24571 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
24572 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
24573 }) {
24574 if !inlay_splice.is_empty() {
24575 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
24576 }
24577 self.refresh_document_colors(None, window, cx);
24578 }
24579
24580 self.refresh_inlay_hints(
24581 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
24582 self.selections.newest_anchor().head(),
24583 &self.buffer.read(cx).snapshot(cx),
24584 cx,
24585 )),
24586 cx,
24587 );
24588
24589 let new_semantic_token_rules = ProjectSettings::get_global(cx)
24590 .global_lsp_settings
24591 .semantic_token_rules
24592 .clone();
24593 let semantic_token_rules_changed = self
24594 .semantic_token_state
24595 .update_rules(new_semantic_token_rules);
24596 if language_settings_changed || semantic_token_rules_changed {
24597 self.invalidate_semantic_tokens(None);
24598 self.refresh_semantic_tokens(None, None, cx);
24599 }
24600 }
24601
24602 cx.notify();
24603 }
24604
24605 fn theme_changed(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24606 if !self.mode.is_full() {
24607 return;
24608 }
24609
24610 let new_accents = self.fetch_accent_data(cx);
24611 if new_accents != self.accent_data {
24612 self.accent_data = new_accents;
24613 self.colorize_brackets(true, cx);
24614 }
24615
24616 self.invalidate_semantic_tokens(None);
24617 self.refresh_semantic_tokens(None, None, cx);
24618 }
24619
24620 pub fn set_searchable(&mut self, searchable: bool) {
24621 self.searchable = searchable;
24622 }
24623
24624 pub fn searchable(&self) -> bool {
24625 self.searchable
24626 }
24627
24628 pub fn open_excerpts_in_split(
24629 &mut self,
24630 _: &OpenExcerptsSplit,
24631 window: &mut Window,
24632 cx: &mut Context<Self>,
24633 ) {
24634 self.open_excerpts_common(None, true, window, cx)
24635 }
24636
24637 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
24638 self.open_excerpts_common(None, false, window, cx)
24639 }
24640
24641 pub(crate) fn open_excerpts_common(
24642 &mut self,
24643 jump_data: Option<JumpData>,
24644 split: bool,
24645 window: &mut Window,
24646 cx: &mut Context<Self>,
24647 ) {
24648 if self.buffer.read(cx).is_singleton() {
24649 cx.propagate();
24650 return;
24651 }
24652
24653 let mut new_selections_by_buffer = HashMap::default();
24654 match &jump_data {
24655 Some(JumpData::MultiBufferPoint {
24656 excerpt_id,
24657 position,
24658 anchor,
24659 line_offset_from_top,
24660 }) => {
24661 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
24662 if let Some(buffer) = multi_buffer_snapshot
24663 .buffer_id_for_excerpt(*excerpt_id)
24664 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
24665 {
24666 let buffer_snapshot = buffer.read(cx).snapshot();
24667 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
24668 language::ToPoint::to_point(anchor, &buffer_snapshot)
24669 } else {
24670 buffer_snapshot.clip_point(*position, Bias::Left)
24671 };
24672 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
24673 new_selections_by_buffer.insert(
24674 buffer,
24675 (
24676 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
24677 Some(*line_offset_from_top),
24678 ),
24679 );
24680 }
24681 }
24682 Some(JumpData::MultiBufferRow {
24683 row,
24684 line_offset_from_top,
24685 }) => {
24686 let point = MultiBufferPoint::new(row.0, 0);
24687 if let Some((buffer, buffer_point, _)) =
24688 self.buffer.read(cx).point_to_buffer_point(point, cx)
24689 {
24690 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
24691 new_selections_by_buffer
24692 .entry(buffer)
24693 .or_insert((Vec::new(), Some(*line_offset_from_top)))
24694 .0
24695 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
24696 }
24697 }
24698 None => {
24699 let selections = self
24700 .selections
24701 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
24702 let multi_buffer = self.buffer.read(cx);
24703 for selection in selections {
24704 for (snapshot, range, _, anchor) in multi_buffer
24705 .snapshot(cx)
24706 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
24707 {
24708 if let Some(anchor) = anchor {
24709 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
24710 else {
24711 continue;
24712 };
24713 let offset = text::ToOffset::to_offset(
24714 &anchor.text_anchor,
24715 &buffer_handle.read(cx).snapshot(),
24716 );
24717 let range = BufferOffset(offset)..BufferOffset(offset);
24718 new_selections_by_buffer
24719 .entry(buffer_handle)
24720 .or_insert((Vec::new(), None))
24721 .0
24722 .push(range)
24723 } else {
24724 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
24725 else {
24726 continue;
24727 };
24728 new_selections_by_buffer
24729 .entry(buffer_handle)
24730 .or_insert((Vec::new(), None))
24731 .0
24732 .push(range)
24733 }
24734 }
24735 }
24736 }
24737 }
24738
24739 if self.delegate_open_excerpts {
24740 let selections_by_buffer: HashMap<_, _> = new_selections_by_buffer
24741 .into_iter()
24742 .map(|(buffer, value)| (buffer.read(cx).remote_id(), value))
24743 .collect();
24744 if !selections_by_buffer.is_empty() {
24745 cx.emit(EditorEvent::OpenExcerptsRequested {
24746 selections_by_buffer,
24747 split,
24748 });
24749 }
24750 return;
24751 }
24752
24753 let Some(workspace) = self.workspace() else {
24754 cx.propagate();
24755 return;
24756 };
24757
24758 new_selections_by_buffer
24759 .retain(|buffer, _| buffer.read(cx).file().is_none_or(|file| file.can_open()));
24760
24761 if new_selections_by_buffer.is_empty() {
24762 return;
24763 }
24764
24765 Self::open_buffers_in_workspace(
24766 workspace.downgrade(),
24767 new_selections_by_buffer,
24768 split,
24769 window,
24770 cx,
24771 );
24772 }
24773
24774 pub(crate) fn open_buffers_in_workspace(
24775 workspace: WeakEntity<Workspace>,
24776 new_selections_by_buffer: HashMap<
24777 Entity<language::Buffer>,
24778 (Vec<Range<BufferOffset>>, Option<u32>),
24779 >,
24780 split: bool,
24781 window: &mut Window,
24782 cx: &mut App,
24783 ) {
24784 // We defer the pane interaction because we ourselves are a workspace item
24785 // and activating a new item causes the pane to call a method on us reentrantly,
24786 // which panics if we're on the stack.
24787 window.defer(cx, move |window, cx| {
24788 workspace
24789 .update(cx, |workspace, cx| {
24790 let pane = if split {
24791 workspace.adjacent_pane(window, cx)
24792 } else {
24793 workspace.active_pane().clone()
24794 };
24795
24796 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
24797 let buffer_read = buffer.read(cx);
24798 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
24799 (true, project::File::from_dyn(Some(file)).is_some())
24800 } else {
24801 (false, false)
24802 };
24803
24804 // If project file is none workspace.open_project_item will fail to open the excerpt
24805 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
24806 // so we check if there's a tab match in that case first
24807 let editor = (!has_file || !is_project_file)
24808 .then(|| {
24809 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
24810 // so `workspace.open_project_item` will never find them, always opening a new editor.
24811 // Instead, we try to activate the existing editor in the pane first.
24812 let (editor, pane_item_index, pane_item_id) =
24813 pane.read(cx).items().enumerate().find_map(|(i, item)| {
24814 let editor = item.downcast::<Editor>()?;
24815 let singleton_buffer =
24816 editor.read(cx).buffer().read(cx).as_singleton()?;
24817 if singleton_buffer == buffer {
24818 Some((editor, i, item.item_id()))
24819 } else {
24820 None
24821 }
24822 })?;
24823 pane.update(cx, |pane, cx| {
24824 pane.activate_item(pane_item_index, true, true, window, cx);
24825 if !PreviewTabsSettings::get_global(cx)
24826 .enable_preview_from_multibuffer
24827 {
24828 pane.unpreview_item_if_preview(pane_item_id);
24829 }
24830 });
24831 Some(editor)
24832 })
24833 .flatten()
24834 .unwrap_or_else(|| {
24835 let keep_old_preview = PreviewTabsSettings::get_global(cx)
24836 .enable_keep_preview_on_code_navigation;
24837 let allow_new_preview = PreviewTabsSettings::get_global(cx)
24838 .enable_preview_from_multibuffer;
24839 workspace.open_project_item::<Self>(
24840 pane.clone(),
24841 buffer,
24842 true,
24843 true,
24844 keep_old_preview,
24845 allow_new_preview,
24846 window,
24847 cx,
24848 )
24849 });
24850
24851 editor.update(cx, |editor, cx| {
24852 if has_file && !is_project_file {
24853 editor.set_read_only(true);
24854 }
24855 let autoscroll = match scroll_offset {
24856 Some(scroll_offset) => {
24857 Autoscroll::top_relative(scroll_offset as usize)
24858 }
24859 None => Autoscroll::newest(),
24860 };
24861 let nav_history = editor.nav_history.take();
24862 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
24863 let Some((excerpt_id, _, buffer_snapshot)) =
24864 multibuffer_snapshot.as_singleton()
24865 else {
24866 return;
24867 };
24868 editor.change_selections(
24869 SelectionEffects::scroll(autoscroll),
24870 window,
24871 cx,
24872 |s| {
24873 s.select_ranges(ranges.into_iter().map(|range| {
24874 let range = buffer_snapshot.anchor_before(range.start)
24875 ..buffer_snapshot.anchor_after(range.end);
24876 multibuffer_snapshot
24877 .anchor_range_in_excerpt(excerpt_id, range)
24878 .unwrap()
24879 }));
24880 },
24881 );
24882 editor.nav_history = nav_history;
24883 });
24884 }
24885 })
24886 .ok();
24887 });
24888 }
24889
24890 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
24891 let snapshot = self.buffer.read(cx).read(cx);
24892 let (_, ranges) = self.text_highlights(HighlightKey::InputComposition, cx)?;
24893 Some(
24894 ranges
24895 .iter()
24896 .map(move |range| {
24897 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
24898 })
24899 .collect(),
24900 )
24901 }
24902
24903 fn selection_replacement_ranges(
24904 &self,
24905 range: Range<MultiBufferOffsetUtf16>,
24906 cx: &mut App,
24907 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
24908 let selections = self
24909 .selections
24910 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24911 let newest_selection = selections
24912 .iter()
24913 .max_by_key(|selection| selection.id)
24914 .unwrap();
24915 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
24916 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
24917 let snapshot = self.buffer.read(cx).read(cx);
24918 selections
24919 .into_iter()
24920 .map(|mut selection| {
24921 selection.start.0.0 =
24922 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
24923 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
24924 snapshot.clip_offset_utf16(selection.start, Bias::Left)
24925 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
24926 })
24927 .collect()
24928 }
24929
24930 fn report_editor_event(
24931 &self,
24932 reported_event: ReportEditorEvent,
24933 file_extension: Option<String>,
24934 cx: &App,
24935 ) {
24936 if cfg!(any(test, feature = "test-support")) {
24937 return;
24938 }
24939
24940 let Some(project) = &self.project else { return };
24941
24942 // If None, we are in a file without an extension
24943 let file = self
24944 .buffer
24945 .read(cx)
24946 .as_singleton()
24947 .and_then(|b| b.read(cx).file());
24948 let file_extension = file_extension.or(file
24949 .as_ref()
24950 .and_then(|file| Path::new(file.file_name(cx)).extension())
24951 .and_then(|e| e.to_str())
24952 .map(|a| a.to_string()));
24953
24954 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
24955 .map(|vim_mode| vim_mode.0)
24956 .unwrap_or(false);
24957
24958 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
24959 let copilot_enabled = edit_predictions_provider
24960 == language::language_settings::EditPredictionProvider::Copilot;
24961 let copilot_enabled_for_language = self
24962 .buffer
24963 .read(cx)
24964 .language_settings(cx)
24965 .show_edit_predictions;
24966
24967 let project = project.read(cx);
24968 let event_type = reported_event.event_type();
24969
24970 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
24971 telemetry::event!(
24972 event_type,
24973 type = if auto_saved {"autosave"} else {"manual"},
24974 file_extension,
24975 vim_mode,
24976 copilot_enabled,
24977 copilot_enabled_for_language,
24978 edit_predictions_provider,
24979 is_via_ssh = project.is_via_remote_server(),
24980 );
24981 } else {
24982 telemetry::event!(
24983 event_type,
24984 file_extension,
24985 vim_mode,
24986 copilot_enabled,
24987 copilot_enabled_for_language,
24988 edit_predictions_provider,
24989 is_via_ssh = project.is_via_remote_server(),
24990 );
24991 };
24992 }
24993
24994 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
24995 /// with each line being an array of {text, highlight} objects.
24996 fn copy_highlight_json(
24997 &mut self,
24998 _: &CopyHighlightJson,
24999 window: &mut Window,
25000 cx: &mut Context<Self>,
25001 ) {
25002 #[derive(Serialize)]
25003 struct Chunk<'a> {
25004 text: String,
25005 highlight: Option<&'a str>,
25006 }
25007
25008 let snapshot = self.buffer.read(cx).snapshot(cx);
25009 let range = self
25010 .selected_text_range(false, window, cx)
25011 .and_then(|selection| {
25012 if selection.range.is_empty() {
25013 None
25014 } else {
25015 Some(
25016 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
25017 selection.range.start,
25018 )))
25019 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
25020 selection.range.end,
25021 ))),
25022 )
25023 }
25024 })
25025 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
25026
25027 let chunks = snapshot.chunks(range, true);
25028 let mut lines = Vec::new();
25029 let mut line: VecDeque<Chunk> = VecDeque::new();
25030
25031 let Some(style) = self.style.as_ref() else {
25032 return;
25033 };
25034
25035 for chunk in chunks {
25036 let highlight = chunk
25037 .syntax_highlight_id
25038 .and_then(|id| id.name(&style.syntax));
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 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
25070 return;
25071 };
25072 cx.write_to_clipboard(ClipboardItem::new_string(lines));
25073 }
25074
25075 pub fn open_context_menu(
25076 &mut self,
25077 _: &OpenContextMenu,
25078 window: &mut Window,
25079 cx: &mut Context<Self>,
25080 ) {
25081 self.request_autoscroll(Autoscroll::newest(), cx);
25082 let position = self
25083 .selections
25084 .newest_display(&self.display_snapshot(cx))
25085 .start;
25086 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
25087 }
25088
25089 pub fn replay_insert_event(
25090 &mut self,
25091 text: &str,
25092 relative_utf16_range: Option<Range<isize>>,
25093 window: &mut Window,
25094 cx: &mut Context<Self>,
25095 ) {
25096 if !self.input_enabled {
25097 cx.emit(EditorEvent::InputIgnored { text: text.into() });
25098 return;
25099 }
25100 if let Some(relative_utf16_range) = relative_utf16_range {
25101 let selections = self
25102 .selections
25103 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
25104 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25105 let new_ranges = selections.into_iter().map(|range| {
25106 let start = MultiBufferOffsetUtf16(OffsetUtf16(
25107 range
25108 .head()
25109 .0
25110 .0
25111 .saturating_add_signed(relative_utf16_range.start),
25112 ));
25113 let end = MultiBufferOffsetUtf16(OffsetUtf16(
25114 range
25115 .head()
25116 .0
25117 .0
25118 .saturating_add_signed(relative_utf16_range.end),
25119 ));
25120 start..end
25121 });
25122 s.select_ranges(new_ranges);
25123 });
25124 }
25125
25126 self.handle_input(text, window, cx);
25127 }
25128
25129 pub fn is_focused(&self, window: &Window) -> bool {
25130 self.focus_handle.is_focused(window)
25131 }
25132
25133 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25134 cx.emit(EditorEvent::Focused);
25135
25136 if let Some(descendant) = self
25137 .last_focused_descendant
25138 .take()
25139 .and_then(|descendant| descendant.upgrade())
25140 {
25141 window.focus(&descendant, cx);
25142 } else {
25143 if let Some(blame) = self.blame.as_ref() {
25144 blame.update(cx, GitBlame::focus)
25145 }
25146
25147 self.blink_manager.update(cx, BlinkManager::enable);
25148 self.show_cursor_names(window, cx);
25149 self.buffer.update(cx, |buffer, cx| {
25150 buffer.finalize_last_transaction(cx);
25151 if self.leader_id.is_none() {
25152 buffer.set_active_selections(
25153 &self.selections.disjoint_anchors_arc(),
25154 self.selections.line_mode(),
25155 self.cursor_shape,
25156 cx,
25157 );
25158 }
25159 });
25160
25161 if let Some(position_map) = self.last_position_map.clone()
25162 && !self.mouse_cursor_hidden
25163 {
25164 EditorElement::mouse_moved(
25165 self,
25166 &MouseMoveEvent {
25167 position: window.mouse_position(),
25168 pressed_button: None,
25169 modifiers: window.modifiers(),
25170 },
25171 &position_map,
25172 None,
25173 window,
25174 cx,
25175 );
25176 }
25177 }
25178 }
25179
25180 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
25181 cx.emit(EditorEvent::FocusedIn)
25182 }
25183
25184 fn handle_focus_out(
25185 &mut self,
25186 event: FocusOutEvent,
25187 _window: &mut Window,
25188 cx: &mut Context<Self>,
25189 ) {
25190 if event.blurred != self.focus_handle {
25191 self.last_focused_descendant = Some(event.blurred);
25192 }
25193 self.selection_drag_state = SelectionDragState::None;
25194 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
25195 }
25196
25197 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25198 self.blink_manager.update(cx, BlinkManager::disable);
25199 self.buffer
25200 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
25201
25202 if let Some(blame) = self.blame.as_ref() {
25203 blame.update(cx, GitBlame::blur)
25204 }
25205 if !self.hover_state.focused(window, cx) {
25206 hide_hover(self, cx);
25207 }
25208 if !self
25209 .context_menu
25210 .borrow()
25211 .as_ref()
25212 .is_some_and(|context_menu| context_menu.focused(window, cx))
25213 {
25214 self.hide_context_menu(window, cx);
25215 }
25216 self.take_active_edit_prediction(true, cx);
25217 cx.emit(EditorEvent::Blurred);
25218 cx.notify();
25219 }
25220
25221 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25222 let mut pending: String = window
25223 .pending_input_keystrokes()
25224 .into_iter()
25225 .flatten()
25226 .filter_map(|keystroke| keystroke.key_char.clone())
25227 .collect();
25228
25229 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
25230 pending = "".to_string();
25231 }
25232
25233 let existing_pending = self
25234 .text_highlights(HighlightKey::PendingInput, cx)
25235 .map(|(_, ranges)| ranges.to_vec());
25236 if existing_pending.is_none() && pending.is_empty() {
25237 return;
25238 }
25239 let transaction =
25240 self.transact(window, cx, |this, window, cx| {
25241 let selections = this
25242 .selections
25243 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
25244 let edits = selections
25245 .iter()
25246 .map(|selection| (selection.end..selection.end, pending.clone()));
25247 this.edit(edits, cx);
25248 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25249 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
25250 sel.start + ix * pending.len()..sel.end + ix * pending.len()
25251 }));
25252 });
25253 if let Some(existing_ranges) = existing_pending {
25254 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
25255 this.edit(edits, cx);
25256 }
25257 });
25258
25259 let snapshot = self.snapshot(window, cx);
25260 let ranges = self
25261 .selections
25262 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
25263 .into_iter()
25264 .map(|selection| {
25265 snapshot.buffer_snapshot().anchor_after(selection.end)
25266 ..snapshot
25267 .buffer_snapshot()
25268 .anchor_before(selection.end + pending.len())
25269 })
25270 .collect();
25271
25272 if pending.is_empty() {
25273 self.clear_highlights(HighlightKey::PendingInput, cx);
25274 } else {
25275 self.highlight_text(
25276 HighlightKey::PendingInput,
25277 ranges,
25278 HighlightStyle {
25279 underline: Some(UnderlineStyle {
25280 thickness: px(1.),
25281 color: None,
25282 wavy: false,
25283 }),
25284 ..Default::default()
25285 },
25286 cx,
25287 );
25288 }
25289
25290 self.ime_transaction = self.ime_transaction.or(transaction);
25291 if let Some(transaction) = self.ime_transaction {
25292 self.buffer.update(cx, |buffer, cx| {
25293 buffer.group_until_transaction(transaction, cx);
25294 });
25295 }
25296
25297 if self
25298 .text_highlights(HighlightKey::PendingInput, cx)
25299 .is_none()
25300 {
25301 self.ime_transaction.take();
25302 }
25303 }
25304
25305 pub fn register_action_renderer(
25306 &mut self,
25307 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
25308 ) -> Subscription {
25309 let id = self.next_editor_action_id.post_inc();
25310 self.editor_actions
25311 .borrow_mut()
25312 .insert(id, Box::new(listener));
25313
25314 let editor_actions = self.editor_actions.clone();
25315 Subscription::new(move || {
25316 editor_actions.borrow_mut().remove(&id);
25317 })
25318 }
25319
25320 pub fn register_action<A: Action>(
25321 &mut self,
25322 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
25323 ) -> Subscription {
25324 let id = self.next_editor_action_id.post_inc();
25325 let listener = Arc::new(listener);
25326 self.editor_actions.borrow_mut().insert(
25327 id,
25328 Box::new(move |_, window, _| {
25329 let listener = listener.clone();
25330 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
25331 let action = action.downcast_ref().unwrap();
25332 if phase == DispatchPhase::Bubble {
25333 listener(action, window, cx)
25334 }
25335 })
25336 }),
25337 );
25338
25339 let editor_actions = self.editor_actions.clone();
25340 Subscription::new(move || {
25341 editor_actions.borrow_mut().remove(&id);
25342 })
25343 }
25344
25345 pub fn file_header_size(&self) -> u32 {
25346 FILE_HEADER_HEIGHT
25347 }
25348
25349 pub fn restore(
25350 &mut self,
25351 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
25352 window: &mut Window,
25353 cx: &mut Context<Self>,
25354 ) {
25355 self.buffer().update(cx, |multi_buffer, cx| {
25356 for (buffer_id, changes) in revert_changes {
25357 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
25358 buffer.update(cx, |buffer, cx| {
25359 buffer.edit(
25360 changes
25361 .into_iter()
25362 .map(|(range, text)| (range, text.to_string())),
25363 None,
25364 cx,
25365 );
25366 });
25367 }
25368 }
25369 });
25370 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25371 selections.refresh()
25372 });
25373 }
25374
25375 pub fn to_pixel_point(
25376 &mut self,
25377 source: Anchor,
25378 editor_snapshot: &EditorSnapshot,
25379 window: &mut Window,
25380 cx: &mut App,
25381 ) -> Option<gpui::Point<Pixels>> {
25382 let source_point = source.to_display_point(editor_snapshot);
25383 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
25384 }
25385
25386 pub fn display_to_pixel_point(
25387 &mut self,
25388 source: DisplayPoint,
25389 editor_snapshot: &EditorSnapshot,
25390 window: &mut Window,
25391 cx: &mut App,
25392 ) -> Option<gpui::Point<Pixels>> {
25393 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
25394 let text_layout_details = self.text_layout_details(window, cx);
25395 let scroll_top = text_layout_details
25396 .scroll_anchor
25397 .scroll_position(editor_snapshot)
25398 .y;
25399
25400 if source.row().as_f64() < scroll_top.floor() {
25401 return None;
25402 }
25403 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
25404 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
25405 Some(gpui::Point::new(source_x, source_y))
25406 }
25407
25408 pub fn has_visible_completions_menu(&self) -> bool {
25409 !self.edit_prediction_preview_is_active()
25410 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
25411 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
25412 })
25413 }
25414
25415 pub fn register_addon<T: Addon>(&mut self, instance: T) {
25416 if self.mode.is_minimap() {
25417 return;
25418 }
25419 self.addons
25420 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
25421 }
25422
25423 pub fn unregister_addon<T: Addon>(&mut self) {
25424 self.addons.remove(&std::any::TypeId::of::<T>());
25425 }
25426
25427 pub fn addon<T: Addon>(&self) -> Option<&T> {
25428 let type_id = std::any::TypeId::of::<T>();
25429 self.addons
25430 .get(&type_id)
25431 .and_then(|item| item.to_any().downcast_ref::<T>())
25432 }
25433
25434 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
25435 let type_id = std::any::TypeId::of::<T>();
25436 self.addons
25437 .get_mut(&type_id)
25438 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
25439 }
25440
25441 fn character_dimensions(&self, window: &mut Window, cx: &mut App) -> CharacterDimensions {
25442 let text_layout_details = self.text_layout_details(window, cx);
25443 let style = &text_layout_details.editor_style;
25444 let font_id = window.text_system().resolve_font(&style.text.font());
25445 let font_size = style.text.font_size.to_pixels(window.rem_size());
25446 let line_height = style.text.line_height_in_pixels(window.rem_size());
25447 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
25448 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
25449
25450 CharacterDimensions {
25451 em_width,
25452 em_advance,
25453 line_height,
25454 }
25455 }
25456
25457 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
25458 self.load_diff_task.clone()
25459 }
25460
25461 fn read_metadata_from_db(
25462 &mut self,
25463 item_id: u64,
25464 workspace_id: WorkspaceId,
25465 window: &mut Window,
25466 cx: &mut Context<Editor>,
25467 ) {
25468 if self.buffer_kind(cx) == ItemBufferKind::Singleton
25469 && !self.mode.is_minimap()
25470 && WorkspaceSettings::get(None, cx).restore_on_startup
25471 != RestoreOnStartupBehavior::EmptyTab
25472 {
25473 let buffer_snapshot = OnceCell::new();
25474
25475 // Get file path for path-based fold lookup
25476 let file_path: Option<Arc<Path>> =
25477 self.buffer().read(cx).as_singleton().and_then(|buffer| {
25478 project::File::from_dyn(buffer.read(cx).file())
25479 .map(|file| Arc::from(file.abs_path(cx)))
25480 });
25481
25482 // Try file_folds (path-based) first, fallback to editor_folds (migration)
25483 let db = EditorDb::global(cx);
25484 let (folds, needs_migration) = if let Some(ref path) = file_path {
25485 if let Some(folds) = db.get_file_folds(workspace_id, path).log_err()
25486 && !folds.is_empty()
25487 {
25488 (Some(folds), false)
25489 } else if let Some(folds) = db.get_editor_folds(item_id, workspace_id).log_err()
25490 && !folds.is_empty()
25491 {
25492 // Found old editor_folds data, will migrate to file_folds
25493 (Some(folds), true)
25494 } else {
25495 (None, false)
25496 }
25497 } else {
25498 // No file path, try editor_folds as fallback
25499 let folds = db.get_editor_folds(item_id, workspace_id).log_err();
25500 (folds.filter(|f| !f.is_empty()), false)
25501 };
25502
25503 if let Some(folds) = folds {
25504 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25505 let snapshot_len = snapshot.len().0;
25506
25507 // Helper: search for fingerprint in buffer, return offset if found
25508 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
25509 // Ensure we start at a character boundary (defensive)
25510 let search_start = snapshot
25511 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
25512 .0;
25513 let search_end = snapshot_len.saturating_sub(fingerprint.len());
25514
25515 let mut byte_offset = search_start;
25516 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
25517 if byte_offset > search_end {
25518 break;
25519 }
25520 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
25521 return Some(byte_offset);
25522 }
25523 byte_offset += ch.len_utf8();
25524 }
25525 None
25526 };
25527
25528 // Track search position to handle duplicate fingerprints correctly.
25529 // Folds are stored in document order, so we advance after each match.
25530 let mut search_start = 0usize;
25531
25532 // Collect db_folds for migration (only folds with valid fingerprints)
25533 let mut db_folds_for_migration: Vec<(usize, usize, String, String)> = Vec::new();
25534
25535 let valid_folds: Vec<_> = folds
25536 .into_iter()
25537 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
25538 // Skip folds without fingerprints (old data before migration)
25539 let sfp = start_fp?;
25540 let efp = end_fp?;
25541 let efp_len = efp.len();
25542
25543 // Fast path: check if fingerprints match at stored offsets
25544 // Note: end_fp is content BEFORE fold end, so check at (stored_end - efp_len)
25545 let start_matches = stored_start < snapshot_len
25546 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
25547 let efp_check_pos = stored_end.saturating_sub(efp_len);
25548 let end_matches = efp_check_pos >= stored_start
25549 && stored_end <= snapshot_len
25550 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
25551
25552 let (new_start, new_end) = if start_matches && end_matches {
25553 // Offsets unchanged, use stored values
25554 (stored_start, stored_end)
25555 } else if sfp == efp {
25556 // Short fold: identical fingerprints can only match once per search
25557 // Use stored fold length to compute new_end
25558 let new_start = find_fingerprint(&sfp, search_start)?;
25559 let fold_len = stored_end - stored_start;
25560 let new_end = new_start + fold_len;
25561 (new_start, new_end)
25562 } else {
25563 // Slow path: search for fingerprints in buffer
25564 let new_start = find_fingerprint(&sfp, search_start)?;
25565 // Search for end_fp after start, then add efp_len to get actual fold end
25566 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
25567 let new_end = efp_pos + efp_len;
25568 (new_start, new_end)
25569 };
25570
25571 // Advance search position for next fold
25572 search_start = new_end;
25573
25574 // Validate fold makes sense (end must be after start)
25575 if new_end <= new_start {
25576 return None;
25577 }
25578
25579 // Collect for migration if needed
25580 if needs_migration {
25581 db_folds_for_migration.push((new_start, new_end, sfp, efp));
25582 }
25583
25584 Some(
25585 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
25586 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
25587 )
25588 })
25589 .collect();
25590
25591 if !valid_folds.is_empty() {
25592 self.fold_ranges(valid_folds, false, window, cx);
25593
25594 // Migrate from editor_folds to file_folds if we loaded from old table
25595 if needs_migration {
25596 if let Some(ref path) = file_path {
25597 let path = path.clone();
25598 let db = EditorDb::global(cx);
25599 cx.spawn(async move |_, _| {
25600 db.save_file_folds(workspace_id, path, db_folds_for_migration)
25601 .await
25602 .log_err();
25603 })
25604 .detach();
25605 }
25606 }
25607 }
25608 }
25609
25610 if let Some(selections) = db.get_editor_selections(item_id, workspace_id).log_err()
25611 && !selections.is_empty()
25612 {
25613 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25614 // skip adding the initial selection to selection history
25615 self.selection_history.mode = SelectionHistoryMode::Skipping;
25616 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25617 s.select_ranges(selections.into_iter().map(|(start, end)| {
25618 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
25619 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
25620 }));
25621 });
25622 self.selection_history.mode = SelectionHistoryMode::Normal;
25623 };
25624 }
25625
25626 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
25627 }
25628
25629 /// Load folds from the file_folds database table by file path.
25630 /// Used when manually opening a file that was previously closed.
25631 fn load_folds_from_db(
25632 &mut self,
25633 workspace_id: WorkspaceId,
25634 file_path: PathBuf,
25635 window: &mut Window,
25636 cx: &mut Context<Editor>,
25637 ) {
25638 if self.mode.is_minimap()
25639 || WorkspaceSettings::get(None, cx).restore_on_startup
25640 == RestoreOnStartupBehavior::EmptyTab
25641 {
25642 return;
25643 }
25644
25645 let Some(folds) = EditorDb::global(cx)
25646 .get_file_folds(workspace_id, &file_path)
25647 .log_err()
25648 else {
25649 return;
25650 };
25651 if folds.is_empty() {
25652 return;
25653 }
25654
25655 let snapshot = self.buffer.read(cx).snapshot(cx);
25656 let snapshot_len = snapshot.len().0;
25657
25658 // Helper: search for fingerprint in buffer, return offset if found
25659 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
25660 let search_start = snapshot
25661 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
25662 .0;
25663 let search_end = snapshot_len.saturating_sub(fingerprint.len());
25664
25665 let mut byte_offset = search_start;
25666 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
25667 if byte_offset > search_end {
25668 break;
25669 }
25670 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
25671 return Some(byte_offset);
25672 }
25673 byte_offset += ch.len_utf8();
25674 }
25675 None
25676 };
25677
25678 let mut search_start = 0usize;
25679
25680 let valid_folds: Vec<_> = folds
25681 .into_iter()
25682 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
25683 let sfp = start_fp?;
25684 let efp = end_fp?;
25685 let efp_len = efp.len();
25686
25687 let start_matches = stored_start < snapshot_len
25688 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
25689 let efp_check_pos = stored_end.saturating_sub(efp_len);
25690 let end_matches = efp_check_pos >= stored_start
25691 && stored_end <= snapshot_len
25692 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
25693
25694 let (new_start, new_end) = if start_matches && end_matches {
25695 (stored_start, stored_end)
25696 } else if sfp == efp {
25697 let new_start = find_fingerprint(&sfp, search_start)?;
25698 let fold_len = stored_end - stored_start;
25699 let new_end = new_start + fold_len;
25700 (new_start, new_end)
25701 } else {
25702 let new_start = find_fingerprint(&sfp, search_start)?;
25703 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
25704 let new_end = efp_pos + efp_len;
25705 (new_start, new_end)
25706 };
25707
25708 search_start = new_end;
25709
25710 if new_end <= new_start {
25711 return None;
25712 }
25713
25714 Some(
25715 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
25716 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
25717 )
25718 })
25719 .collect();
25720
25721 if !valid_folds.is_empty() {
25722 self.fold_ranges(valid_folds, false, window, cx);
25723 }
25724 }
25725
25726 fn lsp_data_enabled(&self) -> bool {
25727 self.enable_lsp_data && self.mode().is_full()
25728 }
25729
25730 fn update_lsp_data(
25731 &mut self,
25732 for_buffer: Option<BufferId>,
25733 window: &mut Window,
25734 cx: &mut Context<'_, Self>,
25735 ) {
25736 if !self.lsp_data_enabled() {
25737 return;
25738 }
25739
25740 if let Some(buffer_id) = for_buffer {
25741 self.pull_diagnostics(buffer_id, window, cx);
25742 }
25743 self.refresh_semantic_tokens(for_buffer, None, cx);
25744 self.refresh_document_colors(for_buffer, window, cx);
25745 self.refresh_folding_ranges(for_buffer, window, cx);
25746 self.refresh_document_symbols(for_buffer, cx);
25747 }
25748
25749 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
25750 if !self.lsp_data_enabled() {
25751 return;
25752 }
25753 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
25754 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
25755 }
25756 }
25757
25758 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
25759 if !self.lsp_data_enabled() {
25760 return;
25761 }
25762
25763 if !self.registered_buffers.contains_key(&buffer_id)
25764 && let Some(project) = self.project.as_ref()
25765 {
25766 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
25767 project.update(cx, |project, cx| {
25768 self.registered_buffers.insert(
25769 buffer_id,
25770 project.register_buffer_with_language_servers(&buffer, cx),
25771 );
25772 });
25773 } else {
25774 self.registered_buffers.remove(&buffer_id);
25775 }
25776 }
25777 }
25778
25779 fn create_style(&self, cx: &App) -> EditorStyle {
25780 let settings = ThemeSettings::get_global(cx);
25781
25782 let mut text_style = match self.mode {
25783 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
25784 color: cx.theme().colors().editor_foreground,
25785 font_family: settings.ui_font.family.clone(),
25786 font_features: settings.ui_font.features.clone(),
25787 font_fallbacks: settings.ui_font.fallbacks.clone(),
25788 font_size: rems(0.875).into(),
25789 font_weight: settings.ui_font.weight,
25790 line_height: relative(settings.buffer_line_height.value()),
25791 ..Default::default()
25792 },
25793 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
25794 color: cx.theme().colors().editor_foreground,
25795 font_family: settings.buffer_font.family.clone(),
25796 font_features: settings.buffer_font.features.clone(),
25797 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25798 font_size: settings.buffer_font_size(cx).into(),
25799 font_weight: settings.buffer_font.weight,
25800 line_height: relative(settings.buffer_line_height.value()),
25801 ..Default::default()
25802 },
25803 };
25804 if let Some(text_style_refinement) = &self.text_style_refinement {
25805 text_style.refine(text_style_refinement)
25806 }
25807
25808 let background = match self.mode {
25809 EditorMode::SingleLine => cx.theme().system().transparent,
25810 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
25811 EditorMode::Full { .. } => cx.theme().colors().editor_background,
25812 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
25813 };
25814
25815 EditorStyle {
25816 background,
25817 border: cx.theme().colors().border,
25818 local_player: cx.theme().players().local(),
25819 text: text_style,
25820 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
25821 syntax: cx.theme().syntax().clone(),
25822 status: cx.theme().status().clone(),
25823 inlay_hints_style: make_inlay_hints_style(cx),
25824 edit_prediction_styles: make_suggestion_styles(cx),
25825 unnecessary_code_fade: settings.unnecessary_code_fade,
25826 show_underlines: self.diagnostics_enabled(),
25827 }
25828 }
25829
25830 fn breadcrumbs_inner(&self, cx: &App) -> Option<Vec<HighlightedText>> {
25831 let multibuffer = self.buffer().read(cx);
25832 let is_singleton = multibuffer.is_singleton();
25833 let (buffer_id, symbols) = self.outline_symbols_at_cursor.as_ref()?;
25834 let buffer = multibuffer.buffer(*buffer_id)?;
25835
25836 let buffer = buffer.read(cx);
25837 // In a multi-buffer layout, we don't want to include the filename in the breadcrumbs
25838 let mut breadcrumbs = if is_singleton {
25839 let text = self.breadcrumb_header.clone().unwrap_or_else(|| {
25840 buffer
25841 .snapshot()
25842 .resolve_file_path(
25843 self.project
25844 .as_ref()
25845 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
25846 .unwrap_or_default(),
25847 cx,
25848 )
25849 .unwrap_or_else(|| {
25850 if multibuffer.is_singleton() {
25851 multibuffer.title(cx).to_string()
25852 } else {
25853 "untitled".to_string()
25854 }
25855 })
25856 });
25857 vec![HighlightedText {
25858 text: text.into(),
25859 highlights: vec![],
25860 }]
25861 } else {
25862 vec![]
25863 };
25864
25865 breadcrumbs.extend(symbols.iter().map(|symbol| HighlightedText {
25866 text: symbol.text.clone().into(),
25867 highlights: symbol.highlight_ranges.clone(),
25868 }));
25869 Some(breadcrumbs)
25870 }
25871
25872 fn disable_lsp_data(&mut self) {
25873 self.enable_lsp_data = false;
25874 }
25875
25876 fn disable_runnables(&mut self) {
25877 self.enable_runnables = false;
25878 }
25879
25880 fn update_data_on_scroll(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) {
25881 self.register_visible_buffers(cx);
25882 self.colorize_brackets(false, cx);
25883 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
25884 if !self.buffer().read(cx).is_singleton() {
25885 self.update_lsp_data(None, window, cx);
25886 self.refresh_runnables(None, window, cx);
25887 }
25888 }
25889}
25890
25891fn edit_for_markdown_paste<'a>(
25892 buffer: &MultiBufferSnapshot,
25893 range: Range<MultiBufferOffset>,
25894 to_insert: &'a str,
25895 url: Option<url::Url>,
25896) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
25897 if url.is_none() {
25898 return (range, Cow::Borrowed(to_insert));
25899 };
25900
25901 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
25902
25903 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
25904 Cow::Borrowed(to_insert)
25905 } else {
25906 Cow::Owned(format!("[{old_text}]({to_insert})"))
25907 };
25908 (range, new_text)
25909}
25910
25911fn process_completion_for_edit(
25912 completion: &Completion,
25913 intent: CompletionIntent,
25914 buffer: &Entity<Buffer>,
25915 cursor_position: &text::Anchor,
25916 cx: &mut Context<Editor>,
25917) -> CompletionEdit {
25918 let buffer = buffer.read(cx);
25919 let buffer_snapshot = buffer.snapshot();
25920 let (snippet, new_text) = if completion.is_snippet() {
25921 let mut snippet_source = completion.new_text.clone();
25922 // Workaround for typescript language server issues so that methods don't expand within
25923 // strings and functions with type expressions. The previous point is used because the query
25924 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
25925 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
25926 let previous_point = if previous_point.column > 0 {
25927 cursor_position.to_previous_offset(&buffer_snapshot)
25928 } else {
25929 cursor_position.to_offset(&buffer_snapshot)
25930 };
25931 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
25932 && scope.prefers_label_for_snippet_in_completion()
25933 && let Some(label) = completion.label()
25934 && matches!(
25935 completion.kind(),
25936 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
25937 )
25938 {
25939 snippet_source = label;
25940 }
25941 match Snippet::parse(&snippet_source).log_err() {
25942 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
25943 None => (None, completion.new_text.clone()),
25944 }
25945 } else {
25946 (None, completion.new_text.clone())
25947 };
25948
25949 let mut range_to_replace = {
25950 let replace_range = &completion.replace_range;
25951 if let CompletionSource::Lsp {
25952 insert_range: Some(insert_range),
25953 ..
25954 } = &completion.source
25955 {
25956 debug_assert_eq!(
25957 insert_range.start, replace_range.start,
25958 "insert_range and replace_range should start at the same position"
25959 );
25960 debug_assert!(
25961 insert_range
25962 .start
25963 .cmp(cursor_position, &buffer_snapshot)
25964 .is_le(),
25965 "insert_range should start before or at cursor position"
25966 );
25967 debug_assert!(
25968 replace_range
25969 .start
25970 .cmp(cursor_position, &buffer_snapshot)
25971 .is_le(),
25972 "replace_range should start before or at cursor position"
25973 );
25974
25975 let should_replace = match intent {
25976 CompletionIntent::CompleteWithInsert => false,
25977 CompletionIntent::CompleteWithReplace => true,
25978 CompletionIntent::Complete | CompletionIntent::Compose => {
25979 let insert_mode = LanguageSettings::for_buffer(&buffer, cx)
25980 .completions
25981 .lsp_insert_mode;
25982 match insert_mode {
25983 LspInsertMode::Insert => false,
25984 LspInsertMode::Replace => true,
25985 LspInsertMode::ReplaceSubsequence => {
25986 let mut text_to_replace = buffer.chars_for_range(
25987 buffer.anchor_before(replace_range.start)
25988 ..buffer.anchor_after(replace_range.end),
25989 );
25990 let mut current_needle = text_to_replace.next();
25991 for haystack_ch in completion.label.text.chars() {
25992 if let Some(needle_ch) = current_needle
25993 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
25994 {
25995 current_needle = text_to_replace.next();
25996 }
25997 }
25998 current_needle.is_none()
25999 }
26000 LspInsertMode::ReplaceSuffix => {
26001 if replace_range
26002 .end
26003 .cmp(cursor_position, &buffer_snapshot)
26004 .is_gt()
26005 {
26006 let range_after_cursor = *cursor_position..replace_range.end;
26007 let text_after_cursor = buffer
26008 .text_for_range(
26009 buffer.anchor_before(range_after_cursor.start)
26010 ..buffer.anchor_after(range_after_cursor.end),
26011 )
26012 .collect::<String>()
26013 .to_ascii_lowercase();
26014 completion
26015 .label
26016 .text
26017 .to_ascii_lowercase()
26018 .ends_with(&text_after_cursor)
26019 } else {
26020 true
26021 }
26022 }
26023 }
26024 }
26025 };
26026
26027 if should_replace {
26028 replace_range.clone()
26029 } else {
26030 insert_range.clone()
26031 }
26032 } else {
26033 replace_range.clone()
26034 }
26035 };
26036
26037 if range_to_replace
26038 .end
26039 .cmp(cursor_position, &buffer_snapshot)
26040 .is_lt()
26041 {
26042 range_to_replace.end = *cursor_position;
26043 }
26044
26045 let replace_range = range_to_replace.to_offset(buffer);
26046 CompletionEdit {
26047 new_text,
26048 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
26049 snippet,
26050 }
26051}
26052
26053struct CompletionEdit {
26054 new_text: String,
26055 replace_range: Range<BufferOffset>,
26056 snippet: Option<Snippet>,
26057}
26058
26059fn comment_delimiter_for_newline(
26060 start_point: &Point,
26061 buffer: &MultiBufferSnapshot,
26062 language: &LanguageScope,
26063) -> Option<Arc<str>> {
26064 let delimiters = language.line_comment_prefixes();
26065 let max_len_of_delimiter = delimiters.iter().map(|delimiter| delimiter.len()).max()?;
26066 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
26067
26068 let num_of_whitespaces = snapshot
26069 .chars_for_range(range.clone())
26070 .take_while(|c| c.is_whitespace())
26071 .count();
26072 let comment_candidate = snapshot
26073 .chars_for_range(range.clone())
26074 .skip(num_of_whitespaces)
26075 .take(max_len_of_delimiter + 2)
26076 .collect::<String>();
26077 let (delimiter, trimmed_len, is_repl) = delimiters
26078 .iter()
26079 .filter_map(|delimiter| {
26080 let prefix = delimiter.trim_end();
26081 if comment_candidate.starts_with(prefix) {
26082 let is_repl = if let Some(stripped_comment) = comment_candidate.strip_prefix(prefix)
26083 {
26084 stripped_comment.starts_with(" %%")
26085 } else {
26086 false
26087 };
26088 Some((delimiter, prefix.len(), is_repl))
26089 } else {
26090 None
26091 }
26092 })
26093 .max_by_key(|(_, len, _)| *len)?;
26094
26095 if let Some(BlockCommentConfig {
26096 start: block_start, ..
26097 }) = language.block_comment()
26098 {
26099 let block_start_trimmed = block_start.trim_end();
26100 if block_start_trimmed.starts_with(delimiter.trim_end()) {
26101 let line_content = snapshot
26102 .chars_for_range(range.clone())
26103 .skip(num_of_whitespaces)
26104 .take(block_start_trimmed.len())
26105 .collect::<String>();
26106
26107 if line_content.starts_with(block_start_trimmed) {
26108 return None;
26109 }
26110 }
26111 }
26112
26113 let cursor_is_placed_after_comment_marker =
26114 num_of_whitespaces + trimmed_len <= start_point.column as usize;
26115 if cursor_is_placed_after_comment_marker {
26116 if !is_repl {
26117 return Some(delimiter.clone());
26118 }
26119
26120 let line_content_after_cursor: String = snapshot
26121 .chars_for_range(range)
26122 .skip(start_point.column as usize)
26123 .collect();
26124
26125 if line_content_after_cursor.trim().is_empty() {
26126 return None;
26127 } else {
26128 return Some(delimiter.clone());
26129 }
26130 } else {
26131 None
26132 }
26133}
26134
26135fn documentation_delimiter_for_newline(
26136 start_point: &Point,
26137 buffer: &MultiBufferSnapshot,
26138 language: &LanguageScope,
26139 newline_config: &mut NewlineConfig,
26140) -> Option<Arc<str>> {
26141 let BlockCommentConfig {
26142 start: start_tag,
26143 end: end_tag,
26144 prefix: delimiter,
26145 tab_size: len,
26146 } = language.documentation_comment()?;
26147 let is_within_block_comment = buffer
26148 .language_scope_at(*start_point)
26149 .is_some_and(|scope| scope.override_name() == Some("comment"));
26150 if !is_within_block_comment {
26151 return None;
26152 }
26153
26154 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
26155
26156 let num_of_whitespaces = snapshot
26157 .chars_for_range(range.clone())
26158 .take_while(|c| c.is_whitespace())
26159 .count();
26160
26161 // 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.
26162 let column = start_point.column;
26163 let cursor_is_after_start_tag = {
26164 let start_tag_len = start_tag.len();
26165 let start_tag_line = snapshot
26166 .chars_for_range(range.clone())
26167 .skip(num_of_whitespaces)
26168 .take(start_tag_len)
26169 .collect::<String>();
26170 if start_tag_line.starts_with(start_tag.as_ref()) {
26171 num_of_whitespaces + start_tag_len <= column as usize
26172 } else {
26173 false
26174 }
26175 };
26176
26177 let cursor_is_after_delimiter = {
26178 let delimiter_trim = delimiter.trim_end();
26179 let delimiter_line = snapshot
26180 .chars_for_range(range.clone())
26181 .skip(num_of_whitespaces)
26182 .take(delimiter_trim.len())
26183 .collect::<String>();
26184 if delimiter_line.starts_with(delimiter_trim) {
26185 num_of_whitespaces + delimiter_trim.len() <= column as usize
26186 } else {
26187 false
26188 }
26189 };
26190
26191 let mut needs_extra_line = false;
26192 let mut extra_line_additional_indent = IndentSize::spaces(0);
26193
26194 let cursor_is_before_end_tag_if_exists = {
26195 let mut char_position = 0u32;
26196 let mut end_tag_offset = None;
26197
26198 'outer: for chunk in snapshot.text_for_range(range) {
26199 if let Some(byte_pos) = chunk.find(&**end_tag) {
26200 let chars_before_match = chunk[..byte_pos].chars().count() as u32;
26201 end_tag_offset = Some(char_position + chars_before_match);
26202 break 'outer;
26203 }
26204 char_position += chunk.chars().count() as u32;
26205 }
26206
26207 if let Some(end_tag_offset) = end_tag_offset {
26208 let cursor_is_before_end_tag = column <= end_tag_offset;
26209 if cursor_is_after_start_tag {
26210 if cursor_is_before_end_tag {
26211 needs_extra_line = true;
26212 }
26213 let cursor_is_at_start_of_end_tag = column == end_tag_offset;
26214 if cursor_is_at_start_of_end_tag {
26215 extra_line_additional_indent.len = *len;
26216 }
26217 }
26218 cursor_is_before_end_tag
26219 } else {
26220 true
26221 }
26222 };
26223
26224 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
26225 && cursor_is_before_end_tag_if_exists
26226 {
26227 let additional_indent = if cursor_is_after_start_tag {
26228 IndentSize::spaces(*len)
26229 } else {
26230 IndentSize::spaces(0)
26231 };
26232
26233 *newline_config = NewlineConfig::Newline {
26234 additional_indent,
26235 extra_line_additional_indent: if needs_extra_line {
26236 Some(extra_line_additional_indent)
26237 } else {
26238 None
26239 },
26240 prevent_auto_indent: true,
26241 };
26242 Some(delimiter.clone())
26243 } else {
26244 None
26245 }
26246}
26247
26248const ORDERED_LIST_MAX_MARKER_LEN: usize = 16;
26249
26250fn list_delimiter_for_newline(
26251 start_point: &Point,
26252 buffer: &MultiBufferSnapshot,
26253 language: &LanguageScope,
26254 newline_config: &mut NewlineConfig,
26255) -> Option<Arc<str>> {
26256 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
26257
26258 let num_of_whitespaces = snapshot
26259 .chars_for_range(range.clone())
26260 .take_while(|c| c.is_whitespace())
26261 .count();
26262
26263 let task_list_entries: Vec<_> = language
26264 .task_list()
26265 .into_iter()
26266 .flat_map(|config| {
26267 config
26268 .prefixes
26269 .iter()
26270 .map(|prefix| (prefix.as_ref(), config.continuation.as_ref()))
26271 })
26272 .collect();
26273 let unordered_list_entries: Vec<_> = language
26274 .unordered_list()
26275 .iter()
26276 .map(|marker| (marker.as_ref(), marker.as_ref()))
26277 .collect();
26278
26279 let all_entries: Vec<_> = task_list_entries
26280 .into_iter()
26281 .chain(unordered_list_entries)
26282 .collect();
26283
26284 if let Some(max_prefix_len) = all_entries.iter().map(|(p, _)| p.len()).max() {
26285 let candidate: String = snapshot
26286 .chars_for_range(range.clone())
26287 .skip(num_of_whitespaces)
26288 .take(max_prefix_len)
26289 .collect();
26290
26291 if let Some((prefix, continuation)) = all_entries
26292 .iter()
26293 .filter(|(prefix, _)| candidate.starts_with(*prefix))
26294 .max_by_key(|(prefix, _)| prefix.len())
26295 {
26296 let end_of_prefix = num_of_whitespaces + prefix.len();
26297 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
26298 let has_content_after_marker = snapshot
26299 .chars_for_range(range)
26300 .skip(end_of_prefix)
26301 .any(|c| !c.is_whitespace());
26302
26303 if has_content_after_marker && cursor_is_after_prefix {
26304 return Some((*continuation).into());
26305 }
26306
26307 if start_point.column as usize == end_of_prefix {
26308 if num_of_whitespaces == 0 {
26309 *newline_config = NewlineConfig::ClearCurrentLine;
26310 } else {
26311 *newline_config = NewlineConfig::UnindentCurrentLine {
26312 continuation: (*continuation).into(),
26313 };
26314 }
26315 }
26316
26317 return None;
26318 }
26319 }
26320
26321 let candidate: String = snapshot
26322 .chars_for_range(range.clone())
26323 .skip(num_of_whitespaces)
26324 .take(ORDERED_LIST_MAX_MARKER_LEN)
26325 .collect();
26326
26327 for ordered_config in language.ordered_list() {
26328 let regex = match Regex::new(&ordered_config.pattern) {
26329 Ok(r) => r,
26330 Err(_) => continue,
26331 };
26332
26333 if let Some(captures) = regex.captures(&candidate) {
26334 let full_match = captures.get(0)?;
26335 let marker_len = full_match.len();
26336 let end_of_prefix = num_of_whitespaces + marker_len;
26337 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
26338
26339 let has_content_after_marker = snapshot
26340 .chars_for_range(range)
26341 .skip(end_of_prefix)
26342 .any(|c| !c.is_whitespace());
26343
26344 if has_content_after_marker && cursor_is_after_prefix {
26345 let number: u32 = captures.get(1)?.as_str().parse().ok()?;
26346 let continuation = ordered_config
26347 .format
26348 .replace("{1}", &(number + 1).to_string());
26349 return Some(continuation.into());
26350 }
26351
26352 if start_point.column as usize == end_of_prefix {
26353 let continuation = ordered_config.format.replace("{1}", "1");
26354 if num_of_whitespaces == 0 {
26355 *newline_config = NewlineConfig::ClearCurrentLine;
26356 } else {
26357 *newline_config = NewlineConfig::UnindentCurrentLine {
26358 continuation: continuation.into(),
26359 };
26360 }
26361 }
26362
26363 return None;
26364 }
26365 }
26366
26367 None
26368}
26369
26370fn is_list_prefix_row(
26371 row: MultiBufferRow,
26372 buffer: &MultiBufferSnapshot,
26373 language: &LanguageScope,
26374) -> bool {
26375 let Some((snapshot, range)) = buffer.buffer_line_for_row(row) else {
26376 return false;
26377 };
26378
26379 let num_of_whitespaces = snapshot
26380 .chars_for_range(range.clone())
26381 .take_while(|c| c.is_whitespace())
26382 .count();
26383
26384 let task_list_prefixes: Vec<_> = language
26385 .task_list()
26386 .into_iter()
26387 .flat_map(|config| {
26388 config
26389 .prefixes
26390 .iter()
26391 .map(|p| p.as_ref())
26392 .collect::<Vec<_>>()
26393 })
26394 .collect();
26395 let unordered_list_markers: Vec<_> = language
26396 .unordered_list()
26397 .iter()
26398 .map(|marker| marker.as_ref())
26399 .collect();
26400 let all_prefixes: Vec<_> = task_list_prefixes
26401 .into_iter()
26402 .chain(unordered_list_markers)
26403 .collect();
26404 if let Some(max_prefix_len) = all_prefixes.iter().map(|p| p.len()).max() {
26405 let candidate: String = snapshot
26406 .chars_for_range(range.clone())
26407 .skip(num_of_whitespaces)
26408 .take(max_prefix_len)
26409 .collect();
26410 if all_prefixes
26411 .iter()
26412 .any(|prefix| candidate.starts_with(*prefix))
26413 {
26414 return true;
26415 }
26416 }
26417
26418 let ordered_list_candidate: String = snapshot
26419 .chars_for_range(range)
26420 .skip(num_of_whitespaces)
26421 .take(ORDERED_LIST_MAX_MARKER_LEN)
26422 .collect();
26423 for ordered_config in language.ordered_list() {
26424 let regex = match Regex::new(&ordered_config.pattern) {
26425 Ok(r) => r,
26426 Err(_) => continue,
26427 };
26428 if let Some(captures) = regex.captures(&ordered_list_candidate) {
26429 return captures.get(0).is_some();
26430 }
26431 }
26432
26433 false
26434}
26435
26436#[derive(Debug)]
26437enum NewlineConfig {
26438 /// Insert newline with optional additional indent and optional extra blank line
26439 Newline {
26440 additional_indent: IndentSize,
26441 extra_line_additional_indent: Option<IndentSize>,
26442 prevent_auto_indent: bool,
26443 },
26444 /// Clear the current line
26445 ClearCurrentLine,
26446 /// Unindent the current line and add continuation
26447 UnindentCurrentLine { continuation: Arc<str> },
26448}
26449
26450impl NewlineConfig {
26451 fn has_extra_line(&self) -> bool {
26452 matches!(
26453 self,
26454 Self::Newline {
26455 extra_line_additional_indent: Some(_),
26456 ..
26457 }
26458 )
26459 }
26460
26461 fn insert_extra_newline_brackets(
26462 buffer: &MultiBufferSnapshot,
26463 range: Range<MultiBufferOffset>,
26464 language: &language::LanguageScope,
26465 ) -> bool {
26466 let leading_whitespace_len = buffer
26467 .reversed_chars_at(range.start)
26468 .take_while(|c| c.is_whitespace() && *c != '\n')
26469 .map(|c| c.len_utf8())
26470 .sum::<usize>();
26471 let trailing_whitespace_len = buffer
26472 .chars_at(range.end)
26473 .take_while(|c| c.is_whitespace() && *c != '\n')
26474 .map(|c| c.len_utf8())
26475 .sum::<usize>();
26476 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
26477
26478 language.brackets().any(|(pair, enabled)| {
26479 let pair_start = pair.start.trim_end();
26480 let pair_end = pair.end.trim_start();
26481
26482 enabled
26483 && pair.newline
26484 && buffer.contains_str_at(range.end, pair_end)
26485 && buffer.contains_str_at(
26486 range.start.saturating_sub_usize(pair_start.len()),
26487 pair_start,
26488 )
26489 })
26490 }
26491
26492 fn insert_extra_newline_tree_sitter(
26493 buffer: &MultiBufferSnapshot,
26494 range: Range<MultiBufferOffset>,
26495 ) -> bool {
26496 let (buffer, range) = match buffer
26497 .range_to_buffer_ranges(range.start..=range.end)
26498 .as_slice()
26499 {
26500 [(buffer, range, _)] => (*buffer, range.clone()),
26501 _ => return false,
26502 };
26503 let pair = {
26504 let mut result: Option<BracketMatch<usize>> = None;
26505
26506 for pair in buffer
26507 .all_bracket_ranges(range.start.0..range.end.0)
26508 .filter(move |pair| {
26509 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
26510 })
26511 {
26512 let len = pair.close_range.end - pair.open_range.start;
26513
26514 if let Some(existing) = &result {
26515 let existing_len = existing.close_range.end - existing.open_range.start;
26516 if len > existing_len {
26517 continue;
26518 }
26519 }
26520
26521 result = Some(pair);
26522 }
26523
26524 result
26525 };
26526 let Some(pair) = pair else {
26527 return false;
26528 };
26529 pair.newline_only
26530 && buffer
26531 .chars_for_range(pair.open_range.end..range.start.0)
26532 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
26533 .all(|c| c.is_whitespace() && c != '\n')
26534 }
26535}
26536
26537fn update_uncommitted_diff_for_buffer(
26538 editor: Entity<Editor>,
26539 project: &Entity<Project>,
26540 buffers: impl IntoIterator<Item = Entity<Buffer>>,
26541 buffer: Entity<MultiBuffer>,
26542 cx: &mut App,
26543) -> Task<()> {
26544 let mut tasks = Vec::new();
26545 project.update(cx, |project, cx| {
26546 for buffer in buffers {
26547 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
26548 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
26549 }
26550 }
26551 });
26552 cx.spawn(async move |cx| {
26553 let diffs = future::join_all(tasks).await;
26554 if editor.read_with(cx, |editor, _cx| editor.temporary_diff_override) {
26555 return;
26556 }
26557
26558 buffer.update(cx, |buffer, cx| {
26559 for diff in diffs.into_iter().flatten() {
26560 buffer.add_diff(diff, cx);
26561 }
26562 });
26563 })
26564}
26565
26566fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
26567 let tab_size = tab_size.get() as usize;
26568 let mut width = offset;
26569
26570 for ch in text.chars() {
26571 width += if ch == '\t' {
26572 tab_size - (width % tab_size)
26573 } else {
26574 1
26575 };
26576 }
26577
26578 width - offset
26579}
26580
26581#[cfg(test)]
26582mod tests {
26583 use super::*;
26584
26585 #[test]
26586 fn test_string_size_with_expanded_tabs() {
26587 let nz = |val| NonZeroU32::new(val).unwrap();
26588 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
26589 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
26590 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
26591 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
26592 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
26593 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
26594 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
26595 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
26596 }
26597}
26598
26599/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
26600struct WordBreakingTokenizer<'a> {
26601 input: &'a str,
26602}
26603
26604impl<'a> WordBreakingTokenizer<'a> {
26605 fn new(input: &'a str) -> Self {
26606 Self { input }
26607 }
26608}
26609
26610fn is_char_ideographic(ch: char) -> bool {
26611 use unicode_script::Script::*;
26612 use unicode_script::UnicodeScript;
26613 matches!(ch.script(), Han | Tangut | Yi)
26614}
26615
26616fn is_grapheme_ideographic(text: &str) -> bool {
26617 text.chars().any(is_char_ideographic)
26618}
26619
26620fn is_grapheme_whitespace(text: &str) -> bool {
26621 text.chars().any(|x| x.is_whitespace())
26622}
26623
26624fn should_stay_with_preceding_ideograph(text: &str) -> bool {
26625 text.chars()
26626 .next()
26627 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
26628}
26629
26630#[derive(PartialEq, Eq, Debug, Clone, Copy)]
26631enum WordBreakToken<'a> {
26632 Word { token: &'a str, grapheme_len: usize },
26633 InlineWhitespace { token: &'a str, grapheme_len: usize },
26634 Newline,
26635}
26636
26637impl<'a> Iterator for WordBreakingTokenizer<'a> {
26638 /// Yields a span, the count of graphemes in the token, and whether it was
26639 /// whitespace. Note that it also breaks at word boundaries.
26640 type Item = WordBreakToken<'a>;
26641
26642 fn next(&mut self) -> Option<Self::Item> {
26643 use unicode_segmentation::UnicodeSegmentation;
26644 if self.input.is_empty() {
26645 return None;
26646 }
26647
26648 let mut iter = self.input.graphemes(true).peekable();
26649 let mut offset = 0;
26650 let mut grapheme_len = 0;
26651 if let Some(first_grapheme) = iter.next() {
26652 let is_newline = first_grapheme == "\n";
26653 let is_whitespace = is_grapheme_whitespace(first_grapheme);
26654 offset += first_grapheme.len();
26655 grapheme_len += 1;
26656 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
26657 if let Some(grapheme) = iter.peek().copied()
26658 && should_stay_with_preceding_ideograph(grapheme)
26659 {
26660 offset += grapheme.len();
26661 grapheme_len += 1;
26662 }
26663 } else {
26664 let mut words = self.input[offset..].split_word_bound_indices().peekable();
26665 let mut next_word_bound = words.peek().copied();
26666 if next_word_bound.is_some_and(|(i, _)| i == 0) {
26667 next_word_bound = words.next();
26668 }
26669 while let Some(grapheme) = iter.peek().copied() {
26670 if next_word_bound.is_some_and(|(i, _)| i == offset) {
26671 break;
26672 };
26673 if is_grapheme_whitespace(grapheme) != is_whitespace
26674 || (grapheme == "\n") != is_newline
26675 {
26676 break;
26677 };
26678 offset += grapheme.len();
26679 grapheme_len += 1;
26680 iter.next();
26681 }
26682 }
26683 let token = &self.input[..offset];
26684 self.input = &self.input[offset..];
26685 if token == "\n" {
26686 Some(WordBreakToken::Newline)
26687 } else if is_whitespace {
26688 Some(WordBreakToken::InlineWhitespace {
26689 token,
26690 grapheme_len,
26691 })
26692 } else {
26693 Some(WordBreakToken::Word {
26694 token,
26695 grapheme_len,
26696 })
26697 }
26698 } else {
26699 None
26700 }
26701 }
26702}
26703
26704#[test]
26705fn test_word_breaking_tokenizer() {
26706 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
26707 ("", &[]),
26708 (" ", &[whitespace(" ", 2)]),
26709 ("Ʒ", &[word("Ʒ", 1)]),
26710 ("Ǽ", &[word("Ǽ", 1)]),
26711 ("⋑", &[word("⋑", 1)]),
26712 ("⋑⋑", &[word("⋑⋑", 2)]),
26713 (
26714 "原理,进而",
26715 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
26716 ),
26717 (
26718 "hello world",
26719 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
26720 ),
26721 (
26722 "hello, world",
26723 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
26724 ),
26725 (
26726 " hello world",
26727 &[
26728 whitespace(" ", 2),
26729 word("hello", 5),
26730 whitespace(" ", 1),
26731 word("world", 5),
26732 ],
26733 ),
26734 (
26735 "这是什么 \n 钢笔",
26736 &[
26737 word("这", 1),
26738 word("是", 1),
26739 word("什", 1),
26740 word("么", 1),
26741 whitespace(" ", 1),
26742 newline(),
26743 whitespace(" ", 1),
26744 word("钢", 1),
26745 word("笔", 1),
26746 ],
26747 ),
26748 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
26749 ];
26750
26751 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26752 WordBreakToken::Word {
26753 token,
26754 grapheme_len,
26755 }
26756 }
26757
26758 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26759 WordBreakToken::InlineWhitespace {
26760 token,
26761 grapheme_len,
26762 }
26763 }
26764
26765 fn newline() -> WordBreakToken<'static> {
26766 WordBreakToken::Newline
26767 }
26768
26769 for (input, result) in tests {
26770 assert_eq!(
26771 WordBreakingTokenizer::new(input)
26772 .collect::<Vec<_>>()
26773 .as_slice(),
26774 *result,
26775 );
26776 }
26777}
26778
26779fn wrap_with_prefix(
26780 first_line_prefix: String,
26781 subsequent_lines_prefix: String,
26782 unwrapped_text: String,
26783 wrap_column: usize,
26784 tab_size: NonZeroU32,
26785 preserve_existing_whitespace: bool,
26786) -> String {
26787 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
26788 let subsequent_lines_prefix_len =
26789 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
26790 let mut wrapped_text = String::new();
26791 let mut current_line = first_line_prefix;
26792 let mut is_first_line = true;
26793
26794 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
26795 let mut current_line_len = first_line_prefix_len;
26796 let mut in_whitespace = false;
26797 for token in tokenizer {
26798 let have_preceding_whitespace = in_whitespace;
26799 match token {
26800 WordBreakToken::Word {
26801 token,
26802 grapheme_len,
26803 } => {
26804 in_whitespace = false;
26805 let current_prefix_len = if is_first_line {
26806 first_line_prefix_len
26807 } else {
26808 subsequent_lines_prefix_len
26809 };
26810 if current_line_len + grapheme_len > wrap_column
26811 && current_line_len != current_prefix_len
26812 {
26813 wrapped_text.push_str(current_line.trim_end());
26814 wrapped_text.push('\n');
26815 is_first_line = false;
26816 current_line = subsequent_lines_prefix.clone();
26817 current_line_len = subsequent_lines_prefix_len;
26818 }
26819 current_line.push_str(token);
26820 current_line_len += grapheme_len;
26821 }
26822 WordBreakToken::InlineWhitespace {
26823 mut token,
26824 mut grapheme_len,
26825 } => {
26826 in_whitespace = true;
26827 if have_preceding_whitespace && !preserve_existing_whitespace {
26828 continue;
26829 }
26830 if !preserve_existing_whitespace {
26831 // Keep a single whitespace grapheme as-is
26832 if let Some(first) =
26833 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
26834 {
26835 token = first;
26836 } else {
26837 token = " ";
26838 }
26839 grapheme_len = 1;
26840 }
26841 let current_prefix_len = if is_first_line {
26842 first_line_prefix_len
26843 } else {
26844 subsequent_lines_prefix_len
26845 };
26846 if current_line_len + grapheme_len > wrap_column {
26847 wrapped_text.push_str(current_line.trim_end());
26848 wrapped_text.push('\n');
26849 is_first_line = false;
26850 current_line = subsequent_lines_prefix.clone();
26851 current_line_len = subsequent_lines_prefix_len;
26852 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
26853 current_line.push_str(token);
26854 current_line_len += grapheme_len;
26855 }
26856 }
26857 WordBreakToken::Newline => {
26858 in_whitespace = true;
26859 let current_prefix_len = if is_first_line {
26860 first_line_prefix_len
26861 } else {
26862 subsequent_lines_prefix_len
26863 };
26864 if preserve_existing_whitespace {
26865 wrapped_text.push_str(current_line.trim_end());
26866 wrapped_text.push('\n');
26867 is_first_line = false;
26868 current_line = subsequent_lines_prefix.clone();
26869 current_line_len = subsequent_lines_prefix_len;
26870 } else if have_preceding_whitespace {
26871 continue;
26872 } else if current_line_len + 1 > wrap_column
26873 && current_line_len != current_prefix_len
26874 {
26875 wrapped_text.push_str(current_line.trim_end());
26876 wrapped_text.push('\n');
26877 is_first_line = false;
26878 current_line = subsequent_lines_prefix.clone();
26879 current_line_len = subsequent_lines_prefix_len;
26880 } else if current_line_len != current_prefix_len {
26881 current_line.push(' ');
26882 current_line_len += 1;
26883 }
26884 }
26885 }
26886 }
26887
26888 if !current_line.is_empty() {
26889 wrapped_text.push_str(¤t_line);
26890 }
26891 wrapped_text
26892}
26893
26894#[test]
26895fn test_wrap_with_prefix() {
26896 assert_eq!(
26897 wrap_with_prefix(
26898 "# ".to_string(),
26899 "# ".to_string(),
26900 "abcdefg".to_string(),
26901 4,
26902 NonZeroU32::new(4).unwrap(),
26903 false,
26904 ),
26905 "# abcdefg"
26906 );
26907 assert_eq!(
26908 wrap_with_prefix(
26909 "".to_string(),
26910 "".to_string(),
26911 "\thello world".to_string(),
26912 8,
26913 NonZeroU32::new(4).unwrap(),
26914 false,
26915 ),
26916 "hello\nworld"
26917 );
26918 assert_eq!(
26919 wrap_with_prefix(
26920 "// ".to_string(),
26921 "// ".to_string(),
26922 "xx \nyy zz aa bb cc".to_string(),
26923 12,
26924 NonZeroU32::new(4).unwrap(),
26925 false,
26926 ),
26927 "// xx yy zz\n// aa bb cc"
26928 );
26929 assert_eq!(
26930 wrap_with_prefix(
26931 String::new(),
26932 String::new(),
26933 "这是什么 \n 钢笔".to_string(),
26934 3,
26935 NonZeroU32::new(4).unwrap(),
26936 false,
26937 ),
26938 "这是什\n么 钢\n笔"
26939 );
26940 assert_eq!(
26941 wrap_with_prefix(
26942 String::new(),
26943 String::new(),
26944 format!("foo{}bar", '\u{2009}'), // thin space
26945 80,
26946 NonZeroU32::new(4).unwrap(),
26947 false,
26948 ),
26949 format!("foo{}bar", '\u{2009}')
26950 );
26951}
26952
26953pub trait CollaborationHub {
26954 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
26955 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
26956 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
26957}
26958
26959impl CollaborationHub for Entity<Project> {
26960 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
26961 self.read(cx).collaborators()
26962 }
26963
26964 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
26965 self.read(cx).user_store().read(cx).participant_indices()
26966 }
26967
26968 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
26969 let this = self.read(cx);
26970 let user_ids = this.collaborators().values().map(|c| c.user_id);
26971 this.user_store().read(cx).participant_names(user_ids, cx)
26972 }
26973}
26974
26975pub trait SemanticsProvider {
26976 fn hover(
26977 &self,
26978 buffer: &Entity<Buffer>,
26979 position: text::Anchor,
26980 cx: &mut App,
26981 ) -> Option<Task<Option<Vec<project::Hover>>>>;
26982
26983 fn inline_values(
26984 &self,
26985 buffer_handle: Entity<Buffer>,
26986 range: Range<text::Anchor>,
26987 cx: &mut App,
26988 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
26989
26990 fn applicable_inlay_chunks(
26991 &self,
26992 buffer: &Entity<Buffer>,
26993 ranges: &[Range<text::Anchor>],
26994 cx: &mut App,
26995 ) -> Vec<Range<BufferRow>>;
26996
26997 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
26998
26999 fn inlay_hints(
27000 &self,
27001 invalidate: InvalidationStrategy,
27002 buffer: Entity<Buffer>,
27003 ranges: Vec<Range<text::Anchor>>,
27004 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
27005 cx: &mut App,
27006 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
27007
27008 fn semantic_tokens(
27009 &self,
27010 buffer: Entity<Buffer>,
27011 refresh: Option<RefreshForServer>,
27012 cx: &mut App,
27013 ) -> Option<Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>>>;
27014
27015 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
27016
27017 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
27018
27019 fn document_highlights(
27020 &self,
27021 buffer: &Entity<Buffer>,
27022 position: text::Anchor,
27023 cx: &mut App,
27024 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
27025
27026 fn definitions(
27027 &self,
27028 buffer: &Entity<Buffer>,
27029 position: text::Anchor,
27030 kind: GotoDefinitionKind,
27031 cx: &mut App,
27032 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
27033
27034 fn range_for_rename(
27035 &self,
27036 buffer: &Entity<Buffer>,
27037 position: text::Anchor,
27038 cx: &mut App,
27039 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
27040
27041 fn perform_rename(
27042 &self,
27043 buffer: &Entity<Buffer>,
27044 position: text::Anchor,
27045 new_name: String,
27046 cx: &mut App,
27047 ) -> Option<Task<Result<ProjectTransaction>>>;
27048}
27049
27050pub trait CompletionProvider {
27051 fn completions(
27052 &self,
27053 excerpt_id: ExcerptId,
27054 buffer: &Entity<Buffer>,
27055 buffer_position: text::Anchor,
27056 trigger: CompletionContext,
27057 window: &mut Window,
27058 cx: &mut Context<Editor>,
27059 ) -> Task<Result<Vec<CompletionResponse>>>;
27060
27061 fn resolve_completions(
27062 &self,
27063 _buffer: Entity<Buffer>,
27064 _completion_indices: Vec<usize>,
27065 _completions: Rc<RefCell<Box<[Completion]>>>,
27066 _cx: &mut Context<Editor>,
27067 ) -> Task<Result<bool>> {
27068 Task::ready(Ok(false))
27069 }
27070
27071 fn apply_additional_edits_for_completion(
27072 &self,
27073 _buffer: Entity<Buffer>,
27074 _completions: Rc<RefCell<Box<[Completion]>>>,
27075 _completion_index: usize,
27076 _push_to_history: bool,
27077 _all_commit_ranges: Vec<Range<language::Anchor>>,
27078 _cx: &mut Context<Editor>,
27079 ) -> Task<Result<Option<language::Transaction>>> {
27080 Task::ready(Ok(None))
27081 }
27082
27083 fn is_completion_trigger(
27084 &self,
27085 buffer: &Entity<Buffer>,
27086 position: language::Anchor,
27087 text: &str,
27088 trigger_in_words: bool,
27089 cx: &mut Context<Editor>,
27090 ) -> bool;
27091
27092 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
27093
27094 fn sort_completions(&self) -> bool {
27095 true
27096 }
27097
27098 fn filter_completions(&self) -> bool {
27099 true
27100 }
27101
27102 fn show_snippets(&self) -> bool {
27103 false
27104 }
27105}
27106
27107pub trait CodeActionProvider {
27108 fn id(&self) -> Arc<str>;
27109
27110 fn code_actions(
27111 &self,
27112 buffer: &Entity<Buffer>,
27113 range: Range<text::Anchor>,
27114 window: &mut Window,
27115 cx: &mut App,
27116 ) -> Task<Result<Vec<CodeAction>>>;
27117
27118 fn apply_code_action(
27119 &self,
27120 buffer_handle: Entity<Buffer>,
27121 action: CodeAction,
27122 excerpt_id: ExcerptId,
27123 push_to_history: bool,
27124 window: &mut Window,
27125 cx: &mut App,
27126 ) -> Task<Result<ProjectTransaction>>;
27127}
27128
27129impl CodeActionProvider for Entity<Project> {
27130 fn id(&self) -> Arc<str> {
27131 "project".into()
27132 }
27133
27134 fn code_actions(
27135 &self,
27136 buffer: &Entity<Buffer>,
27137 range: Range<text::Anchor>,
27138 _window: &mut Window,
27139 cx: &mut App,
27140 ) -> Task<Result<Vec<CodeAction>>> {
27141 self.update(cx, |project, cx| {
27142 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
27143 let code_actions = project.code_actions(buffer, range, None, cx);
27144 cx.background_spawn(async move {
27145 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
27146 Ok(code_lens_actions
27147 .context("code lens fetch")?
27148 .into_iter()
27149 .flatten()
27150 .chain(
27151 code_actions
27152 .context("code action fetch")?
27153 .into_iter()
27154 .flatten(),
27155 )
27156 .collect())
27157 })
27158 })
27159 }
27160
27161 fn apply_code_action(
27162 &self,
27163 buffer_handle: Entity<Buffer>,
27164 action: CodeAction,
27165 _excerpt_id: ExcerptId,
27166 push_to_history: bool,
27167 _window: &mut Window,
27168 cx: &mut App,
27169 ) -> Task<Result<ProjectTransaction>> {
27170 self.update(cx, |project, cx| {
27171 project.apply_code_action(buffer_handle, action, push_to_history, cx)
27172 })
27173 }
27174}
27175
27176fn snippet_completions(
27177 project: &Project,
27178 buffer: &Entity<Buffer>,
27179 buffer_anchor: text::Anchor,
27180 classifier: CharClassifier,
27181 cx: &mut App,
27182) -> Task<Result<CompletionResponse>> {
27183 let languages = buffer.read(cx).languages_at(buffer_anchor);
27184 let snippet_store = project.snippets().read(cx);
27185
27186 let scopes: Vec<_> = languages
27187 .iter()
27188 .filter_map(|language| {
27189 let language_name = language.lsp_id();
27190 let snippets = snippet_store.snippets_for(Some(language_name), cx);
27191
27192 if snippets.is_empty() {
27193 None
27194 } else {
27195 Some((language.default_scope(), snippets))
27196 }
27197 })
27198 .collect();
27199
27200 if scopes.is_empty() {
27201 return Task::ready(Ok(CompletionResponse {
27202 completions: vec![],
27203 display_options: CompletionDisplayOptions::default(),
27204 is_incomplete: false,
27205 }));
27206 }
27207
27208 let snapshot = buffer.read(cx).text_snapshot();
27209 let executor = cx.background_executor().clone();
27210
27211 cx.background_spawn(async move {
27212 let is_word_char = |c| classifier.is_word(c);
27213
27214 let mut is_incomplete = false;
27215 let mut completions: Vec<Completion> = Vec::new();
27216
27217 const MAX_PREFIX_LEN: usize = 128;
27218 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
27219 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
27220 let window_start = snapshot.clip_offset(window_start, Bias::Left);
27221
27222 let max_buffer_window: String = snapshot
27223 .text_for_range(window_start..buffer_offset)
27224 .collect();
27225
27226 if max_buffer_window.is_empty() {
27227 return Ok(CompletionResponse {
27228 completions: vec![],
27229 display_options: CompletionDisplayOptions::default(),
27230 is_incomplete: true,
27231 });
27232 }
27233
27234 for (_scope, snippets) in scopes.into_iter() {
27235 // Sort snippets by word count to match longer snippet prefixes first.
27236 let mut sorted_snippet_candidates = snippets
27237 .iter()
27238 .enumerate()
27239 .flat_map(|(snippet_ix, snippet)| {
27240 snippet
27241 .prefix
27242 .iter()
27243 .enumerate()
27244 .map(move |(prefix_ix, prefix)| {
27245 let word_count =
27246 snippet_candidate_suffixes(prefix, &is_word_char).count();
27247 ((snippet_ix, prefix_ix), prefix, word_count)
27248 })
27249 })
27250 .collect_vec();
27251 sorted_snippet_candidates
27252 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
27253
27254 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
27255
27256 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, &is_word_char)
27257 .take(
27258 sorted_snippet_candidates
27259 .first()
27260 .map(|(_, _, word_count)| *word_count)
27261 .unwrap_or_default(),
27262 )
27263 .collect_vec();
27264
27265 const MAX_RESULTS: usize = 100;
27266 // Each match also remembers how many characters from the buffer it consumed
27267 let mut matches: Vec<(StringMatch, usize)> = vec![];
27268
27269 let mut snippet_list_cutoff_index = 0;
27270 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
27271 let word_count = buffer_index + 1;
27272 // Increase `snippet_list_cutoff_index` until we have all of the
27273 // snippets with sufficiently many words.
27274 while sorted_snippet_candidates
27275 .get(snippet_list_cutoff_index)
27276 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
27277 *snippet_word_count >= word_count
27278 })
27279 {
27280 snippet_list_cutoff_index += 1;
27281 }
27282
27283 // Take only the candidates with at least `word_count` many words
27284 let snippet_candidates_at_word_len =
27285 &sorted_snippet_candidates[..snippet_list_cutoff_index];
27286
27287 let candidates = snippet_candidates_at_word_len
27288 .iter()
27289 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
27290 .enumerate() // index in `sorted_snippet_candidates`
27291 // First char must match
27292 .filter(|(_ix, prefix)| {
27293 itertools::equal(
27294 prefix
27295 .chars()
27296 .next()
27297 .into_iter()
27298 .flat_map(|c| c.to_lowercase()),
27299 buffer_window
27300 .chars()
27301 .next()
27302 .into_iter()
27303 .flat_map(|c| c.to_lowercase()),
27304 )
27305 })
27306 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
27307 .collect::<Vec<StringMatchCandidate>>();
27308
27309 matches.extend(
27310 fuzzy::match_strings(
27311 &candidates,
27312 &buffer_window,
27313 buffer_window.chars().any(|c| c.is_uppercase()),
27314 true,
27315 MAX_RESULTS - matches.len(), // always prioritize longer snippets
27316 &Default::default(),
27317 executor.clone(),
27318 )
27319 .await
27320 .into_iter()
27321 .map(|string_match| (string_match, buffer_window.len())),
27322 );
27323
27324 if matches.len() >= MAX_RESULTS {
27325 break;
27326 }
27327 }
27328
27329 let to_lsp = |point: &text::Anchor| {
27330 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
27331 point_to_lsp(end)
27332 };
27333 let lsp_end = to_lsp(&buffer_anchor);
27334
27335 if matches.len() >= MAX_RESULTS {
27336 is_incomplete = true;
27337 }
27338
27339 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
27340 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
27341 sorted_snippet_candidates[string_match.candidate_id];
27342 let snippet = &snippets[snippet_index];
27343 let start = buffer_offset - buffer_window_len;
27344 let start = snapshot.anchor_before(start);
27345 let range = start..buffer_anchor;
27346 let lsp_start = to_lsp(&start);
27347 let lsp_range = lsp::Range {
27348 start: lsp_start,
27349 end: lsp_end,
27350 };
27351 Completion {
27352 replace_range: range,
27353 new_text: snippet.body.clone(),
27354 source: CompletionSource::Lsp {
27355 insert_range: None,
27356 server_id: LanguageServerId(usize::MAX),
27357 resolved: true,
27358 lsp_completion: Box::new(lsp::CompletionItem {
27359 label: snippet.prefix.first().unwrap().clone(),
27360 kind: Some(CompletionItemKind::SNIPPET),
27361 label_details: snippet.description.as_ref().map(|description| {
27362 lsp::CompletionItemLabelDetails {
27363 detail: Some(description.clone()),
27364 description: None,
27365 }
27366 }),
27367 insert_text_format: Some(InsertTextFormat::SNIPPET),
27368 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
27369 lsp::InsertReplaceEdit {
27370 new_text: snippet.body.clone(),
27371 insert: lsp_range,
27372 replace: lsp_range,
27373 },
27374 )),
27375 filter_text: Some(snippet.body.clone()),
27376 sort_text: Some(char::MAX.to_string()),
27377 ..lsp::CompletionItem::default()
27378 }),
27379 lsp_defaults: None,
27380 },
27381 label: CodeLabel {
27382 text: matching_prefix.clone(),
27383 runs: Vec::new(),
27384 filter_range: 0..matching_prefix.len(),
27385 },
27386 icon_path: None,
27387 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
27388 single_line: snippet.name.clone().into(),
27389 plain_text: snippet
27390 .description
27391 .clone()
27392 .map(|description| description.into()),
27393 }),
27394 insert_text_mode: None,
27395 confirm: None,
27396 match_start: Some(start),
27397 snippet_deduplication_key: Some((snippet_index, prefix_index)),
27398 }
27399 }));
27400 }
27401
27402 Ok(CompletionResponse {
27403 completions,
27404 display_options: CompletionDisplayOptions::default(),
27405 is_incomplete,
27406 })
27407 })
27408}
27409
27410impl CompletionProvider for Entity<Project> {
27411 fn completions(
27412 &self,
27413 _excerpt_id: ExcerptId,
27414 buffer: &Entity<Buffer>,
27415 buffer_position: text::Anchor,
27416 options: CompletionContext,
27417 _window: &mut Window,
27418 cx: &mut Context<Editor>,
27419 ) -> Task<Result<Vec<CompletionResponse>>> {
27420 self.update(cx, |project, cx| {
27421 let task = project.completions(buffer, buffer_position, options, cx);
27422 cx.background_spawn(task)
27423 })
27424 }
27425
27426 fn resolve_completions(
27427 &self,
27428 buffer: Entity<Buffer>,
27429 completion_indices: Vec<usize>,
27430 completions: Rc<RefCell<Box<[Completion]>>>,
27431 cx: &mut Context<Editor>,
27432 ) -> Task<Result<bool>> {
27433 self.update(cx, |project, cx| {
27434 project.lsp_store().update(cx, |lsp_store, cx| {
27435 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
27436 })
27437 })
27438 }
27439
27440 fn apply_additional_edits_for_completion(
27441 &self,
27442 buffer: Entity<Buffer>,
27443 completions: Rc<RefCell<Box<[Completion]>>>,
27444 completion_index: usize,
27445 push_to_history: bool,
27446 all_commit_ranges: Vec<Range<language::Anchor>>,
27447 cx: &mut Context<Editor>,
27448 ) -> Task<Result<Option<language::Transaction>>> {
27449 self.update(cx, |project, cx| {
27450 project.lsp_store().update(cx, |lsp_store, cx| {
27451 lsp_store.apply_additional_edits_for_completion(
27452 buffer,
27453 completions,
27454 completion_index,
27455 push_to_history,
27456 all_commit_ranges,
27457 cx,
27458 )
27459 })
27460 })
27461 }
27462
27463 fn is_completion_trigger(
27464 &self,
27465 buffer: &Entity<Buffer>,
27466 position: language::Anchor,
27467 text: &str,
27468 trigger_in_words: bool,
27469 cx: &mut Context<Editor>,
27470 ) -> bool {
27471 let mut chars = text.chars();
27472 let char = if let Some(char) = chars.next() {
27473 char
27474 } else {
27475 return false;
27476 };
27477 if chars.next().is_some() {
27478 return false;
27479 }
27480
27481 let buffer = buffer.read(cx);
27482 let snapshot = buffer.snapshot();
27483 let classifier = snapshot
27484 .char_classifier_at(position)
27485 .scope_context(Some(CharScopeContext::Completion));
27486 if trigger_in_words && classifier.is_word(char) {
27487 return true;
27488 }
27489
27490 buffer.completion_triggers().contains(text)
27491 }
27492
27493 fn show_snippets(&self) -> bool {
27494 true
27495 }
27496}
27497
27498impl SemanticsProvider for WeakEntity<Project> {
27499 fn hover(
27500 &self,
27501 buffer: &Entity<Buffer>,
27502 position: text::Anchor,
27503 cx: &mut App,
27504 ) -> Option<Task<Option<Vec<project::Hover>>>> {
27505 self.update(cx, |project, cx| project.hover(buffer, position, cx))
27506 .ok()
27507 }
27508
27509 fn document_highlights(
27510 &self,
27511 buffer: &Entity<Buffer>,
27512 position: text::Anchor,
27513 cx: &mut App,
27514 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
27515 self.update(cx, |project, cx| {
27516 project.document_highlights(buffer, position, cx)
27517 })
27518 .ok()
27519 }
27520
27521 fn definitions(
27522 &self,
27523 buffer: &Entity<Buffer>,
27524 position: text::Anchor,
27525 kind: GotoDefinitionKind,
27526 cx: &mut App,
27527 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
27528 self.update(cx, |project, cx| match kind {
27529 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
27530 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
27531 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
27532 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
27533 })
27534 .ok()
27535 }
27536
27537 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27538 self.update(cx, |project, cx| {
27539 if project
27540 .active_debug_session(cx)
27541 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
27542 {
27543 return true;
27544 }
27545
27546 buffer.update(cx, |buffer, cx| {
27547 project.any_language_server_supports_inlay_hints(buffer, cx)
27548 })
27549 })
27550 .unwrap_or(false)
27551 }
27552
27553 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27554 self.update(cx, |project, cx| {
27555 buffer.update(cx, |buffer, cx| {
27556 project.any_language_server_supports_semantic_tokens(buffer, cx)
27557 })
27558 })
27559 .unwrap_or(false)
27560 }
27561
27562 fn inline_values(
27563 &self,
27564 buffer_handle: Entity<Buffer>,
27565 range: Range<text::Anchor>,
27566 cx: &mut App,
27567 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
27568 self.update(cx, |project, cx| {
27569 let (session, active_stack_frame) = project.active_debug_session(cx)?;
27570
27571 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
27572 })
27573 .ok()
27574 .flatten()
27575 }
27576
27577 fn applicable_inlay_chunks(
27578 &self,
27579 buffer: &Entity<Buffer>,
27580 ranges: &[Range<text::Anchor>],
27581 cx: &mut App,
27582 ) -> Vec<Range<BufferRow>> {
27583 self.update(cx, |project, cx| {
27584 project.lsp_store().update(cx, |lsp_store, cx| {
27585 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
27586 })
27587 })
27588 .unwrap_or_default()
27589 }
27590
27591 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
27592 self.update(cx, |project, cx| {
27593 project.lsp_store().update(cx, |lsp_store, _| {
27594 lsp_store.invalidate_inlay_hints(for_buffers)
27595 })
27596 })
27597 .ok();
27598 }
27599
27600 fn inlay_hints(
27601 &self,
27602 invalidate: InvalidationStrategy,
27603 buffer: Entity<Buffer>,
27604 ranges: Vec<Range<text::Anchor>>,
27605 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
27606 cx: &mut App,
27607 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
27608 self.update(cx, |project, cx| {
27609 project.lsp_store().update(cx, |lsp_store, cx| {
27610 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
27611 })
27612 })
27613 .ok()
27614 }
27615
27616 fn semantic_tokens(
27617 &self,
27618 buffer: Entity<Buffer>,
27619 refresh: Option<RefreshForServer>,
27620 cx: &mut App,
27621 ) -> Option<Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>>> {
27622 self.update(cx, |this, cx| {
27623 this.lsp_store().update(cx, |lsp_store, cx| {
27624 lsp_store.semantic_tokens(buffer, refresh, cx)
27625 })
27626 })
27627 .ok()
27628 }
27629
27630 fn range_for_rename(
27631 &self,
27632 buffer: &Entity<Buffer>,
27633 position: text::Anchor,
27634 cx: &mut App,
27635 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
27636 self.update(cx, |project, cx| {
27637 let buffer = buffer.clone();
27638 let task = project.prepare_rename(buffer.clone(), position, cx);
27639 cx.spawn(async move |_, cx| {
27640 Ok(match task.await? {
27641 PrepareRenameResponse::Success(range) => Some(range),
27642 PrepareRenameResponse::InvalidPosition => None,
27643 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
27644 // Fallback on using TreeSitter info to determine identifier range
27645 buffer.read_with(cx, |buffer, _| {
27646 let snapshot = buffer.snapshot();
27647 let (range, kind) = snapshot.surrounding_word(position, None);
27648 if kind != Some(CharKind::Word) {
27649 return None;
27650 }
27651 Some(
27652 snapshot.anchor_before(range.start)
27653 ..snapshot.anchor_after(range.end),
27654 )
27655 })
27656 }
27657 })
27658 })
27659 })
27660 .ok()
27661 }
27662
27663 fn perform_rename(
27664 &self,
27665 buffer: &Entity<Buffer>,
27666 position: text::Anchor,
27667 new_name: String,
27668 cx: &mut App,
27669 ) -> Option<Task<Result<ProjectTransaction>>> {
27670 self.update(cx, |project, cx| {
27671 project.perform_rename(buffer.clone(), position, new_name, cx)
27672 })
27673 .ok()
27674 }
27675}
27676
27677fn consume_contiguous_rows(
27678 contiguous_row_selections: &mut Vec<Selection<Point>>,
27679 selection: &Selection<Point>,
27680 display_map: &DisplaySnapshot,
27681 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
27682) -> (MultiBufferRow, MultiBufferRow) {
27683 contiguous_row_selections.push(selection.clone());
27684 let start_row = starting_row(selection, display_map);
27685 let mut end_row = ending_row(selection, display_map);
27686
27687 while let Some(next_selection) = selections.peek() {
27688 if next_selection.start.row <= end_row.0 {
27689 end_row = ending_row(next_selection, display_map);
27690 contiguous_row_selections.push(selections.next().unwrap().clone());
27691 } else {
27692 break;
27693 }
27694 }
27695 (start_row, end_row)
27696}
27697
27698fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27699 if selection.start.column > 0 {
27700 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
27701 } else {
27702 MultiBufferRow(selection.start.row)
27703 }
27704}
27705
27706fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27707 if next_selection.end.column > 0 || next_selection.is_empty() {
27708 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
27709 } else {
27710 MultiBufferRow(next_selection.end.row)
27711 }
27712}
27713
27714impl EditorSnapshot {
27715 pub fn remote_selections_in_range<'a>(
27716 &'a self,
27717 range: &'a Range<Anchor>,
27718 collaboration_hub: &dyn CollaborationHub,
27719 cx: &'a App,
27720 ) -> impl 'a + Iterator<Item = RemoteSelection> {
27721 let participant_names = collaboration_hub.user_names(cx);
27722 let participant_indices = collaboration_hub.user_participant_indices(cx);
27723 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
27724 let collaborators_by_replica_id = collaborators_by_peer_id
27725 .values()
27726 .map(|collaborator| (collaborator.replica_id, collaborator))
27727 .collect::<HashMap<_, _>>();
27728 self.buffer_snapshot()
27729 .selections_in_range(range, false)
27730 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
27731 if replica_id == ReplicaId::AGENT {
27732 Some(RemoteSelection {
27733 replica_id,
27734 selection,
27735 cursor_shape,
27736 line_mode,
27737 collaborator_id: CollaboratorId::Agent,
27738 user_name: Some("Agent".into()),
27739 color: cx.theme().players().agent(),
27740 })
27741 } else {
27742 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
27743 let participant_index = participant_indices.get(&collaborator.user_id).copied();
27744 let user_name = participant_names.get(&collaborator.user_id).cloned();
27745 Some(RemoteSelection {
27746 replica_id,
27747 selection,
27748 cursor_shape,
27749 line_mode,
27750 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
27751 user_name,
27752 color: if let Some(index) = participant_index {
27753 cx.theme().players().color_for_participant(index.0)
27754 } else {
27755 cx.theme().players().absent()
27756 },
27757 })
27758 }
27759 })
27760 }
27761
27762 pub fn hunks_for_ranges(
27763 &self,
27764 ranges: impl IntoIterator<Item = Range<Point>>,
27765 ) -> Vec<MultiBufferDiffHunk> {
27766 let mut hunks = Vec::new();
27767 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
27768 HashMap::default();
27769 for query_range in ranges {
27770 let query_rows =
27771 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
27772 for hunk in self.buffer_snapshot().diff_hunks_in_range(
27773 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
27774 ) {
27775 // Include deleted hunks that are adjacent to the query range, because
27776 // otherwise they would be missed.
27777 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
27778 if hunk.status().is_deleted() {
27779 intersects_range |= hunk.row_range.start == query_rows.end;
27780 intersects_range |= hunk.row_range.end == query_rows.start;
27781 }
27782 if intersects_range {
27783 if !processed_buffer_rows
27784 .entry(hunk.buffer_id)
27785 .or_default()
27786 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
27787 {
27788 continue;
27789 }
27790 hunks.push(hunk);
27791 }
27792 }
27793 }
27794
27795 hunks
27796 }
27797
27798 fn display_diff_hunks_for_rows<'a>(
27799 &'a self,
27800 display_rows: Range<DisplayRow>,
27801 folded_buffers: &'a HashSet<BufferId>,
27802 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
27803 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
27804 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
27805
27806 self.buffer_snapshot()
27807 .diff_hunks_in_range(buffer_start..buffer_end)
27808 .filter_map(|hunk| {
27809 if folded_buffers.contains(&hunk.buffer_id)
27810 || (hunk.row_range.is_empty() && self.buffer.all_diff_hunks_expanded())
27811 {
27812 return None;
27813 }
27814
27815 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
27816 let hunk_end_point = if hunk.row_range.end > hunk.row_range.start {
27817 let last_row = MultiBufferRow(hunk.row_range.end.0 - 1);
27818 let line_len = self.buffer_snapshot().line_len(last_row);
27819 Point::new(last_row.0, line_len)
27820 } else {
27821 Point::new(hunk.row_range.end.0, 0)
27822 };
27823
27824 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
27825 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
27826
27827 let display_hunk = if hunk_display_start.column() != 0 {
27828 DisplayDiffHunk::Folded {
27829 display_row: hunk_display_start.row(),
27830 }
27831 } else {
27832 let mut end_row = hunk_display_end.row();
27833 if hunk.row_range.end > hunk.row_range.start || hunk_display_end.column() > 0 {
27834 end_row.0 += 1;
27835 }
27836 let is_created_file = hunk.is_created_file();
27837
27838 DisplayDiffHunk::Unfolded {
27839 status: hunk.status(),
27840 diff_base_byte_range: hunk.diff_base_byte_range.start.0
27841 ..hunk.diff_base_byte_range.end.0,
27842 word_diffs: hunk.word_diffs,
27843 display_row_range: hunk_display_start.row()..end_row,
27844 multi_buffer_range: Anchor::range_in_buffer(
27845 hunk.excerpt_id,
27846 hunk.buffer_range,
27847 ),
27848 is_created_file,
27849 }
27850 };
27851
27852 Some(display_hunk)
27853 })
27854 }
27855
27856 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
27857 self.display_snapshot
27858 .buffer_snapshot()
27859 .language_at(position)
27860 }
27861
27862 pub fn is_focused(&self) -> bool {
27863 self.is_focused
27864 }
27865
27866 pub fn placeholder_text(&self) -> Option<String> {
27867 self.placeholder_display_snapshot
27868 .as_ref()
27869 .map(|display_map| display_map.text())
27870 }
27871
27872 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
27873 self.scroll_anchor.scroll_position(&self.display_snapshot)
27874 }
27875
27876 pub fn gutter_dimensions(
27877 &self,
27878 font_id: FontId,
27879 font_size: Pixels,
27880 style: &EditorStyle,
27881 window: &mut Window,
27882 cx: &App,
27883 ) -> GutterDimensions {
27884 if self.show_gutter
27885 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
27886 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
27887 {
27888 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
27889 matches!(
27890 ProjectSettings::get_global(cx).git.git_gutter,
27891 GitGutterSetting::TrackedFiles
27892 )
27893 });
27894 let gutter_settings = EditorSettings::get_global(cx).gutter;
27895 let show_line_numbers = self
27896 .show_line_numbers
27897 .unwrap_or(gutter_settings.line_numbers);
27898 let line_gutter_width = if show_line_numbers {
27899 // Avoid flicker-like gutter resizes when the line number gains another digit by
27900 // only resizing the gutter on files with > 10**min_line_number_digits lines.
27901 let min_width_for_number_on_gutter =
27902 ch_advance * gutter_settings.min_line_number_digits as f32;
27903 self.max_line_number_width(style, window)
27904 .max(min_width_for_number_on_gutter)
27905 } else {
27906 0.0.into()
27907 };
27908
27909 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
27910 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
27911
27912 let git_blame_entries_width =
27913 self.git_blame_gutter_max_author_length
27914 .map(|max_author_length| {
27915 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
27916 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
27917
27918 /// The number of characters to dedicate to gaps and margins.
27919 const SPACING_WIDTH: usize = 4;
27920
27921 let max_char_count = max_author_length.min(renderer.max_author_length())
27922 + ::git::SHORT_SHA_LENGTH
27923 + MAX_RELATIVE_TIMESTAMP.len()
27924 + SPACING_WIDTH;
27925
27926 ch_advance * max_char_count
27927 });
27928
27929 let is_singleton = self.buffer_snapshot().is_singleton();
27930
27931 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
27932 left_padding += if !is_singleton {
27933 ch_width * 4.0
27934 } else if show_runnables || show_breakpoints {
27935 ch_width * 3.0
27936 } else if show_git_gutter && show_line_numbers {
27937 ch_width * 2.0
27938 } else if show_git_gutter || show_line_numbers {
27939 ch_width
27940 } else {
27941 px(0.)
27942 };
27943
27944 let shows_folds = is_singleton && gutter_settings.folds;
27945
27946 let right_padding = if shows_folds && show_line_numbers {
27947 ch_width * 4.0
27948 } else if shows_folds || (!is_singleton && show_line_numbers) {
27949 ch_width * 3.0
27950 } else if show_line_numbers {
27951 ch_width
27952 } else {
27953 px(0.)
27954 };
27955
27956 GutterDimensions {
27957 left_padding,
27958 right_padding,
27959 width: line_gutter_width + left_padding + right_padding,
27960 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
27961 git_blame_entries_width,
27962 }
27963 } else if self.offset_content {
27964 GutterDimensions::default_with_margin(font_id, font_size, cx)
27965 } else {
27966 GutterDimensions::default()
27967 }
27968 }
27969
27970 pub fn render_crease_toggle(
27971 &self,
27972 buffer_row: MultiBufferRow,
27973 row_contains_cursor: bool,
27974 editor: Entity<Editor>,
27975 window: &mut Window,
27976 cx: &mut App,
27977 ) -> Option<AnyElement> {
27978 let folded = self.is_line_folded(buffer_row);
27979 let mut is_foldable = false;
27980
27981 if let Some(crease) = self
27982 .crease_snapshot
27983 .query_row(buffer_row, self.buffer_snapshot())
27984 {
27985 is_foldable = true;
27986 match crease {
27987 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
27988 if let Some(render_toggle) = render_toggle {
27989 let toggle_callback =
27990 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
27991 if folded {
27992 editor.update(cx, |editor, cx| {
27993 editor.fold_at(buffer_row, window, cx)
27994 });
27995 } else {
27996 editor.update(cx, |editor, cx| {
27997 editor.unfold_at(buffer_row, window, cx)
27998 });
27999 }
28000 });
28001 return Some((render_toggle)(
28002 buffer_row,
28003 folded,
28004 toggle_callback,
28005 window,
28006 cx,
28007 ));
28008 }
28009 }
28010 }
28011 }
28012
28013 is_foldable |= !self.use_lsp_folding_ranges && self.starts_indent(buffer_row);
28014
28015 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
28016 Some(
28017 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
28018 .toggle_state(folded)
28019 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
28020 if folded {
28021 this.unfold_at(buffer_row, window, cx);
28022 } else {
28023 this.fold_at(buffer_row, window, cx);
28024 }
28025 }))
28026 .into_any_element(),
28027 )
28028 } else {
28029 None
28030 }
28031 }
28032
28033 pub fn render_crease_trailer(
28034 &self,
28035 buffer_row: MultiBufferRow,
28036 window: &mut Window,
28037 cx: &mut App,
28038 ) -> Option<AnyElement> {
28039 let folded = self.is_line_folded(buffer_row);
28040 if let Crease::Inline { render_trailer, .. } = self
28041 .crease_snapshot
28042 .query_row(buffer_row, self.buffer_snapshot())?
28043 {
28044 let render_trailer = render_trailer.as_ref()?;
28045 Some(render_trailer(buffer_row, folded, window, cx))
28046 } else {
28047 None
28048 }
28049 }
28050
28051 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
28052 let digit_count = self.widest_line_number().ilog10() + 1;
28053 column_pixels(style, digit_count as usize, window)
28054 }
28055
28056 /// Returns the line delta from `base` to `line` in the multibuffer, ignoring wrapped lines.
28057 ///
28058 /// This is positive if `base` is before `line`.
28059 fn relative_line_delta(
28060 &self,
28061 current_selection_head: DisplayRow,
28062 first_visible_row: DisplayRow,
28063 consider_wrapped_lines: bool,
28064 ) -> i64 {
28065 let current_selection_head = current_selection_head.as_display_point().to_point(self);
28066 let first_visible_row = first_visible_row.as_display_point().to_point(self);
28067
28068 if consider_wrapped_lines {
28069 let wrap_snapshot = self.wrap_snapshot();
28070 let base_wrap_row = wrap_snapshot
28071 .make_wrap_point(current_selection_head, Bias::Left)
28072 .row();
28073 let wrap_row = wrap_snapshot
28074 .make_wrap_point(first_visible_row, Bias::Left)
28075 .row();
28076
28077 wrap_row.0 as i64 - base_wrap_row.0 as i64
28078 } else {
28079 let fold_snapshot = self.fold_snapshot();
28080 let base_fold_row = fold_snapshot
28081 .to_fold_point(self.to_inlay_point(current_selection_head), Bias::Left)
28082 .row();
28083 let fold_row = fold_snapshot
28084 .to_fold_point(self.to_inlay_point(first_visible_row), Bias::Left)
28085 .row();
28086
28087 fold_row as i64 - base_fold_row as i64
28088 }
28089 }
28090
28091 /// Returns the unsigned relative line number to display for each row in `rows`.
28092 ///
28093 /// Wrapped rows are excluded from the hashmap if `count_relative_lines` is `false`.
28094 pub fn calculate_relative_line_numbers(
28095 &self,
28096 rows: &Range<DisplayRow>,
28097 current_selection_head: DisplayRow,
28098 count_wrapped_lines: bool,
28099 ) -> HashMap<DisplayRow, u32> {
28100 let initial_offset =
28101 self.relative_line_delta(current_selection_head, rows.start, count_wrapped_lines);
28102
28103 self.row_infos(rows.start)
28104 .take(rows.len())
28105 .enumerate()
28106 .map(|(i, row_info)| (DisplayRow(rows.start.0 + i as u32), row_info))
28107 .filter(|(_row, row_info)| {
28108 row_info.buffer_row.is_some()
28109 || (count_wrapped_lines && row_info.wrapped_buffer_row.is_some())
28110 })
28111 .enumerate()
28112 .filter_map(|(i, (row, row_info))| {
28113 // We want to ensure here that the current line has absolute
28114 // numbering, even if we are in a soft-wrapped line. With the
28115 // exception that if we are in a deleted line, we should number this
28116 // relative with 0, as otherwise it would have no line number at all
28117 let relative_line_number = (initial_offset + i as i64).unsigned_abs() as u32;
28118
28119 (relative_line_number != 0
28120 || row_info
28121 .diff_status
28122 .is_some_and(|status| status.is_deleted()))
28123 .then_some((row, relative_line_number))
28124 })
28125 .collect()
28126 }
28127}
28128
28129pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
28130 let font_size = style.text.font_size.to_pixels(window.rem_size());
28131 let layout = window.text_system().shape_line(
28132 SharedString::from(" ".repeat(column)),
28133 font_size,
28134 &[TextRun {
28135 len: column,
28136 font: style.text.font(),
28137 color: Hsla::default(),
28138 ..Default::default()
28139 }],
28140 None,
28141 );
28142
28143 layout.width
28144}
28145
28146impl Deref for EditorSnapshot {
28147 type Target = DisplaySnapshot;
28148
28149 fn deref(&self) -> &Self::Target {
28150 &self.display_snapshot
28151 }
28152}
28153
28154#[derive(Clone, Debug, PartialEq, Eq)]
28155pub enum EditorEvent {
28156 /// Emitted when the stored review comments change (added, removed, or updated).
28157 ReviewCommentsChanged {
28158 /// The new total count of review comments.
28159 total_count: usize,
28160 },
28161 InputIgnored {
28162 text: Arc<str>,
28163 },
28164 InputHandled {
28165 utf16_range_to_replace: Option<Range<isize>>,
28166 text: Arc<str>,
28167 },
28168 ExcerptsAdded {
28169 buffer: Entity<Buffer>,
28170 predecessor: ExcerptId,
28171 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
28172 },
28173 ExcerptsRemoved {
28174 ids: Vec<ExcerptId>,
28175 removed_buffer_ids: Vec<BufferId>,
28176 },
28177 BufferFoldToggled {
28178 ids: Vec<ExcerptId>,
28179 folded: bool,
28180 },
28181 ExcerptsEdited {
28182 ids: Vec<ExcerptId>,
28183 },
28184 ExcerptsExpanded {
28185 ids: Vec<ExcerptId>,
28186 },
28187 ExpandExcerptsRequested {
28188 excerpt_ids: Vec<ExcerptId>,
28189 lines: u32,
28190 direction: ExpandExcerptDirection,
28191 },
28192 StageOrUnstageRequested {
28193 stage: bool,
28194 hunks: Vec<MultiBufferDiffHunk>,
28195 },
28196 OpenExcerptsRequested {
28197 selections_by_buffer: HashMap<BufferId, (Vec<Range<BufferOffset>>, Option<u32>)>,
28198 split: bool,
28199 },
28200 RestoreRequested {
28201 hunks: Vec<MultiBufferDiffHunk>,
28202 },
28203 BufferEdited,
28204 Edited {
28205 transaction_id: clock::Lamport,
28206 },
28207 Reparsed(BufferId),
28208 Focused,
28209 FocusedIn,
28210 Blurred,
28211 DirtyChanged,
28212 Saved,
28213 TitleChanged,
28214 SelectionsChanged {
28215 local: bool,
28216 },
28217 ScrollPositionChanged {
28218 local: bool,
28219 autoscroll: bool,
28220 },
28221 TransactionUndone {
28222 transaction_id: clock::Lamport,
28223 },
28224 TransactionBegun {
28225 transaction_id: clock::Lamport,
28226 },
28227 CursorShapeChanged,
28228 BreadcrumbsChanged,
28229 OutlineSymbolsChanged,
28230 PushedToNavHistory {
28231 anchor: Anchor,
28232 is_deactivate: bool,
28233 },
28234}
28235
28236impl EventEmitter<EditorEvent> for Editor {}
28237
28238impl Focusable for Editor {
28239 fn focus_handle(&self, _cx: &App) -> FocusHandle {
28240 self.focus_handle.clone()
28241 }
28242}
28243
28244impl Render for Editor {
28245 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28246 EditorElement::new(&cx.entity(), self.create_style(cx))
28247 }
28248}
28249
28250impl EntityInputHandler for Editor {
28251 fn text_for_range(
28252 &mut self,
28253 range_utf16: Range<usize>,
28254 adjusted_range: &mut Option<Range<usize>>,
28255 _: &mut Window,
28256 cx: &mut Context<Self>,
28257 ) -> Option<String> {
28258 let snapshot = self.buffer.read(cx).read(cx);
28259 let start = snapshot.clip_offset_utf16(
28260 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
28261 Bias::Left,
28262 );
28263 let end = snapshot.clip_offset_utf16(
28264 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
28265 Bias::Right,
28266 );
28267 if (start.0.0..end.0.0) != range_utf16 {
28268 adjusted_range.replace(start.0.0..end.0.0);
28269 }
28270 Some(snapshot.text_for_range(start..end).collect())
28271 }
28272
28273 fn selected_text_range(
28274 &mut self,
28275 ignore_disabled_input: bool,
28276 _: &mut Window,
28277 cx: &mut Context<Self>,
28278 ) -> Option<UTF16Selection> {
28279 // Prevent the IME menu from appearing when holding down an alphabetic key
28280 // while input is disabled.
28281 if !ignore_disabled_input && !self.input_enabled {
28282 return None;
28283 }
28284
28285 let selection = self
28286 .selections
28287 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
28288 let range = selection.range();
28289
28290 Some(UTF16Selection {
28291 range: range.start.0.0..range.end.0.0,
28292 reversed: selection.reversed,
28293 })
28294 }
28295
28296 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
28297 let snapshot = self.buffer.read(cx).read(cx);
28298 let range = self
28299 .text_highlights(HighlightKey::InputComposition, cx)?
28300 .1
28301 .first()?;
28302 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
28303 }
28304
28305 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
28306 self.clear_highlights(HighlightKey::InputComposition, cx);
28307 self.ime_transaction.take();
28308 }
28309
28310 fn replace_text_in_range(
28311 &mut self,
28312 range_utf16: Option<Range<usize>>,
28313 text: &str,
28314 window: &mut Window,
28315 cx: &mut Context<Self>,
28316 ) {
28317 if !self.input_enabled {
28318 cx.emit(EditorEvent::InputIgnored { text: text.into() });
28319 return;
28320 }
28321
28322 self.transact(window, cx, |this, window, cx| {
28323 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
28324 if let Some(marked_ranges) = this.marked_text_ranges(cx) {
28325 // During IME composition, macOS reports the replacement range
28326 // relative to the first marked region (the only one visible via
28327 // marked_text_range). The correct targets for replacement are the
28328 // marked ranges themselves — one per cursor — so use them directly.
28329 Some(marked_ranges)
28330 } else if range_utf16.start == range_utf16.end {
28331 // An empty replacement range means "insert at cursor" with no text
28332 // to replace. macOS reports the cursor position from its own
28333 // (single-cursor) view of the buffer, which diverges from our actual
28334 // cursor positions after multi-cursor edits have shifted offsets.
28335 // Treating this as range_utf16=None lets each cursor insert in place.
28336 None
28337 } else {
28338 // Outside of IME composition (e.g. Accessibility Keyboard word
28339 // completion), the range is an absolute document offset for the
28340 // newest cursor. Fan it out to all cursors via
28341 // selection_replacement_ranges, which applies the delta relative
28342 // to the newest selection to every cursor.
28343 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
28344 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
28345 Some(this.selection_replacement_ranges(range_utf16, cx))
28346 }
28347 } else {
28348 this.marked_text_ranges(cx)
28349 };
28350
28351 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
28352 let newest_selection_id = this.selections.newest_anchor().id;
28353 this.selections
28354 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
28355 .iter()
28356 .zip(ranges_to_replace.iter())
28357 .find_map(|(selection, range)| {
28358 if selection.id == newest_selection_id {
28359 Some(
28360 (range.start.0.0 as isize - selection.head().0.0 as isize)
28361 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
28362 )
28363 } else {
28364 None
28365 }
28366 })
28367 });
28368
28369 cx.emit(EditorEvent::InputHandled {
28370 utf16_range_to_replace: range_to_replace,
28371 text: text.into(),
28372 });
28373
28374 if let Some(new_selected_ranges) = new_selected_ranges {
28375 // Only backspace if at least one range covers actual text. When all
28376 // ranges are empty (e.g. a trailing-space insertion from Accessibility
28377 // Keyboard sends replacementRange=cursor..cursor), backspace would
28378 // incorrectly delete the character just before the cursor.
28379 let should_backspace = new_selected_ranges.iter().any(|r| r.start != r.end);
28380 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
28381 selections.select_ranges(new_selected_ranges)
28382 });
28383 if should_backspace {
28384 this.backspace(&Default::default(), window, cx);
28385 }
28386 }
28387
28388 this.handle_input(text, window, cx);
28389 });
28390
28391 if let Some(transaction) = self.ime_transaction {
28392 self.buffer.update(cx, |buffer, cx| {
28393 buffer.group_until_transaction(transaction, cx);
28394 });
28395 }
28396
28397 self.unmark_text(window, cx);
28398 }
28399
28400 fn replace_and_mark_text_in_range(
28401 &mut self,
28402 range_utf16: Option<Range<usize>>,
28403 text: &str,
28404 new_selected_range_utf16: Option<Range<usize>>,
28405 window: &mut Window,
28406 cx: &mut Context<Self>,
28407 ) {
28408 if !self.input_enabled {
28409 return;
28410 }
28411
28412 let transaction = self.transact(window, cx, |this, window, cx| {
28413 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
28414 let snapshot = this.buffer.read(cx).read(cx);
28415 if let Some(relative_range_utf16) = range_utf16.as_ref() {
28416 for marked_range in &mut marked_ranges {
28417 marked_range.end = marked_range.start + relative_range_utf16.end;
28418 marked_range.start += relative_range_utf16.start;
28419 marked_range.start =
28420 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
28421 marked_range.end =
28422 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
28423 }
28424 }
28425 Some(marked_ranges)
28426 } else if let Some(range_utf16) = range_utf16 {
28427 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
28428 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
28429 Some(this.selection_replacement_ranges(range_utf16, cx))
28430 } else {
28431 None
28432 };
28433
28434 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
28435 let newest_selection_id = this.selections.newest_anchor().id;
28436 this.selections
28437 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
28438 .iter()
28439 .zip(ranges_to_replace.iter())
28440 .find_map(|(selection, range)| {
28441 if selection.id == newest_selection_id {
28442 Some(
28443 (range.start.0.0 as isize - selection.head().0.0 as isize)
28444 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
28445 )
28446 } else {
28447 None
28448 }
28449 })
28450 });
28451
28452 cx.emit(EditorEvent::InputHandled {
28453 utf16_range_to_replace: range_to_replace,
28454 text: text.into(),
28455 });
28456
28457 if let Some(ranges) = ranges_to_replace {
28458 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28459 s.select_ranges(ranges)
28460 });
28461 }
28462
28463 let marked_ranges = {
28464 let snapshot = this.buffer.read(cx).read(cx);
28465 this.selections
28466 .disjoint_anchors_arc()
28467 .iter()
28468 .map(|selection| {
28469 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
28470 })
28471 .collect::<Vec<_>>()
28472 };
28473
28474 if text.is_empty() {
28475 this.unmark_text(window, cx);
28476 } else {
28477 this.highlight_text(
28478 HighlightKey::InputComposition,
28479 marked_ranges.clone(),
28480 HighlightStyle {
28481 underline: Some(UnderlineStyle {
28482 thickness: px(1.),
28483 color: None,
28484 wavy: false,
28485 }),
28486 ..Default::default()
28487 },
28488 cx,
28489 );
28490 }
28491
28492 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
28493 let use_autoclose = this.use_autoclose;
28494 let use_auto_surround = this.use_auto_surround;
28495 this.set_use_autoclose(false);
28496 this.set_use_auto_surround(false);
28497 this.handle_input(text, window, cx);
28498 this.set_use_autoclose(use_autoclose);
28499 this.set_use_auto_surround(use_auto_surround);
28500
28501 if let Some(new_selected_range) = new_selected_range_utf16 {
28502 let snapshot = this.buffer.read(cx).read(cx);
28503 let new_selected_ranges = marked_ranges
28504 .into_iter()
28505 .map(|marked_range| {
28506 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
28507 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
28508 insertion_start.0 + new_selected_range.start,
28509 ));
28510 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
28511 insertion_start.0 + new_selected_range.end,
28512 ));
28513 snapshot.clip_offset_utf16(new_start, Bias::Left)
28514 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
28515 })
28516 .collect::<Vec<_>>();
28517
28518 drop(snapshot);
28519 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
28520 selections.select_ranges(new_selected_ranges)
28521 });
28522 }
28523 });
28524
28525 self.ime_transaction = self.ime_transaction.or(transaction);
28526 if let Some(transaction) = self.ime_transaction {
28527 self.buffer.update(cx, |buffer, cx| {
28528 buffer.group_until_transaction(transaction, cx);
28529 });
28530 }
28531
28532 if self
28533 .text_highlights(HighlightKey::InputComposition, cx)
28534 .is_none()
28535 {
28536 self.ime_transaction.take();
28537 }
28538 }
28539
28540 fn bounds_for_range(
28541 &mut self,
28542 range_utf16: Range<usize>,
28543 element_bounds: gpui::Bounds<Pixels>,
28544 window: &mut Window,
28545 cx: &mut Context<Self>,
28546 ) -> Option<gpui::Bounds<Pixels>> {
28547 let text_layout_details = self.text_layout_details(window, cx);
28548 let CharacterDimensions {
28549 em_width,
28550 em_advance,
28551 line_height,
28552 } = self.character_dimensions(window, cx);
28553
28554 let snapshot = self.snapshot(window, cx);
28555 let scroll_position = snapshot.scroll_position();
28556 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
28557
28558 let start =
28559 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
28560 let x = Pixels::from(
28561 ScrollOffset::from(
28562 snapshot.x_for_display_point(start, &text_layout_details)
28563 + self.gutter_dimensions.full_width(),
28564 ) - scroll_left,
28565 );
28566 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
28567
28568 Some(Bounds {
28569 origin: element_bounds.origin + point(x, y),
28570 size: size(em_width, line_height),
28571 })
28572 }
28573
28574 fn character_index_for_point(
28575 &mut self,
28576 point: gpui::Point<Pixels>,
28577 _window: &mut Window,
28578 _cx: &mut Context<Self>,
28579 ) -> Option<usize> {
28580 let position_map = self.last_position_map.as_ref()?;
28581 if !position_map.text_hitbox.contains(&point) {
28582 return None;
28583 }
28584 let display_point = position_map.point_for_position(point).previous_valid;
28585 let anchor = position_map
28586 .snapshot
28587 .display_point_to_anchor(display_point, Bias::Left);
28588 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
28589 Some(utf16_offset.0.0)
28590 }
28591
28592 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
28593 self.expects_character_input
28594 }
28595}
28596
28597trait SelectionExt {
28598 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
28599 fn spanned_rows(
28600 &self,
28601 include_end_if_at_line_start: bool,
28602 map: &DisplaySnapshot,
28603 ) -> Range<MultiBufferRow>;
28604}
28605
28606impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
28607 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
28608 let start = self
28609 .start
28610 .to_point(map.buffer_snapshot())
28611 .to_display_point(map);
28612 let end = self
28613 .end
28614 .to_point(map.buffer_snapshot())
28615 .to_display_point(map);
28616 if self.reversed {
28617 end..start
28618 } else {
28619 start..end
28620 }
28621 }
28622
28623 fn spanned_rows(
28624 &self,
28625 include_end_if_at_line_start: bool,
28626 map: &DisplaySnapshot,
28627 ) -> Range<MultiBufferRow> {
28628 let start = self.start.to_point(map.buffer_snapshot());
28629 let mut end = self.end.to_point(map.buffer_snapshot());
28630 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
28631 end.row -= 1;
28632 }
28633
28634 let buffer_start = map.prev_line_boundary(start).0;
28635 let buffer_end = map.next_line_boundary(end).0;
28636 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
28637 }
28638}
28639
28640impl<T: InvalidationRegion> InvalidationStack<T> {
28641 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
28642 where
28643 S: Clone + ToOffset,
28644 {
28645 while let Some(region) = self.last() {
28646 let all_selections_inside_invalidation_ranges =
28647 if selections.len() == region.ranges().len() {
28648 selections
28649 .iter()
28650 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
28651 .all(|(selection, invalidation_range)| {
28652 let head = selection.head().to_offset(buffer);
28653 invalidation_range.start <= head && invalidation_range.end >= head
28654 })
28655 } else {
28656 false
28657 };
28658
28659 if all_selections_inside_invalidation_ranges {
28660 break;
28661 } else {
28662 self.pop();
28663 }
28664 }
28665 }
28666}
28667
28668#[derive(Clone)]
28669struct ErasedEditorImpl(Entity<Editor>);
28670
28671impl ui_input::ErasedEditor for ErasedEditorImpl {
28672 fn text(&self, cx: &App) -> String {
28673 self.0.read(cx).text(cx)
28674 }
28675
28676 fn set_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28677 self.0.update(cx, |this, cx| {
28678 this.set_text(text, window, cx);
28679 })
28680 }
28681
28682 fn clear(&self, window: &mut Window, cx: &mut App) {
28683 self.0.update(cx, |this, cx| this.clear(window, cx));
28684 }
28685
28686 fn set_placeholder_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28687 self.0.update(cx, |this, cx| {
28688 this.set_placeholder_text(text, window, cx);
28689 });
28690 }
28691
28692 fn focus_handle(&self, cx: &App) -> FocusHandle {
28693 self.0.read(cx).focus_handle(cx)
28694 }
28695
28696 fn render(&self, _: &mut Window, cx: &App) -> AnyElement {
28697 let settings = ThemeSettings::get_global(cx);
28698 let theme_color = cx.theme().colors();
28699
28700 let text_style = TextStyle {
28701 font_family: settings.ui_font.family.clone(),
28702 font_features: settings.ui_font.features.clone(),
28703 font_size: rems(0.875).into(),
28704 font_weight: settings.ui_font.weight,
28705 font_style: FontStyle::Normal,
28706 line_height: relative(1.2),
28707 color: theme_color.text,
28708 ..Default::default()
28709 };
28710 let editor_style = EditorStyle {
28711 background: theme_color.ghost_element_background,
28712 local_player: cx.theme().players().local(),
28713 syntax: cx.theme().syntax().clone(),
28714 text: text_style,
28715 ..Default::default()
28716 };
28717 EditorElement::new(&self.0, editor_style).into_any()
28718 }
28719
28720 fn as_any(&self) -> &dyn Any {
28721 &self.0
28722 }
28723
28724 fn move_selection_to_end(&self, window: &mut Window, cx: &mut App) {
28725 self.0.update(cx, |editor, cx| {
28726 let editor_offset = editor.buffer().read(cx).len(cx);
28727 editor.change_selections(
28728 SelectionEffects::scroll(Autoscroll::Next),
28729 window,
28730 cx,
28731 |s| s.select_ranges(Some(editor_offset..editor_offset)),
28732 );
28733 });
28734 }
28735
28736 fn subscribe(
28737 &self,
28738 mut callback: Box<dyn FnMut(ui_input::ErasedEditorEvent, &mut Window, &mut App) + 'static>,
28739 window: &mut Window,
28740 cx: &mut App,
28741 ) -> Subscription {
28742 window.subscribe(&self.0, cx, move |_, event: &EditorEvent, window, cx| {
28743 let event = match event {
28744 EditorEvent::BufferEdited => ui_input::ErasedEditorEvent::BufferEdited,
28745 EditorEvent::Blurred => ui_input::ErasedEditorEvent::Blurred,
28746 _ => return,
28747 };
28748 (callback)(event, window, cx);
28749 })
28750 }
28751
28752 fn set_masked(&self, masked: bool, _window: &mut Window, cx: &mut App) {
28753 self.0.update(cx, |editor, cx| {
28754 editor.set_masked(masked, cx);
28755 });
28756 }
28757}
28758impl<T> Default for InvalidationStack<T> {
28759 fn default() -> Self {
28760 Self(Default::default())
28761 }
28762}
28763
28764impl<T> Deref for InvalidationStack<T> {
28765 type Target = Vec<T>;
28766
28767 fn deref(&self) -> &Self::Target {
28768 &self.0
28769 }
28770}
28771
28772impl<T> DerefMut for InvalidationStack<T> {
28773 fn deref_mut(&mut self) -> &mut Self::Target {
28774 &mut self.0
28775 }
28776}
28777
28778impl InvalidationRegion for SnippetState {
28779 fn ranges(&self) -> &[Range<Anchor>] {
28780 &self.ranges[self.active_index]
28781 }
28782}
28783
28784fn edit_prediction_edit_text(
28785 current_snapshot: &BufferSnapshot,
28786 edits: &[(Range<Anchor>, impl AsRef<str>)],
28787 edit_preview: &EditPreview,
28788 include_deletions: bool,
28789 cx: &App,
28790) -> HighlightedText {
28791 let edits = edits
28792 .iter()
28793 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
28794 .collect::<Vec<_>>();
28795
28796 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
28797}
28798
28799fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
28800 // Fallback for providers that don't provide edit_preview (like Copilot)
28801 // Just show the raw edit text with basic styling
28802 let mut text = String::new();
28803 let mut highlights = Vec::new();
28804
28805 let insertion_highlight_style = HighlightStyle {
28806 color: Some(cx.theme().colors().text),
28807 ..Default::default()
28808 };
28809
28810 for (_, edit_text) in edits {
28811 let start_offset = text.len();
28812 text.push_str(edit_text);
28813 let end_offset = text.len();
28814
28815 if start_offset < end_offset {
28816 highlights.push((start_offset..end_offset, insertion_highlight_style));
28817 }
28818 }
28819
28820 HighlightedText {
28821 text: text.into(),
28822 highlights,
28823 }
28824}
28825
28826pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
28827 match severity {
28828 lsp::DiagnosticSeverity::ERROR => colors.error,
28829 lsp::DiagnosticSeverity::WARNING => colors.warning,
28830 lsp::DiagnosticSeverity::INFORMATION => colors.info,
28831 lsp::DiagnosticSeverity::HINT => colors.info,
28832 _ => colors.ignored,
28833 }
28834}
28835
28836pub fn styled_runs_for_code_label<'a>(
28837 label: &'a CodeLabel,
28838 syntax_theme: &'a theme::SyntaxTheme,
28839 local_player: &'a theme::PlayerColor,
28840) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
28841 let fade_out = HighlightStyle {
28842 fade_out: Some(0.35),
28843 ..Default::default()
28844 };
28845
28846 let mut prev_end = label.filter_range.end;
28847 label
28848 .runs
28849 .iter()
28850 .enumerate()
28851 .flat_map(move |(ix, (range, highlight_id))| {
28852 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
28853 HighlightStyle {
28854 color: Some(local_player.cursor),
28855 ..Default::default()
28856 }
28857 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
28858 HighlightStyle {
28859 background_color: Some(local_player.selection),
28860 ..Default::default()
28861 }
28862 } else if let Some(style) = highlight_id.style(syntax_theme) {
28863 style
28864 } else {
28865 return Default::default();
28866 };
28867 let muted_style = style.highlight(fade_out);
28868
28869 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
28870 if range.start >= label.filter_range.end {
28871 if range.start > prev_end {
28872 runs.push((prev_end..range.start, fade_out));
28873 }
28874 runs.push((range.clone(), muted_style));
28875 } else if range.end <= label.filter_range.end {
28876 runs.push((range.clone(), style));
28877 } else {
28878 runs.push((range.start..label.filter_range.end, style));
28879 runs.push((label.filter_range.end..range.end, muted_style));
28880 }
28881 prev_end = cmp::max(prev_end, range.end);
28882
28883 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
28884 runs.push((prev_end..label.text.len(), fade_out));
28885 }
28886
28887 runs
28888 })
28889}
28890
28891pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
28892 let mut prev_index = 0;
28893 let mut prev_codepoint: Option<char> = None;
28894 text.char_indices()
28895 .chain([(text.len(), '\0')])
28896 .filter_map(move |(index, codepoint)| {
28897 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28898 let is_boundary = index == text.len()
28899 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
28900 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
28901 if is_boundary {
28902 let chunk = &text[prev_index..index];
28903 prev_index = index;
28904 Some(chunk)
28905 } else {
28906 None
28907 }
28908 })
28909}
28910
28911/// Given a string of text immediately before the cursor, iterates over possible
28912/// strings a snippet could match to. More precisely: returns an iterator over
28913/// suffixes of `text` created by splitting at word boundaries (before & after
28914/// every non-word character).
28915///
28916/// Shorter suffixes are returned first.
28917pub(crate) fn snippet_candidate_suffixes<'a>(
28918 text: &'a str,
28919 is_word_char: &'a dyn Fn(char) -> bool,
28920) -> impl std::iter::Iterator<Item = &'a str> + 'a {
28921 let mut prev_index = text.len();
28922 let mut prev_codepoint = None;
28923 text.char_indices()
28924 .rev()
28925 .chain([(0, '\0')])
28926 .filter_map(move |(index, codepoint)| {
28927 let prev_index = std::mem::replace(&mut prev_index, index);
28928 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28929 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
28930 None
28931 } else {
28932 let chunk = &text[prev_index..]; // go to end of string
28933 Some(chunk)
28934 }
28935 })
28936}
28937
28938pub trait RangeToAnchorExt: Sized {
28939 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
28940
28941 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
28942 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
28943 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
28944 }
28945}
28946
28947impl<T: ToOffset> RangeToAnchorExt for Range<T> {
28948 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
28949 let start_offset = self.start.to_offset(snapshot);
28950 let end_offset = self.end.to_offset(snapshot);
28951 if start_offset == end_offset {
28952 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
28953 } else {
28954 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
28955 }
28956 }
28957}
28958
28959pub trait RowExt {
28960 fn as_f64(&self) -> f64;
28961
28962 fn next_row(&self) -> Self;
28963
28964 fn previous_row(&self) -> Self;
28965
28966 fn minus(&self, other: Self) -> u32;
28967}
28968
28969impl RowExt for DisplayRow {
28970 fn as_f64(&self) -> f64 {
28971 self.0 as _
28972 }
28973
28974 fn next_row(&self) -> Self {
28975 Self(self.0 + 1)
28976 }
28977
28978 fn previous_row(&self) -> Self {
28979 Self(self.0.saturating_sub(1))
28980 }
28981
28982 fn minus(&self, other: Self) -> u32 {
28983 self.0 - other.0
28984 }
28985}
28986
28987impl RowExt for MultiBufferRow {
28988 fn as_f64(&self) -> f64 {
28989 self.0 as _
28990 }
28991
28992 fn next_row(&self) -> Self {
28993 Self(self.0 + 1)
28994 }
28995
28996 fn previous_row(&self) -> Self {
28997 Self(self.0.saturating_sub(1))
28998 }
28999
29000 fn minus(&self, other: Self) -> u32 {
29001 self.0 - other.0
29002 }
29003}
29004
29005trait RowRangeExt {
29006 type Row;
29007
29008 fn len(&self) -> usize;
29009
29010 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
29011}
29012
29013impl RowRangeExt for Range<MultiBufferRow> {
29014 type Row = MultiBufferRow;
29015
29016 fn len(&self) -> usize {
29017 (self.end.0 - self.start.0) as usize
29018 }
29019
29020 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
29021 (self.start.0..self.end.0).map(MultiBufferRow)
29022 }
29023}
29024
29025impl RowRangeExt for Range<DisplayRow> {
29026 type Row = DisplayRow;
29027
29028 fn len(&self) -> usize {
29029 (self.end.0 - self.start.0) as usize
29030 }
29031
29032 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
29033 (self.start.0..self.end.0).map(DisplayRow)
29034 }
29035}
29036
29037/// If select range has more than one line, we
29038/// just point the cursor to range.start.
29039fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
29040 if range.start.row == range.end.row {
29041 range
29042 } else {
29043 range.start..range.start
29044 }
29045}
29046pub struct KillRing(ClipboardItem);
29047impl Global for KillRing {}
29048
29049const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
29050
29051enum BreakpointPromptEditAction {
29052 Log,
29053 Condition,
29054 HitCondition,
29055}
29056
29057struct BreakpointPromptEditor {
29058 pub(crate) prompt: Entity<Editor>,
29059 editor: WeakEntity<Editor>,
29060 breakpoint_anchor: Anchor,
29061 breakpoint: Breakpoint,
29062 edit_action: BreakpointPromptEditAction,
29063 block_ids: HashSet<CustomBlockId>,
29064 editor_margins: Arc<Mutex<EditorMargins>>,
29065 _subscriptions: Vec<Subscription>,
29066}
29067
29068impl BreakpointPromptEditor {
29069 const MAX_LINES: u8 = 4;
29070
29071 fn new(
29072 editor: WeakEntity<Editor>,
29073 breakpoint_anchor: Anchor,
29074 breakpoint: Breakpoint,
29075 edit_action: BreakpointPromptEditAction,
29076 window: &mut Window,
29077 cx: &mut Context<Self>,
29078 ) -> Self {
29079 let base_text = match edit_action {
29080 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
29081 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
29082 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
29083 }
29084 .map(|msg| msg.to_string())
29085 .unwrap_or_default();
29086
29087 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
29088 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
29089
29090 let prompt = cx.new(|cx| {
29091 let mut prompt = Editor::new(
29092 EditorMode::AutoHeight {
29093 min_lines: 1,
29094 max_lines: Some(Self::MAX_LINES as usize),
29095 },
29096 buffer,
29097 None,
29098 window,
29099 cx,
29100 );
29101 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
29102 prompt.set_show_cursor_when_unfocused(false, cx);
29103 prompt.set_placeholder_text(
29104 match edit_action {
29105 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
29106 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
29107 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
29108 },
29109 window,
29110 cx,
29111 );
29112
29113 prompt
29114 });
29115
29116 Self {
29117 prompt,
29118 editor,
29119 breakpoint_anchor,
29120 breakpoint,
29121 edit_action,
29122 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
29123 block_ids: Default::default(),
29124 _subscriptions: vec![],
29125 }
29126 }
29127
29128 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
29129 self.block_ids.extend(block_ids)
29130 }
29131
29132 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
29133 if let Some(editor) = self.editor.upgrade() {
29134 let message = self
29135 .prompt
29136 .read(cx)
29137 .buffer
29138 .read(cx)
29139 .as_singleton()
29140 .expect("A multi buffer in breakpoint prompt isn't possible")
29141 .read(cx)
29142 .as_rope()
29143 .to_string();
29144
29145 editor.update(cx, |editor, cx| {
29146 editor.edit_breakpoint_at_anchor(
29147 self.breakpoint_anchor,
29148 self.breakpoint.clone(),
29149 match self.edit_action {
29150 BreakpointPromptEditAction::Log => {
29151 BreakpointEditAction::EditLogMessage(message.into())
29152 }
29153 BreakpointPromptEditAction::Condition => {
29154 BreakpointEditAction::EditCondition(message.into())
29155 }
29156 BreakpointPromptEditAction::HitCondition => {
29157 BreakpointEditAction::EditHitCondition(message.into())
29158 }
29159 },
29160 cx,
29161 );
29162
29163 editor.remove_blocks(self.block_ids.clone(), None, cx);
29164 cx.focus_self(window);
29165 });
29166 }
29167 }
29168
29169 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
29170 self.editor
29171 .update(cx, |editor, cx| {
29172 editor.remove_blocks(self.block_ids.clone(), None, cx);
29173 window.focus(&editor.focus_handle, cx);
29174 })
29175 .log_err();
29176 }
29177
29178 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
29179 let settings = ThemeSettings::get_global(cx);
29180 let text_style = TextStyle {
29181 color: if self.prompt.read(cx).read_only(cx) {
29182 cx.theme().colors().text_disabled
29183 } else {
29184 cx.theme().colors().text
29185 },
29186 font_family: settings.buffer_font.family.clone(),
29187 font_fallbacks: settings.buffer_font.fallbacks.clone(),
29188 font_size: settings.buffer_font_size(cx).into(),
29189 font_weight: settings.buffer_font.weight,
29190 line_height: relative(settings.buffer_line_height.value()),
29191 ..Default::default()
29192 };
29193 EditorElement::new(
29194 &self.prompt,
29195 EditorStyle {
29196 background: cx.theme().colors().editor_background,
29197 local_player: cx.theme().players().local(),
29198 text: text_style,
29199 ..Default::default()
29200 },
29201 )
29202 }
29203
29204 fn render_close_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
29205 let focus_handle = self.prompt.focus_handle(cx);
29206 IconButton::new("cancel", IconName::Close)
29207 .icon_color(Color::Muted)
29208 .shape(IconButtonShape::Square)
29209 .tooltip(move |_window, cx| {
29210 Tooltip::for_action_in("Cancel", &menu::Cancel, &focus_handle, cx)
29211 })
29212 .on_click(cx.listener(|this, _, window, cx| {
29213 this.cancel(&menu::Cancel, window, cx);
29214 }))
29215 }
29216
29217 fn render_confirm_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
29218 let focus_handle = self.prompt.focus_handle(cx);
29219 IconButton::new("confirm", IconName::Return)
29220 .icon_color(Color::Muted)
29221 .shape(IconButtonShape::Square)
29222 .tooltip(move |_window, cx| {
29223 Tooltip::for_action_in("Confirm", &menu::Confirm, &focus_handle, cx)
29224 })
29225 .on_click(cx.listener(|this, _, window, cx| {
29226 this.confirm(&menu::Confirm, window, cx);
29227 }))
29228 }
29229}
29230
29231impl Render for BreakpointPromptEditor {
29232 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
29233 let ui_font_size = ThemeSettings::get_global(cx).ui_font_size(cx);
29234 let editor_margins = *self.editor_margins.lock();
29235 let gutter_dimensions = editor_margins.gutter;
29236 let left_gutter_width = gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0);
29237 let right_padding = editor_margins.right + px(9.);
29238 h_flex()
29239 .key_context("Editor")
29240 .bg(cx.theme().colors().editor_background)
29241 .border_y_1()
29242 .border_color(cx.theme().status().info_border)
29243 .size_full()
29244 .py(window.line_height() / 2.5)
29245 .pr(right_padding)
29246 .on_action(cx.listener(Self::confirm))
29247 .on_action(cx.listener(Self::cancel))
29248 .child(
29249 WithRemSize::new(ui_font_size)
29250 .h_full()
29251 .w(left_gutter_width)
29252 .flex()
29253 .flex_row()
29254 .flex_shrink_0()
29255 .items_center()
29256 .justify_center()
29257 .gap_1()
29258 .child(self.render_close_button(cx)),
29259 )
29260 .child(
29261 h_flex()
29262 .w_full()
29263 .justify_between()
29264 .child(div().flex_1().child(self.render_prompt_editor(cx)))
29265 .child(
29266 WithRemSize::new(ui_font_size)
29267 .flex()
29268 .flex_row()
29269 .items_center()
29270 .child(self.render_confirm_button(cx)),
29271 ),
29272 )
29273 }
29274}
29275
29276impl Focusable for BreakpointPromptEditor {
29277 fn focus_handle(&self, cx: &App) -> FocusHandle {
29278 self.prompt.focus_handle(cx)
29279 }
29280}
29281
29282fn all_edits_insertions_or_deletions(
29283 edits: &Vec<(Range<Anchor>, Arc<str>)>,
29284 snapshot: &MultiBufferSnapshot,
29285) -> bool {
29286 let mut all_insertions = true;
29287 let mut all_deletions = true;
29288
29289 for (range, new_text) in edits.iter() {
29290 let range_is_empty = range.to_offset(snapshot).is_empty();
29291 let text_is_empty = new_text.is_empty();
29292
29293 if range_is_empty != text_is_empty {
29294 if range_is_empty {
29295 all_deletions = false;
29296 } else {
29297 all_insertions = false;
29298 }
29299 } else {
29300 return false;
29301 }
29302
29303 if !all_insertions && !all_deletions {
29304 return false;
29305 }
29306 }
29307 all_insertions || all_deletions
29308}
29309
29310struct MissingEditPredictionKeybindingTooltip;
29311
29312impl Render for MissingEditPredictionKeybindingTooltip {
29313 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
29314 ui::tooltip_container(cx, |container, cx| {
29315 container
29316 .flex_shrink_0()
29317 .max_w_80()
29318 .min_h(rems_from_px(124.))
29319 .justify_between()
29320 .child(
29321 v_flex()
29322 .flex_1()
29323 .text_ui_sm(cx)
29324 .child(Label::new("Conflict with Accept Keybinding"))
29325 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
29326 )
29327 .child(
29328 h_flex()
29329 .pb_1()
29330 .gap_1()
29331 .items_end()
29332 .w_full()
29333 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
29334 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
29335 }))
29336 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
29337 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
29338 })),
29339 )
29340 })
29341 }
29342}
29343
29344#[derive(Debug, Clone, Copy, PartialEq)]
29345pub struct LineHighlight {
29346 pub background: Background,
29347 pub border: Option<gpui::Hsla>,
29348 pub include_gutter: bool,
29349 pub type_id: Option<TypeId>,
29350}
29351
29352struct LineManipulationResult {
29353 pub new_text: String,
29354 pub line_count_before: usize,
29355 pub line_count_after: usize,
29356}
29357
29358fn render_diff_hunk_controls(
29359 row: u32,
29360 status: &DiffHunkStatus,
29361 hunk_range: Range<Anchor>,
29362 is_created_file: bool,
29363 line_height: Pixels,
29364 editor: &Entity<Editor>,
29365 _window: &mut Window,
29366 cx: &mut App,
29367) -> AnyElement {
29368 h_flex()
29369 .h(line_height)
29370 .mr_1()
29371 .gap_1()
29372 .px_0p5()
29373 .pb_1()
29374 .border_x_1()
29375 .border_b_1()
29376 .border_color(cx.theme().colors().border_variant)
29377 .rounded_b_lg()
29378 .bg(cx.theme().colors().editor_background)
29379 .gap_1()
29380 .block_mouse_except_scroll()
29381 .shadow_md()
29382 .child(if status.has_secondary_hunk() {
29383 Button::new(("stage", row as u64), "Stage")
29384 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
29385 .tooltip({
29386 let focus_handle = editor.focus_handle(cx);
29387 move |_window, cx| {
29388 Tooltip::for_action_in(
29389 "Stage Hunk",
29390 &::git::ToggleStaged,
29391 &focus_handle,
29392 cx,
29393 )
29394 }
29395 })
29396 .on_click({
29397 let editor = editor.clone();
29398 move |_event, _window, cx| {
29399 editor.update(cx, |editor, cx| {
29400 editor.stage_or_unstage_diff_hunks(
29401 true,
29402 vec![hunk_range.start..hunk_range.start],
29403 cx,
29404 );
29405 });
29406 }
29407 })
29408 } else {
29409 Button::new(("unstage", row as u64), "Unstage")
29410 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
29411 .tooltip({
29412 let focus_handle = editor.focus_handle(cx);
29413 move |_window, cx| {
29414 Tooltip::for_action_in(
29415 "Unstage Hunk",
29416 &::git::ToggleStaged,
29417 &focus_handle,
29418 cx,
29419 )
29420 }
29421 })
29422 .on_click({
29423 let editor = editor.clone();
29424 move |_event, _window, cx| {
29425 editor.update(cx, |editor, cx| {
29426 editor.stage_or_unstage_diff_hunks(
29427 false,
29428 vec![hunk_range.start..hunk_range.start],
29429 cx,
29430 );
29431 });
29432 }
29433 })
29434 })
29435 .child(
29436 Button::new(("restore", row as u64), "Restore")
29437 .tooltip({
29438 let focus_handle = editor.focus_handle(cx);
29439 move |_window, cx| {
29440 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
29441 }
29442 })
29443 .on_click({
29444 let editor = editor.clone();
29445 move |_event, window, cx| {
29446 editor.update(cx, |editor, cx| {
29447 let snapshot = editor.snapshot(window, cx);
29448 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
29449 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
29450 });
29451 }
29452 })
29453 .disabled(is_created_file),
29454 )
29455 .when(
29456 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
29457 |el| {
29458 el.child(
29459 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
29460 .shape(IconButtonShape::Square)
29461 .icon_size(IconSize::Small)
29462 // .disabled(!has_multiple_hunks)
29463 .tooltip({
29464 let focus_handle = editor.focus_handle(cx);
29465 move |_window, cx| {
29466 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
29467 }
29468 })
29469 .on_click({
29470 let editor = editor.clone();
29471 move |_event, window, cx| {
29472 editor.update(cx, |editor, cx| {
29473 let snapshot = editor.snapshot(window, cx);
29474 let position =
29475 hunk_range.end.to_point(&snapshot.buffer_snapshot());
29476 editor.go_to_hunk_before_or_after_position(
29477 &snapshot,
29478 position,
29479 Direction::Next,
29480 true,
29481 window,
29482 cx,
29483 );
29484 editor.expand_selected_diff_hunks(cx);
29485 });
29486 }
29487 }),
29488 )
29489 .child(
29490 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
29491 .shape(IconButtonShape::Square)
29492 .icon_size(IconSize::Small)
29493 // .disabled(!has_multiple_hunks)
29494 .tooltip({
29495 let focus_handle = editor.focus_handle(cx);
29496 move |_window, cx| {
29497 Tooltip::for_action_in(
29498 "Previous Hunk",
29499 &GoToPreviousHunk,
29500 &focus_handle,
29501 cx,
29502 )
29503 }
29504 })
29505 .on_click({
29506 let editor = editor.clone();
29507 move |_event, window, cx| {
29508 editor.update(cx, |editor, cx| {
29509 let snapshot = editor.snapshot(window, cx);
29510 let point =
29511 hunk_range.start.to_point(&snapshot.buffer_snapshot());
29512 editor.go_to_hunk_before_or_after_position(
29513 &snapshot,
29514 point,
29515 Direction::Prev,
29516 true,
29517 window,
29518 cx,
29519 );
29520 editor.expand_selected_diff_hunks(cx);
29521 });
29522 }
29523 }),
29524 )
29525 },
29526 )
29527 .into_any_element()
29528}
29529
29530pub fn multibuffer_context_lines(cx: &App) -> u32 {
29531 EditorSettings::try_get(cx)
29532 .map(|settings| settings.excerpt_context_lines)
29533 .unwrap_or(2)
29534 .min(32)
29535}