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 editor_settings;
21mod element;
22mod git;
23mod highlight_matching_bracket;
24mod hover_links;
25pub mod hover_popover;
26mod indent_guides;
27mod inlays;
28pub mod items;
29mod jsx_tag_auto_close;
30mod linked_editing_ranges;
31mod lsp_colors;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod rust_analyzer_ext;
37pub mod scroll;
38mod selections_collection;
39mod split;
40pub mod split_editor_view;
41pub mod tasks;
42
43#[cfg(test)]
44mod code_completion_tests;
45#[cfg(test)]
46mod edit_prediction_tests;
47#[cfg(test)]
48mod editor_tests;
49mod signature_help;
50#[cfg(any(test, feature = "test-support"))]
51pub mod test;
52
53pub(crate) use actions::*;
54pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
55pub use edit_prediction_types::Direction;
56pub use editor_settings::{
57 CompletionDetailAlignment, CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings,
58 HideMouseMode, ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
59};
60pub use element::{
61 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
62 render_breadcrumb_text,
63};
64pub use git::blame::BlameRenderer;
65pub use hover_popover::hover_markdown_style;
66pub use inlays::Inlay;
67pub use items::MAX_TAB_TITLE_LEN;
68pub use lsp::CompletionContext;
69pub use lsp_ext::lsp_tasks;
70pub use multi_buffer::{
71 Anchor, AnchorRangeExt, BufferOffset, ExcerptId, ExcerptRange, MBTextSummary, MultiBuffer,
72 MultiBufferOffset, MultiBufferOffsetUtf16, MultiBufferSnapshot, PathKey, RowInfo, ToOffset,
73 ToPoint,
74};
75pub use split::{SplitDiffFeatureFlag, SplittableEditor, ToggleLockedCursors, ToggleSplitDiff};
76pub use split_editor_view::SplitEditorView;
77pub use text::Bias;
78
79use ::git::{Restore, blame::BlameEntry, commit::ParsedCommitMessage, status::FileStatus};
80use aho_corasick::{AhoCorasick, AhoCorasickBuilder, BuildError};
81use anyhow::{Context as _, Result, anyhow, bail};
82use blink_manager::BlinkManager;
83use buffer_diff::DiffHunkStatus;
84use client::{Collaborator, ParticipantIndex, parse_zed_link};
85use clock::ReplicaId;
86use code_context_menus::{
87 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
88 CompletionsMenu, ContextMenuOrigin,
89};
90use collections::{BTreeMap, HashMap, HashSet, VecDeque};
91use convert_case::{Case, Casing};
92use dap::TelemetrySpawnLocation;
93use display_map::*;
94use edit_prediction_types::{
95 EditPredictionDelegate, EditPredictionDelegateHandle, EditPredictionDismissReason,
96 EditPredictionGranularity, SuggestionDisplayType,
97};
98use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
99use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
100use futures::{
101 FutureExt,
102 future::{self, Shared, join},
103};
104use fuzzy::{StringMatch, StringMatchCandidate};
105use git::blame::{GitBlame, GlobalBlameRenderer};
106use gpui::{
107 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
108 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
109 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
110 Focusable, FontId, FontStyle, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
111 MouseButton, MouseDownEvent, MouseMoveEvent, PaintQuad, ParentElement, Pixels, PressureStage,
112 Render, ScrollHandle, SharedString, SharedUri, Size, Stateful, Styled, Subscription, Task,
113 TextRun, TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle,
114 UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window, div, point, prelude::*,
115 pulsating_between, px, relative, size,
116};
117use hover_links::{HoverLink, HoveredLinkState, find_file};
118use hover_popover::{HoverState, hide_hover};
119use indent_guides::ActiveIndentGuidesState;
120use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
121use itertools::{Either, Itertools};
122use language::{
123 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
124 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
125 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
126 IndentSize, Language, LanguageName, LanguageRegistry, LanguageScope, OffsetRangeExt,
127 OutlineItem, Point, Runnable, Selection, SelectionGoal, TextObject, TransactionId,
128 TreeSitterOptions, WordsQuery,
129 language_settings::{
130 self, LanguageSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
131 all_language_settings, language_settings,
132 },
133 point_from_lsp, point_to_lsp, text_diff_with_options,
134};
135use linked_editing_ranges::refresh_linked_ranges;
136use lsp::{
137 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
138 LanguageServerId,
139};
140use lsp_colors::LspColorData;
141use markdown::Markdown;
142use mouse_context_menu::MouseContextMenu;
143use movement::TextLayoutDetails;
144use multi_buffer::{
145 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
146};
147use parking_lot::Mutex;
148use persistence::DB;
149use project::{
150 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
151 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
152 InvalidationStrategy, Location, LocationLink, LspAction, PrepareRenameResponse, Project,
153 ProjectItem, ProjectPath, ProjectTransaction, TaskSourceKind,
154 debugger::{
155 breakpoint_store::{
156 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
157 BreakpointStore, BreakpointStoreEvent,
158 },
159 session::{Session, SessionEvent},
160 },
161 git_store::GitStoreEvent,
162 lsp_store::{
163 CacheInlayHints, CompletionDocumentation, FormatTrigger, LspFormatTarget,
164 OpenLspBufferHandle,
165 },
166 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
167};
168use rand::seq::SliceRandom;
169use regex::Regex;
170use rpc::{ErrorCode, ErrorExt, proto::PeerId};
171use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, SharedScrollAnchor};
172use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
173use serde::{Deserialize, Serialize};
174use settings::{
175 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
176 update_settings_file,
177};
178use smallvec::{SmallVec, smallvec};
179use snippet::Snippet;
180use std::{
181 any::{Any, TypeId},
182 borrow::Cow,
183 cell::{OnceCell, RefCell},
184 cmp::{self, Ordering, Reverse},
185 collections::hash_map,
186 iter::{self, Peekable},
187 mem,
188 num::NonZeroU32,
189 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
190 path::{Path, PathBuf},
191 rc::Rc,
192 sync::Arc,
193 time::{Duration, Instant},
194};
195use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
196use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
197use theme::{
198 AccentColors, ActiveTheme, GlobalTheme, PlayerColor, StatusColors, SyntaxTheme, Theme,
199 ThemeSettings, observe_buffer_font_size_adjustment,
200};
201use ui::{
202 Avatar, ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape,
203 IconName, IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
204};
205use ui_input::ErasedEditor;
206use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
207use workspace::{
208 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, NavigationEntry, OpenInTerminal,
209 OpenTerminal, Pane, RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection,
210 TabBarSettings, Toast, ViewId, Workspace, WorkspaceId, WorkspaceSettings,
211 item::{BreadcrumbText, ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
212 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
213 searchable::{CollapseDirection, SearchEvent},
214};
215use zed_actions::editor::{MoveDown, MoveUp};
216
217use crate::{
218 code_context_menus::CompletionsMenuSource,
219 editor_settings::MultiCursorModifier,
220 hover_links::{find_url, find_url_from_range},
221 inlays::{
222 InlineValueCache,
223 inlay_hints::{LspInlayHintData, inlay_hint_settings},
224 },
225 scroll::{ScrollOffset, ScrollPixelOffset},
226 selections_collection::resolve_selections_wrapping_blocks,
227 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
228};
229
230pub const FILE_HEADER_HEIGHT: u32 = 2;
231pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
232const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
233const MAX_LINE_LEN: usize = 1024;
234const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
235const MAX_SELECTION_HISTORY_LEN: usize = 1024;
236pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
237#[doc(hidden)]
238pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
239pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
240
241pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
242pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
243pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
244pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
245
246pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
247pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
248pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
249
250pub type RenderDiffHunkControlsFn = Arc<
251 dyn Fn(
252 u32,
253 &DiffHunkStatus,
254 Range<Anchor>,
255 bool,
256 Pixels,
257 &Entity<Editor>,
258 &mut Window,
259 &mut App,
260 ) -> AnyElement,
261>;
262
263enum ReportEditorEvent {
264 Saved { auto_saved: bool },
265 EditorOpened,
266 Closed,
267}
268
269impl ReportEditorEvent {
270 pub fn event_type(&self) -> &'static str {
271 match self {
272 Self::Saved { .. } => "Editor Saved",
273 Self::EditorOpened => "Editor Opened",
274 Self::Closed => "Editor Closed",
275 }
276 }
277}
278
279pub enum ActiveDebugLine {}
280pub enum DebugStackFrameLine {}
281enum DocumentHighlightRead {}
282enum DocumentHighlightWrite {}
283enum InputComposition {}
284pub enum PendingInput {}
285enum SelectedTextHighlight {}
286
287pub enum ConflictsOuter {}
288pub enum ConflictsOurs {}
289pub enum ConflictsTheirs {}
290pub enum ConflictsOursMarker {}
291pub enum ConflictsTheirsMarker {}
292
293pub struct HunkAddedColor;
294pub struct HunkRemovedColor;
295
296#[derive(Debug, Copy, Clone, PartialEq, Eq)]
297pub enum Navigated {
298 Yes,
299 No,
300}
301
302impl Navigated {
303 pub fn from_bool(yes: bool) -> Navigated {
304 if yes { Navigated::Yes } else { Navigated::No }
305 }
306}
307
308#[derive(Debug, Clone, PartialEq, Eq)]
309enum DisplayDiffHunk {
310 Folded {
311 display_row: DisplayRow,
312 },
313 Unfolded {
314 is_created_file: bool,
315 diff_base_byte_range: Range<usize>,
316 display_row_range: Range<DisplayRow>,
317 multi_buffer_range: Range<Anchor>,
318 status: DiffHunkStatus,
319 word_diffs: Vec<Range<MultiBufferOffset>>,
320 },
321}
322
323pub enum HideMouseCursorOrigin {
324 TypingAction,
325 MovementAction,
326}
327
328pub fn init(cx: &mut App) {
329 cx.set_global(GlobalBlameRenderer(Arc::new(())));
330
331 workspace::register_project_item::<Editor>(cx);
332 workspace::FollowableViewRegistry::register::<Editor>(cx);
333 workspace::register_serializable_item::<Editor>(cx);
334
335 cx.observe_new(
336 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
337 workspace.register_action(Editor::new_file);
338 workspace.register_action(Editor::new_file_split);
339 workspace.register_action(Editor::new_file_vertical);
340 workspace.register_action(Editor::new_file_horizontal);
341 workspace.register_action(Editor::cancel_language_server_work);
342 workspace.register_action(Editor::toggle_focus);
343 },
344 )
345 .detach();
346
347 cx.on_action(move |_: &workspace::NewFile, cx| {
348 let app_state = workspace::AppState::global(cx);
349 if let Some(app_state) = app_state.upgrade() {
350 workspace::open_new(
351 Default::default(),
352 app_state,
353 cx,
354 |workspace, window, cx| {
355 Editor::new_file(workspace, &Default::default(), window, cx)
356 },
357 )
358 .detach();
359 }
360 })
361 .on_action(move |_: &workspace::NewWindow, cx| {
362 let app_state = workspace::AppState::global(cx);
363 if let Some(app_state) = app_state.upgrade() {
364 workspace::open_new(
365 Default::default(),
366 app_state,
367 cx,
368 |workspace, window, cx| {
369 cx.activate(true);
370 Editor::new_file(workspace, &Default::default(), window, cx)
371 },
372 )
373 .detach();
374 }
375 });
376 _ = ui_input::ERASED_EDITOR_FACTORY.set(|window, cx| {
377 Arc::new(ErasedEditorImpl(
378 cx.new(|cx| Editor::single_line(window, cx)),
379 )) as Arc<dyn ErasedEditor>
380 });
381 _ = multi_buffer::EXCERPT_CONTEXT_LINES.set(multibuffer_context_lines);
382}
383
384pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
385 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
386}
387
388pub trait DiagnosticRenderer {
389 fn render_group(
390 &self,
391 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
392 buffer_id: BufferId,
393 snapshot: EditorSnapshot,
394 editor: WeakEntity<Editor>,
395 language_registry: Option<Arc<LanguageRegistry>>,
396 cx: &mut App,
397 ) -> Vec<BlockProperties<Anchor>>;
398
399 fn render_hover(
400 &self,
401 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
402 range: Range<Point>,
403 buffer_id: BufferId,
404 language_registry: Option<Arc<LanguageRegistry>>,
405 cx: &mut App,
406 ) -> Option<Entity<markdown::Markdown>>;
407
408 fn open_link(
409 &self,
410 editor: &mut Editor,
411 link: SharedString,
412 window: &mut Window,
413 cx: &mut Context<Editor>,
414 );
415}
416
417pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
418
419impl GlobalDiagnosticRenderer {
420 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
421 cx.try_global::<Self>().map(|g| g.0.clone())
422 }
423}
424
425impl gpui::Global for GlobalDiagnosticRenderer {}
426pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
427 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
428}
429
430pub struct SearchWithinRange;
431
432trait InvalidationRegion {
433 fn ranges(&self) -> &[Range<Anchor>];
434}
435
436#[derive(Clone, Debug, PartialEq)]
437pub enum SelectPhase {
438 Begin {
439 position: DisplayPoint,
440 add: bool,
441 click_count: usize,
442 },
443 BeginColumnar {
444 position: DisplayPoint,
445 reset: bool,
446 mode: ColumnarMode,
447 goal_column: u32,
448 },
449 Extend {
450 position: DisplayPoint,
451 click_count: usize,
452 },
453 Update {
454 position: DisplayPoint,
455 goal_column: u32,
456 scroll_delta: gpui::Point<f32>,
457 },
458 End,
459}
460
461#[derive(Clone, Debug, PartialEq)]
462pub enum ColumnarMode {
463 FromMouse,
464 FromSelection,
465}
466
467#[derive(Clone, Debug)]
468pub enum SelectMode {
469 Character,
470 Word(Range<Anchor>),
471 Line(Range<Anchor>),
472 All,
473}
474
475#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
476pub enum SizingBehavior {
477 /// The editor will layout itself using `size_full` and will include the vertical
478 /// scroll margin as requested by user settings.
479 #[default]
480 Default,
481 /// The editor will layout itself using `size_full`, but will not have any
482 /// vertical overscroll.
483 ExcludeOverscrollMargin,
484 /// The editor will request a vertical size according to its content and will be
485 /// layouted without a vertical scroll margin.
486 SizeByContent,
487}
488
489#[derive(Clone, PartialEq, Eq, Debug)]
490pub enum EditorMode {
491 SingleLine,
492 AutoHeight {
493 min_lines: usize,
494 max_lines: Option<usize>,
495 },
496 Full {
497 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
498 scale_ui_elements_with_buffer_font_size: bool,
499 /// When set to `true`, the editor will render a background for the active line.
500 show_active_line_background: bool,
501 /// Determines the sizing behavior for this editor
502 sizing_behavior: SizingBehavior,
503 },
504 Minimap {
505 parent: WeakEntity<Editor>,
506 },
507}
508
509impl EditorMode {
510 pub fn full() -> Self {
511 Self::Full {
512 scale_ui_elements_with_buffer_font_size: true,
513 show_active_line_background: true,
514 sizing_behavior: SizingBehavior::Default,
515 }
516 }
517
518 #[inline]
519 pub fn is_full(&self) -> bool {
520 matches!(self, Self::Full { .. })
521 }
522
523 #[inline]
524 pub fn is_single_line(&self) -> bool {
525 matches!(self, Self::SingleLine { .. })
526 }
527
528 #[inline]
529 fn is_minimap(&self) -> bool {
530 matches!(self, Self::Minimap { .. })
531 }
532}
533
534#[derive(Copy, Clone, Debug)]
535pub enum SoftWrap {
536 /// Prefer not to wrap at all.
537 ///
538 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
539 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
540 GitDiff,
541 /// Prefer a single line generally, unless an overly long line is encountered.
542 None,
543 /// Soft wrap lines that exceed the editor width.
544 EditorWidth,
545 /// Soft wrap lines at the preferred line length.
546 Column(u32),
547 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
548 Bounded(u32),
549}
550
551#[derive(Clone)]
552pub struct EditorStyle {
553 pub background: Hsla,
554 pub border: Hsla,
555 pub local_player: PlayerColor,
556 pub text: TextStyle,
557 pub scrollbar_width: Pixels,
558 pub syntax: Arc<SyntaxTheme>,
559 pub status: StatusColors,
560 pub inlay_hints_style: HighlightStyle,
561 pub edit_prediction_styles: EditPredictionStyles,
562 pub unnecessary_code_fade: f32,
563 pub show_underlines: bool,
564}
565
566impl Default for EditorStyle {
567 fn default() -> Self {
568 Self {
569 background: Hsla::default(),
570 border: Hsla::default(),
571 local_player: PlayerColor::default(),
572 text: TextStyle::default(),
573 scrollbar_width: Pixels::default(),
574 syntax: Default::default(),
575 // HACK: Status colors don't have a real default.
576 // We should look into removing the status colors from the editor
577 // style and retrieve them directly from the theme.
578 status: StatusColors::dark(),
579 inlay_hints_style: HighlightStyle::default(),
580 edit_prediction_styles: EditPredictionStyles {
581 insertion: HighlightStyle::default(),
582 whitespace: HighlightStyle::default(),
583 },
584 unnecessary_code_fade: Default::default(),
585 show_underlines: true,
586 }
587 }
588}
589
590pub fn make_inlay_hints_style(cx: &App) -> HighlightStyle {
591 let show_background = language_settings::language_settings(None, None, cx)
592 .inlay_hints
593 .show_background;
594
595 let mut style = cx.theme().syntax().get("hint");
596
597 if style.color.is_none() {
598 style.color = Some(cx.theme().status().hint);
599 }
600
601 if !show_background {
602 style.background_color = None;
603 return style;
604 }
605
606 if style.background_color.is_none() {
607 style.background_color = Some(cx.theme().status().hint_background);
608 }
609
610 style
611}
612
613pub fn make_suggestion_styles(cx: &App) -> EditPredictionStyles {
614 EditPredictionStyles {
615 insertion: HighlightStyle {
616 color: Some(cx.theme().status().predictive),
617 ..HighlightStyle::default()
618 },
619 whitespace: HighlightStyle {
620 background_color: Some(cx.theme().status().created_background),
621 ..HighlightStyle::default()
622 },
623 }
624}
625
626type CompletionId = usize;
627
628pub(crate) enum EditDisplayMode {
629 TabAccept,
630 DiffPopover,
631 Inline,
632}
633
634enum EditPrediction {
635 Edit {
636 edits: Vec<(Range<Anchor>, Arc<str>)>,
637 /// Predicted cursor position as (anchor, offset_from_anchor).
638 /// The anchor is in multibuffer coordinates; after applying edits,
639 /// resolve the anchor and add the offset to get the final cursor position.
640 cursor_position: Option<(Anchor, usize)>,
641 edit_preview: Option<EditPreview>,
642 display_mode: EditDisplayMode,
643 snapshot: BufferSnapshot,
644 },
645 /// Move to a specific location in the active editor
646 MoveWithin {
647 target: Anchor,
648 snapshot: BufferSnapshot,
649 },
650 /// Move to a specific location in a different editor (not the active one)
651 MoveOutside {
652 target: language::Anchor,
653 snapshot: BufferSnapshot,
654 },
655}
656
657struct EditPredictionState {
658 inlay_ids: Vec<InlayId>,
659 completion: EditPrediction,
660 completion_id: Option<SharedString>,
661 invalidation_range: Option<Range<Anchor>>,
662}
663
664enum EditPredictionSettings {
665 Disabled,
666 Enabled {
667 show_in_menu: bool,
668 preview_requires_modifier: bool,
669 },
670}
671
672enum EditPredictionHighlight {}
673
674#[derive(Debug, Clone)]
675struct InlineDiagnostic {
676 message: SharedString,
677 group_id: usize,
678 is_primary: bool,
679 start: Point,
680 severity: lsp::DiagnosticSeverity,
681}
682
683pub enum MenuEditPredictionsPolicy {
684 Never,
685 ByProvider,
686}
687
688pub enum EditPredictionPreview {
689 /// Modifier is not pressed
690 Inactive { released_too_fast: bool },
691 /// Modifier pressed
692 Active {
693 since: Instant,
694 previous_scroll_position: Option<SharedScrollAnchor>,
695 },
696}
697
698impl EditPredictionPreview {
699 pub fn released_too_fast(&self) -> bool {
700 match self {
701 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
702 EditPredictionPreview::Active { .. } => false,
703 }
704 }
705
706 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<SharedScrollAnchor>) {
707 if let EditPredictionPreview::Active {
708 previous_scroll_position,
709 ..
710 } = self
711 {
712 *previous_scroll_position = scroll_position;
713 }
714 }
715}
716
717pub struct ContextMenuOptions {
718 pub min_entries_visible: usize,
719 pub max_entries_visible: usize,
720 pub placement: Option<ContextMenuPlacement>,
721}
722
723#[derive(Debug, Clone, PartialEq, Eq)]
724pub enum ContextMenuPlacement {
725 Above,
726 Below,
727}
728
729#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
730struct EditorActionId(usize);
731
732impl EditorActionId {
733 pub fn post_inc(&mut self) -> Self {
734 let answer = self.0;
735
736 *self = Self(answer + 1);
737
738 Self(answer)
739 }
740}
741
742// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
743// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
744
745type BackgroundHighlight = (
746 Arc<dyn Fn(&usize, &Theme) -> Hsla + Send + Sync>,
747 Arc<[Range<Anchor>]>,
748);
749type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
750
751#[derive(Default)]
752struct ScrollbarMarkerState {
753 scrollbar_size: Size<Pixels>,
754 dirty: bool,
755 markers: Arc<[PaintQuad]>,
756 pending_refresh: Option<Task<Result<()>>>,
757}
758
759impl ScrollbarMarkerState {
760 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
761 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
762 }
763}
764
765#[derive(Clone, Copy, PartialEq, Eq)]
766pub enum MinimapVisibility {
767 Disabled,
768 Enabled {
769 /// The configuration currently present in the users settings.
770 setting_configuration: bool,
771 /// Whether to override the currently set visibility from the users setting.
772 toggle_override: bool,
773 },
774}
775
776impl MinimapVisibility {
777 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
778 if mode.is_full() {
779 Self::Enabled {
780 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
781 toggle_override: false,
782 }
783 } else {
784 Self::Disabled
785 }
786 }
787
788 fn hidden(&self) -> Self {
789 match *self {
790 Self::Enabled {
791 setting_configuration,
792 ..
793 } => Self::Enabled {
794 setting_configuration,
795 toggle_override: setting_configuration,
796 },
797 Self::Disabled => Self::Disabled,
798 }
799 }
800
801 fn disabled(&self) -> bool {
802 matches!(*self, Self::Disabled)
803 }
804
805 fn settings_visibility(&self) -> bool {
806 match *self {
807 Self::Enabled {
808 setting_configuration,
809 ..
810 } => setting_configuration,
811 _ => false,
812 }
813 }
814
815 fn visible(&self) -> bool {
816 match *self {
817 Self::Enabled {
818 setting_configuration,
819 toggle_override,
820 } => setting_configuration ^ toggle_override,
821 _ => false,
822 }
823 }
824
825 fn toggle_visibility(&self) -> Self {
826 match *self {
827 Self::Enabled {
828 toggle_override,
829 setting_configuration,
830 } => Self::Enabled {
831 setting_configuration,
832 toggle_override: !toggle_override,
833 },
834 Self::Disabled => Self::Disabled,
835 }
836 }
837}
838
839#[derive(Debug, Clone, Copy, PartialEq, Eq)]
840pub enum BufferSerialization {
841 All,
842 NonDirtyBuffers,
843}
844
845impl BufferSerialization {
846 fn new(restore_unsaved_buffers: bool) -> Self {
847 if restore_unsaved_buffers {
848 Self::All
849 } else {
850 Self::NonDirtyBuffers
851 }
852 }
853}
854
855#[derive(Clone, Debug)]
856struct RunnableTasks {
857 templates: Vec<(TaskSourceKind, TaskTemplate)>,
858 offset: multi_buffer::Anchor,
859 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
860 column: u32,
861 // Values of all named captures, including those starting with '_'
862 extra_variables: HashMap<String, String>,
863 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
864 context_range: Range<BufferOffset>,
865}
866
867impl RunnableTasks {
868 fn resolve<'a>(
869 &'a self,
870 cx: &'a task::TaskContext,
871 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
872 self.templates.iter().filter_map(|(kind, template)| {
873 template
874 .resolve_task(&kind.to_id_base(), cx)
875 .map(|task| (kind.clone(), task))
876 })
877 }
878}
879
880#[derive(Clone)]
881pub struct ResolvedTasks {
882 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
883 position: Anchor,
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 show_line_numbers: Option<bool>,
1181 use_relative_line_numbers: Option<bool>,
1182 show_git_diff_gutter: Option<bool>,
1183 show_code_actions: Option<bool>,
1184 show_runnables: Option<bool>,
1185 show_breakpoints: Option<bool>,
1186 show_diff_review_button: bool,
1187 show_wrap_guides: Option<bool>,
1188 show_indent_guides: Option<bool>,
1189 buffers_with_disabled_indent_guides: HashSet<BufferId>,
1190 highlight_order: usize,
1191 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1192 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1193 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1194 scrollbar_marker_state: ScrollbarMarkerState,
1195 active_indent_guides_state: ActiveIndentGuidesState,
1196 nav_history: Option<ItemNavHistory>,
1197 context_menu: RefCell<Option<CodeContextMenu>>,
1198 context_menu_options: Option<ContextMenuOptions>,
1199 mouse_context_menu: Option<MouseContextMenu>,
1200 completion_tasks: Vec<(CompletionId, Task<()>)>,
1201 inline_blame_popover: Option<InlineBlamePopover>,
1202 inline_blame_popover_show_task: Option<Task<()>>,
1203 signature_help_state: SignatureHelpState,
1204 auto_signature_help: Option<bool>,
1205 find_all_references_task_sources: Vec<Anchor>,
1206 next_completion_id: CompletionId,
1207 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1208 code_actions_task: Option<Task<Result<()>>>,
1209 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1210 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1211 debounced_selection_highlight_complete: bool,
1212 document_highlights_task: Option<Task<()>>,
1213 linked_editing_range_task: Option<Task<Option<()>>>,
1214 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1215 pending_rename: Option<RenameState>,
1216 searchable: bool,
1217 cursor_shape: CursorShape,
1218 /// Whether the cursor is offset one character to the left when something is
1219 /// selected (needed for vim visual mode)
1220 cursor_offset_on_selection: bool,
1221 current_line_highlight: Option<CurrentLineHighlight>,
1222 /// Whether to collapse search match ranges to just their start position.
1223 /// When true, navigating to a match positions the cursor at the match
1224 /// without selecting the matched text.
1225 collapse_matches: bool,
1226 autoindent_mode: Option<AutoindentMode>,
1227 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1228 input_enabled: bool,
1229 use_modal_editing: bool,
1230 read_only: bool,
1231 leader_id: Option<CollaboratorId>,
1232 remote_id: Option<ViewId>,
1233 pub hover_state: HoverState,
1234 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1235 prev_pressure_stage: Option<PressureStage>,
1236 gutter_hovered: bool,
1237 hovered_link_state: Option<HoveredLinkState>,
1238 edit_prediction_provider: Option<RegisteredEditPredictionDelegate>,
1239 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1240 active_edit_prediction: Option<EditPredictionState>,
1241 /// Used to prevent flickering as the user types while the menu is open
1242 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1243 edit_prediction_settings: EditPredictionSettings,
1244 edit_predictions_hidden_for_vim_mode: bool,
1245 show_edit_predictions_override: Option<bool>,
1246 show_completions_on_input_override: Option<bool>,
1247 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1248 edit_prediction_preview: EditPredictionPreview,
1249 edit_prediction_indent_conflict: bool,
1250 edit_prediction_requires_modifier_in_indent_conflict: bool,
1251 next_inlay_id: usize,
1252 next_color_inlay_id: usize,
1253 _subscriptions: Vec<Subscription>,
1254 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1255 gutter_dimensions: GutterDimensions,
1256 style: Option<EditorStyle>,
1257 text_style_refinement: Option<TextStyleRefinement>,
1258 next_editor_action_id: EditorActionId,
1259 editor_actions: Rc<
1260 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1261 >,
1262 use_autoclose: bool,
1263 use_auto_surround: bool,
1264 auto_replace_emoji_shortcode: bool,
1265 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1266 show_git_blame_gutter: bool,
1267 show_git_blame_inline: bool,
1268 show_git_blame_inline_delay_task: Option<Task<()>>,
1269 git_blame_inline_enabled: bool,
1270 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1271 buffer_serialization: Option<BufferSerialization>,
1272 show_selection_menu: Option<bool>,
1273 blame: Option<Entity<GitBlame>>,
1274 blame_subscription: Option<Subscription>,
1275 custom_context_menu: Option<
1276 Box<
1277 dyn 'static
1278 + Fn(
1279 &mut Self,
1280 DisplayPoint,
1281 &mut Window,
1282 &mut Context<Self>,
1283 ) -> Option<Entity<ui::ContextMenu>>,
1284 >,
1285 >,
1286 last_bounds: Option<Bounds<Pixels>>,
1287 last_position_map: Option<Rc<PositionMap>>,
1288 expect_bounds_change: Option<Bounds<Pixels>>,
1289 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1290 tasks_update_task: Option<Task<()>>,
1291 breakpoint_store: Option<Entity<BreakpointStore>>,
1292 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1293 pub(crate) gutter_diff_review_indicator: (Option<PhantomDiffReviewIndicator>, Option<Task<()>>),
1294 pub(crate) diff_review_drag_state: Option<DiffReviewDragState>,
1295 /// Active diff review overlays. Multiple overlays can be open simultaneously
1296 /// when hunks have comments stored.
1297 pub(crate) diff_review_overlays: Vec<DiffReviewOverlay>,
1298 /// Stored review comments grouped by hunk.
1299 /// Uses a Vec instead of HashMap because DiffHunkKey contains an Anchor
1300 /// which doesn't implement Hash/Eq in a way suitable for HashMap keys.
1301 stored_review_comments: Vec<(DiffHunkKey, Vec<StoredReviewComment>)>,
1302 /// Counter for generating unique comment IDs.
1303 next_review_comment_id: usize,
1304 hovered_diff_hunk_row: Option<DisplayRow>,
1305 pull_diagnostics_task: Task<()>,
1306 in_project_search: bool,
1307 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1308 breadcrumb_header: Option<String>,
1309 focused_block: Option<FocusedBlock>,
1310 next_scroll_position: NextScrollCursorCenterTopBottom,
1311 addons: HashMap<TypeId, Box<dyn Addon>>,
1312 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1313 load_diff_task: Option<Shared<Task<()>>>,
1314 /// Whether we are temporarily displaying a diff other than git's
1315 temporary_diff_override: bool,
1316 selection_mark_mode: bool,
1317 toggle_fold_multiple_buffers: Task<()>,
1318 _scroll_cursor_center_top_bottom_task: Task<()>,
1319 serialize_selections: Task<()>,
1320 serialize_folds: Task<()>,
1321 mouse_cursor_hidden: bool,
1322 minimap: Option<Entity<Self>>,
1323 hide_mouse_mode: HideMouseMode,
1324 pub change_list: ChangeList,
1325 inline_value_cache: InlineValueCache,
1326 number_deleted_lines: bool,
1327
1328 selection_drag_state: SelectionDragState,
1329 colors: Option<LspColorData>,
1330 post_scroll_update: Task<()>,
1331 refresh_colors_task: Task<()>,
1332 inlay_hints: Option<LspInlayHintData>,
1333 folding_newlines: Task<()>,
1334 select_next_is_case_sensitive: Option<bool>,
1335 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1336 on_local_selections_changed:
1337 Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
1338 suppress_selection_callback: bool,
1339 applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
1340 accent_data: Option<AccentData>,
1341 fetched_tree_sitter_chunks: HashMap<ExcerptId, HashSet<Range<BufferRow>>>,
1342}
1343
1344#[derive(Debug, PartialEq)]
1345struct AccentData {
1346 colors: AccentColors,
1347 overrides: Vec<SharedString>,
1348}
1349
1350fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1351 if debounce_ms > 0 {
1352 Some(Duration::from_millis(debounce_ms))
1353 } else {
1354 None
1355 }
1356}
1357
1358#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1359enum NextScrollCursorCenterTopBottom {
1360 #[default]
1361 Center,
1362 Top,
1363 Bottom,
1364}
1365
1366impl NextScrollCursorCenterTopBottom {
1367 fn next(&self) -> Self {
1368 match self {
1369 Self::Center => Self::Top,
1370 Self::Top => Self::Bottom,
1371 Self::Bottom => Self::Center,
1372 }
1373 }
1374}
1375
1376#[derive(Clone)]
1377pub struct EditorSnapshot {
1378 pub mode: EditorMode,
1379 show_gutter: bool,
1380 offset_content: bool,
1381 show_line_numbers: Option<bool>,
1382 number_deleted_lines: bool,
1383 show_git_diff_gutter: Option<bool>,
1384 show_code_actions: Option<bool>,
1385 show_runnables: Option<bool>,
1386 show_breakpoints: Option<bool>,
1387 git_blame_gutter_max_author_length: Option<usize>,
1388 pub display_snapshot: DisplaySnapshot,
1389 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1390 is_focused: bool,
1391 scroll_anchor: SharedScrollAnchor,
1392 ongoing_scroll: OngoingScroll,
1393 current_line_highlight: CurrentLineHighlight,
1394 gutter_hovered: bool,
1395}
1396
1397#[derive(Default, Debug, Clone, Copy)]
1398pub struct GutterDimensions {
1399 pub left_padding: Pixels,
1400 pub right_padding: Pixels,
1401 pub width: Pixels,
1402 pub margin: Pixels,
1403 pub git_blame_entries_width: Option<Pixels>,
1404}
1405
1406impl GutterDimensions {
1407 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1408 Self {
1409 margin: Self::default_gutter_margin(font_id, font_size, cx),
1410 ..Default::default()
1411 }
1412 }
1413
1414 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1415 -cx.text_system().descent(font_id, font_size)
1416 }
1417 /// The full width of the space taken up by the gutter.
1418 pub fn full_width(&self) -> Pixels {
1419 self.margin + self.width
1420 }
1421
1422 /// The width of the space reserved for the fold indicators,
1423 /// use alongside 'justify_end' and `gutter_width` to
1424 /// right align content with the line numbers
1425 pub fn fold_area_width(&self) -> Pixels {
1426 self.margin + self.right_padding
1427 }
1428}
1429
1430struct CharacterDimensions {
1431 em_width: Pixels,
1432 em_advance: Pixels,
1433 line_height: Pixels,
1434}
1435
1436#[derive(Debug)]
1437pub struct RemoteSelection {
1438 pub replica_id: ReplicaId,
1439 pub selection: Selection<Anchor>,
1440 pub cursor_shape: CursorShape,
1441 pub collaborator_id: CollaboratorId,
1442 pub line_mode: bool,
1443 pub user_name: Option<SharedString>,
1444 pub color: PlayerColor,
1445}
1446
1447#[derive(Clone, Debug)]
1448struct SelectionHistoryEntry {
1449 selections: Arc<[Selection<Anchor>]>,
1450 select_next_state: Option<SelectNextState>,
1451 select_prev_state: Option<SelectNextState>,
1452 add_selections_state: Option<AddSelectionsState>,
1453}
1454
1455#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1456enum SelectionHistoryMode {
1457 #[default]
1458 Normal,
1459 Undoing,
1460 Redoing,
1461 Skipping,
1462}
1463
1464#[derive(Clone, PartialEq, Eq, Hash)]
1465struct HoveredCursor {
1466 replica_id: ReplicaId,
1467 selection_id: usize,
1468}
1469
1470#[derive(Debug)]
1471/// SelectionEffects controls the side-effects of updating the selection.
1472///
1473/// The default behaviour does "what you mostly want":
1474/// - it pushes to the nav history if the cursor moved by >10 lines
1475/// - it re-triggers completion requests
1476/// - it scrolls to fit
1477///
1478/// You might want to modify these behaviours. For example when doing a "jump"
1479/// like go to definition, we always want to add to nav history; but when scrolling
1480/// in vim mode we never do.
1481///
1482/// Similarly, you might want to disable scrolling if you don't want the viewport to
1483/// move.
1484#[derive(Clone)]
1485pub struct SelectionEffects {
1486 nav_history: Option<bool>,
1487 completions: bool,
1488 scroll: Option<Autoscroll>,
1489}
1490
1491impl Default for SelectionEffects {
1492 fn default() -> Self {
1493 Self {
1494 nav_history: None,
1495 completions: true,
1496 scroll: Some(Autoscroll::fit()),
1497 }
1498 }
1499}
1500impl SelectionEffects {
1501 pub fn scroll(scroll: Autoscroll) -> Self {
1502 Self {
1503 scroll: Some(scroll),
1504 ..Default::default()
1505 }
1506 }
1507
1508 pub fn no_scroll() -> Self {
1509 Self {
1510 scroll: None,
1511 ..Default::default()
1512 }
1513 }
1514
1515 pub fn completions(self, completions: bool) -> Self {
1516 Self {
1517 completions,
1518 ..self
1519 }
1520 }
1521
1522 pub fn nav_history(self, nav_history: bool) -> Self {
1523 Self {
1524 nav_history: Some(nav_history),
1525 ..self
1526 }
1527 }
1528}
1529
1530struct DeferredSelectionEffectsState {
1531 changed: bool,
1532 effects: SelectionEffects,
1533 old_cursor_position: Anchor,
1534 history_entry: SelectionHistoryEntry,
1535}
1536
1537#[derive(Default)]
1538struct SelectionHistory {
1539 #[allow(clippy::type_complexity)]
1540 selections_by_transaction:
1541 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1542 mode: SelectionHistoryMode,
1543 undo_stack: VecDeque<SelectionHistoryEntry>,
1544 redo_stack: VecDeque<SelectionHistoryEntry>,
1545}
1546
1547impl SelectionHistory {
1548 #[track_caller]
1549 fn insert_transaction(
1550 &mut self,
1551 transaction_id: TransactionId,
1552 selections: Arc<[Selection<Anchor>]>,
1553 ) {
1554 if selections.is_empty() {
1555 log::error!(
1556 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1557 std::panic::Location::caller()
1558 );
1559 return;
1560 }
1561 self.selections_by_transaction
1562 .insert(transaction_id, (selections, None));
1563 }
1564
1565 #[allow(clippy::type_complexity)]
1566 fn transaction(
1567 &self,
1568 transaction_id: TransactionId,
1569 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1570 self.selections_by_transaction.get(&transaction_id)
1571 }
1572
1573 #[allow(clippy::type_complexity)]
1574 fn transaction_mut(
1575 &mut self,
1576 transaction_id: TransactionId,
1577 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1578 self.selections_by_transaction.get_mut(&transaction_id)
1579 }
1580
1581 fn push(&mut self, entry: SelectionHistoryEntry) {
1582 if !entry.selections.is_empty() {
1583 match self.mode {
1584 SelectionHistoryMode::Normal => {
1585 self.push_undo(entry);
1586 self.redo_stack.clear();
1587 }
1588 SelectionHistoryMode::Undoing => self.push_redo(entry),
1589 SelectionHistoryMode::Redoing => self.push_undo(entry),
1590 SelectionHistoryMode::Skipping => {}
1591 }
1592 }
1593 }
1594
1595 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1596 if self
1597 .undo_stack
1598 .back()
1599 .is_none_or(|e| e.selections != entry.selections)
1600 {
1601 self.undo_stack.push_back(entry);
1602 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1603 self.undo_stack.pop_front();
1604 }
1605 }
1606 }
1607
1608 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1609 if self
1610 .redo_stack
1611 .back()
1612 .is_none_or(|e| e.selections != entry.selections)
1613 {
1614 self.redo_stack.push_back(entry);
1615 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1616 self.redo_stack.pop_front();
1617 }
1618 }
1619 }
1620}
1621
1622#[derive(Clone, Copy)]
1623pub struct RowHighlightOptions {
1624 pub autoscroll: bool,
1625 pub include_gutter: bool,
1626}
1627
1628impl Default for RowHighlightOptions {
1629 fn default() -> Self {
1630 Self {
1631 autoscroll: Default::default(),
1632 include_gutter: true,
1633 }
1634 }
1635}
1636
1637struct RowHighlight {
1638 index: usize,
1639 range: Range<Anchor>,
1640 color: Hsla,
1641 options: RowHighlightOptions,
1642 type_id: TypeId,
1643}
1644
1645#[derive(Clone, Debug)]
1646struct AddSelectionsState {
1647 groups: Vec<AddSelectionsGroup>,
1648}
1649
1650#[derive(Clone, Debug)]
1651struct AddSelectionsGroup {
1652 above: bool,
1653 stack: Vec<usize>,
1654}
1655
1656#[derive(Clone)]
1657struct SelectNextState {
1658 query: AhoCorasick,
1659 wordwise: bool,
1660 done: bool,
1661}
1662
1663impl std::fmt::Debug for SelectNextState {
1664 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1665 f.debug_struct(std::any::type_name::<Self>())
1666 .field("wordwise", &self.wordwise)
1667 .field("done", &self.done)
1668 .finish()
1669 }
1670}
1671
1672#[derive(Debug)]
1673struct AutocloseRegion {
1674 selection_id: usize,
1675 range: Range<Anchor>,
1676 pair: BracketPair,
1677}
1678
1679#[derive(Debug)]
1680struct SnippetState {
1681 ranges: Vec<Vec<Range<Anchor>>>,
1682 active_index: usize,
1683 choices: Vec<Option<Vec<String>>>,
1684}
1685
1686#[doc(hidden)]
1687pub struct RenameState {
1688 pub range: Range<Anchor>,
1689 pub old_name: Arc<str>,
1690 pub editor: Entity<Editor>,
1691 block_id: CustomBlockId,
1692}
1693
1694struct InvalidationStack<T>(Vec<T>);
1695
1696struct RegisteredEditPredictionDelegate {
1697 provider: Arc<dyn EditPredictionDelegateHandle>,
1698 _subscription: Subscription,
1699}
1700
1701#[derive(Debug, PartialEq, Eq)]
1702pub struct ActiveDiagnosticGroup {
1703 pub active_range: Range<Anchor>,
1704 pub active_message: String,
1705 pub group_id: usize,
1706 pub blocks: HashSet<CustomBlockId>,
1707}
1708
1709#[derive(Debug, PartialEq, Eq)]
1710
1711pub(crate) enum ActiveDiagnostic {
1712 None,
1713 All,
1714 Group(ActiveDiagnosticGroup),
1715}
1716
1717#[derive(Serialize, Deserialize, Clone, Debug)]
1718pub struct ClipboardSelection {
1719 /// The number of bytes in this selection.
1720 pub len: usize,
1721 /// Whether this was a full-line selection.
1722 pub is_entire_line: bool,
1723 /// The indentation of the first line when this content was originally copied.
1724 pub first_line_indent: u32,
1725 #[serde(default)]
1726 pub file_path: Option<PathBuf>,
1727 #[serde(default)]
1728 pub line_range: Option<RangeInclusive<u32>>,
1729}
1730
1731impl ClipboardSelection {
1732 pub fn for_buffer(
1733 len: usize,
1734 is_entire_line: bool,
1735 range: Range<Point>,
1736 buffer: &MultiBufferSnapshot,
1737 project: Option<&Entity<Project>>,
1738 cx: &App,
1739 ) -> Self {
1740 let first_line_indent = buffer
1741 .indent_size_for_line(MultiBufferRow(range.start.row))
1742 .len;
1743
1744 let file_path = util::maybe!({
1745 let project = project?.read(cx);
1746 let file = buffer.file_at(range.start)?;
1747 let project_path = ProjectPath {
1748 worktree_id: file.worktree_id(cx),
1749 path: file.path().clone(),
1750 };
1751 project.absolute_path(&project_path, cx)
1752 });
1753
1754 let line_range = file_path.as_ref().and_then(|_| {
1755 let (_, start_point, start_excerpt_id) = buffer.point_to_buffer_point(range.start)?;
1756 let (_, end_point, end_excerpt_id) = buffer.point_to_buffer_point(range.end)?;
1757 if start_excerpt_id == end_excerpt_id {
1758 Some(start_point.row..=end_point.row)
1759 } else {
1760 None
1761 }
1762 });
1763
1764 Self {
1765 len,
1766 is_entire_line,
1767 first_line_indent,
1768 file_path,
1769 line_range,
1770 }
1771 }
1772}
1773
1774// selections, scroll behavior, was newest selection reversed
1775type SelectSyntaxNodeHistoryState = (
1776 Box<[Selection<MultiBufferOffset>]>,
1777 SelectSyntaxNodeScrollBehavior,
1778 bool,
1779);
1780
1781#[derive(Default)]
1782struct SelectSyntaxNodeHistory {
1783 stack: Vec<SelectSyntaxNodeHistoryState>,
1784 // disable temporarily to allow changing selections without losing the stack
1785 pub disable_clearing: bool,
1786}
1787
1788impl SelectSyntaxNodeHistory {
1789 pub fn try_clear(&mut self) {
1790 if !self.disable_clearing {
1791 self.stack.clear();
1792 }
1793 }
1794
1795 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1796 self.stack.push(selection);
1797 }
1798
1799 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1800 self.stack.pop()
1801 }
1802}
1803
1804enum SelectSyntaxNodeScrollBehavior {
1805 CursorTop,
1806 FitSelection,
1807 CursorBottom,
1808}
1809
1810#[derive(Debug, Clone, Copy)]
1811pub(crate) struct NavigationData {
1812 cursor_anchor: Anchor,
1813 cursor_position: Point,
1814 scroll_anchor: ScrollAnchor,
1815 scroll_top_row: u32,
1816}
1817
1818#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1819pub enum GotoDefinitionKind {
1820 Symbol,
1821 Declaration,
1822 Type,
1823 Implementation,
1824}
1825
1826pub enum FormatTarget {
1827 Buffers(HashSet<Entity<Buffer>>),
1828 Ranges(Vec<Range<MultiBufferPoint>>),
1829}
1830
1831pub(crate) struct FocusedBlock {
1832 id: BlockId,
1833 focus_handle: WeakFocusHandle,
1834}
1835
1836#[derive(Clone, Debug)]
1837pub enum JumpData {
1838 MultiBufferRow {
1839 row: MultiBufferRow,
1840 line_offset_from_top: u32,
1841 },
1842 MultiBufferPoint {
1843 excerpt_id: ExcerptId,
1844 position: Point,
1845 anchor: text::Anchor,
1846 line_offset_from_top: u32,
1847 },
1848}
1849
1850pub enum MultibufferSelectionMode {
1851 First,
1852 All,
1853}
1854
1855#[derive(Clone, Copy, Debug, Default)]
1856pub struct RewrapOptions {
1857 pub override_language_settings: bool,
1858 pub preserve_existing_whitespace: bool,
1859}
1860
1861impl Editor {
1862 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1863 let buffer = cx.new(|cx| Buffer::local("", cx));
1864 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1865 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1866 }
1867
1868 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1869 let buffer = cx.new(|cx| Buffer::local("", cx));
1870 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1871 Self::new(EditorMode::full(), buffer, None, window, cx)
1872 }
1873
1874 pub fn auto_height(
1875 min_lines: usize,
1876 max_lines: usize,
1877 window: &mut Window,
1878 cx: &mut Context<Self>,
1879 ) -> Self {
1880 let buffer = cx.new(|cx| Buffer::local("", cx));
1881 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1882 Self::new(
1883 EditorMode::AutoHeight {
1884 min_lines,
1885 max_lines: Some(max_lines),
1886 },
1887 buffer,
1888 None,
1889 window,
1890 cx,
1891 )
1892 }
1893
1894 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1895 /// The editor grows as tall as needed to fit its content.
1896 pub fn auto_height_unbounded(
1897 min_lines: usize,
1898 window: &mut Window,
1899 cx: &mut Context<Self>,
1900 ) -> Self {
1901 let buffer = cx.new(|cx| Buffer::local("", cx));
1902 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1903 Self::new(
1904 EditorMode::AutoHeight {
1905 min_lines,
1906 max_lines: None,
1907 },
1908 buffer,
1909 None,
1910 window,
1911 cx,
1912 )
1913 }
1914
1915 pub fn for_buffer(
1916 buffer: Entity<Buffer>,
1917 project: Option<Entity<Project>>,
1918 window: &mut Window,
1919 cx: &mut Context<Self>,
1920 ) -> Self {
1921 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1922 Self::new(EditorMode::full(), buffer, project, window, cx)
1923 }
1924
1925 pub fn for_multibuffer(
1926 buffer: Entity<MultiBuffer>,
1927 project: Option<Entity<Project>>,
1928 window: &mut Window,
1929 cx: &mut Context<Self>,
1930 ) -> Self {
1931 Self::new(EditorMode::full(), buffer, project, window, cx)
1932 }
1933
1934 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1935 let mut clone = Self::new(
1936 self.mode.clone(),
1937 self.buffer.clone(),
1938 self.project.clone(),
1939 window,
1940 cx,
1941 );
1942 let my_snapshot = self.display_map.update(cx, |display_map, cx| {
1943 let snapshot = display_map.snapshot(cx);
1944 clone.display_map.update(cx, |display_map, cx| {
1945 display_map.set_state(&snapshot, cx);
1946 });
1947 snapshot
1948 });
1949 let clone_snapshot = clone.display_map.update(cx, |map, cx| map.snapshot(cx));
1950 clone.folds_did_change(cx);
1951 clone.selections.clone_state(&self.selections);
1952 clone
1953 .scroll_manager
1954 .clone_state(&self.scroll_manager, &my_snapshot, &clone_snapshot, cx);
1955 clone.searchable = self.searchable;
1956 clone.read_only = self.read_only;
1957 clone
1958 }
1959
1960 pub fn new(
1961 mode: EditorMode,
1962 buffer: Entity<MultiBuffer>,
1963 project: Option<Entity<Project>>,
1964 window: &mut Window,
1965 cx: &mut Context<Self>,
1966 ) -> Self {
1967 Editor::new_internal(mode, buffer, project, None, window, cx)
1968 }
1969
1970 pub fn sticky_headers(
1971 &self,
1972 display_snapshot: &DisplaySnapshot,
1973 style: &EditorStyle,
1974 cx: &App,
1975 ) -> Option<Vec<OutlineItem<Anchor>>> {
1976 let multi_buffer = self.buffer().read(cx);
1977 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
1978 let multi_buffer_visible_start = self
1979 .scroll_manager
1980 .native_anchor(display_snapshot, cx)
1981 .anchor
1982 .to_point(&multi_buffer_snapshot);
1983 let max_row = multi_buffer_snapshot.max_point().row;
1984
1985 let start_row = (multi_buffer_visible_start.row).min(max_row);
1986 let end_row = (multi_buffer_visible_start.row + 10).min(max_row);
1987
1988 if let Some((excerpt_id, _, buffer)) = multi_buffer.read(cx).as_singleton() {
1989 let outline_items = buffer
1990 .outline_items_containing(
1991 Point::new(start_row, 0)..Point::new(end_row, 0),
1992 true,
1993 Some(style.syntax.as_ref()),
1994 )
1995 .into_iter()
1996 .map(|outline_item| OutlineItem {
1997 depth: outline_item.depth,
1998 range: Anchor::range_in_buffer(*excerpt_id, outline_item.range),
1999 source_range_for_text: Anchor::range_in_buffer(
2000 *excerpt_id,
2001 outline_item.source_range_for_text,
2002 ),
2003 text: outline_item.text,
2004 highlight_ranges: outline_item.highlight_ranges,
2005 name_ranges: outline_item.name_ranges,
2006 body_range: outline_item
2007 .body_range
2008 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
2009 annotation_range: outline_item
2010 .annotation_range
2011 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
2012 });
2013 return Some(outline_items.collect());
2014 }
2015
2016 None
2017 }
2018
2019 fn new_internal(
2020 mode: EditorMode,
2021 multi_buffer: Entity<MultiBuffer>,
2022 project: Option<Entity<Project>>,
2023 display_map: Option<Entity<DisplayMap>>,
2024 window: &mut Window,
2025 cx: &mut Context<Self>,
2026 ) -> Self {
2027 debug_assert!(
2028 display_map.is_none() || mode.is_minimap(),
2029 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
2030 );
2031
2032 let full_mode = mode.is_full();
2033 let is_minimap = mode.is_minimap();
2034 let diagnostics_max_severity = if full_mode {
2035 EditorSettings::get_global(cx)
2036 .diagnostics_max_severity
2037 .unwrap_or(DiagnosticSeverity::Hint)
2038 } else {
2039 DiagnosticSeverity::Off
2040 };
2041 let style = window.text_style();
2042 let font_size = style.font_size.to_pixels(window.rem_size());
2043 let editor = cx.entity().downgrade();
2044 let fold_placeholder = FoldPlaceholder {
2045 constrain_width: false,
2046 render: Arc::new(move |fold_id, fold_range, cx| {
2047 let editor = editor.clone();
2048 div()
2049 .id(fold_id)
2050 .bg(cx.theme().colors().ghost_element_background)
2051 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
2052 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
2053 .rounded_xs()
2054 .size_full()
2055 .cursor_pointer()
2056 .child("⋯")
2057 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
2058 .on_click(move |_, _window, cx| {
2059 editor
2060 .update(cx, |editor, cx| {
2061 editor.unfold_ranges(
2062 &[fold_range.start..fold_range.end],
2063 true,
2064 false,
2065 cx,
2066 );
2067 cx.stop_propagation();
2068 })
2069 .ok();
2070 })
2071 .into_any()
2072 }),
2073 merge_adjacent: true,
2074 ..FoldPlaceholder::default()
2075 };
2076 let display_map = display_map.unwrap_or_else(|| {
2077 cx.new(|cx| {
2078 DisplayMap::new(
2079 multi_buffer.clone(),
2080 style.font(),
2081 font_size,
2082 None,
2083 FILE_HEADER_HEIGHT,
2084 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2085 fold_placeholder,
2086 diagnostics_max_severity,
2087 cx,
2088 )
2089 })
2090 });
2091
2092 let selections = SelectionsCollection::new();
2093
2094 let blink_manager = cx.new(|cx| {
2095 let mut blink_manager = BlinkManager::new(
2096 CURSOR_BLINK_INTERVAL,
2097 |cx| EditorSettings::get_global(cx).cursor_blink,
2098 cx,
2099 );
2100 if is_minimap {
2101 blink_manager.disable(cx);
2102 }
2103 blink_manager
2104 });
2105
2106 let soft_wrap_mode_override =
2107 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
2108
2109 let mut project_subscriptions = Vec::new();
2110 if full_mode && let Some(project) = project.as_ref() {
2111 project_subscriptions.push(cx.subscribe_in(
2112 project,
2113 window,
2114 |editor, _, event, window, cx| match event {
2115 project::Event::RefreshCodeLens => {
2116 // we always query lens with actions, without storing them, always refreshing them
2117 }
2118 project::Event::RefreshInlayHints {
2119 server_id,
2120 request_id,
2121 } => {
2122 editor.refresh_inlay_hints(
2123 InlayHintRefreshReason::RefreshRequested {
2124 server_id: *server_id,
2125 request_id: *request_id,
2126 },
2127 cx,
2128 );
2129 }
2130 project::Event::LanguageServerRemoved(..) => {
2131 if editor.tasks_update_task.is_none() {
2132 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2133 }
2134 editor.registered_buffers.clear();
2135 editor.register_visible_buffers(cx);
2136 }
2137 project::Event::LanguageServerAdded(..) => {
2138 if editor.tasks_update_task.is_none() {
2139 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2140 }
2141 }
2142 project::Event::SnippetEdit(id, snippet_edits) => {
2143 // todo(lw): Non singletons
2144 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
2145 let snapshot = buffer.read(cx).snapshot();
2146 let focus_handle = editor.focus_handle(cx);
2147 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
2148 for (range, snippet) in snippet_edits {
2149 let buffer_range =
2150 language::range_from_lsp(*range).to_offset(&snapshot);
2151 editor
2152 .insert_snippet(
2153 &[MultiBufferOffset(buffer_range.start)
2154 ..MultiBufferOffset(buffer_range.end)],
2155 snippet.clone(),
2156 window,
2157 cx,
2158 )
2159 .ok();
2160 }
2161 }
2162 }
2163 }
2164 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
2165 let buffer_id = *buffer_id;
2166 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
2167 editor.register_buffer(buffer_id, cx);
2168 editor.update_lsp_data(Some(buffer_id), window, cx);
2169 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
2170 refresh_linked_ranges(editor, window, cx);
2171 editor.refresh_code_actions(window, cx);
2172 editor.refresh_document_highlights(cx);
2173 }
2174 }
2175
2176 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
2177 let Some(workspace) = editor.workspace() else {
2178 return;
2179 };
2180 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2181 else {
2182 return;
2183 };
2184
2185 if active_editor.entity_id() == cx.entity_id() {
2186 let entity_id = cx.entity_id();
2187 workspace.update(cx, |this, cx| {
2188 this.panes_mut()
2189 .iter_mut()
2190 .filter(|pane| pane.entity_id() != entity_id)
2191 .for_each(|p| {
2192 p.update(cx, |pane, _| {
2193 pane.nav_history_mut().rename_item(
2194 entity_id,
2195 project_path.clone(),
2196 abs_path.clone().into(),
2197 );
2198 })
2199 });
2200 });
2201
2202 Self::open_transaction_for_hidden_buffers(
2203 workspace,
2204 transaction.clone(),
2205 "Rename".to_string(),
2206 window,
2207 cx,
2208 );
2209 }
2210 }
2211
2212 project::Event::WorkspaceEditApplied(transaction) => {
2213 let Some(workspace) = editor.workspace() else {
2214 return;
2215 };
2216 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2217 else {
2218 return;
2219 };
2220
2221 if active_editor.entity_id() == cx.entity_id() {
2222 Self::open_transaction_for_hidden_buffers(
2223 workspace,
2224 transaction.clone(),
2225 "LSP Edit".to_string(),
2226 window,
2227 cx,
2228 );
2229 }
2230 }
2231
2232 _ => {}
2233 },
2234 ));
2235 if let Some(task_inventory) = project
2236 .read(cx)
2237 .task_store()
2238 .read(cx)
2239 .task_inventory()
2240 .cloned()
2241 {
2242 project_subscriptions.push(cx.observe_in(
2243 &task_inventory,
2244 window,
2245 |editor, _, window, cx| {
2246 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2247 },
2248 ));
2249 };
2250
2251 project_subscriptions.push(cx.subscribe_in(
2252 &project.read(cx).breakpoint_store(),
2253 window,
2254 |editor, _, event, window, cx| match event {
2255 BreakpointStoreEvent::ClearDebugLines => {
2256 editor.clear_row_highlights::<ActiveDebugLine>();
2257 editor.refresh_inline_values(cx);
2258 }
2259 BreakpointStoreEvent::SetDebugLine => {
2260 if editor.go_to_active_debug_line(window, cx) {
2261 cx.stop_propagation();
2262 }
2263
2264 editor.refresh_inline_values(cx);
2265 }
2266 _ => {}
2267 },
2268 ));
2269 let git_store = project.read(cx).git_store().clone();
2270 let project = project.clone();
2271 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2272 if let GitStoreEvent::RepositoryAdded = event {
2273 this.load_diff_task = Some(
2274 update_uncommitted_diff_for_buffer(
2275 cx.entity(),
2276 &project,
2277 this.buffer.read(cx).all_buffers(),
2278 this.buffer.clone(),
2279 cx,
2280 )
2281 .shared(),
2282 );
2283 }
2284 }));
2285 }
2286
2287 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2288
2289 let inlay_hint_settings =
2290 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2291 let focus_handle = cx.focus_handle();
2292 if !is_minimap {
2293 cx.on_focus(&focus_handle, window, Self::handle_focus)
2294 .detach();
2295 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2296 .detach();
2297 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2298 .detach();
2299 cx.on_blur(&focus_handle, window, Self::handle_blur)
2300 .detach();
2301 cx.observe_pending_input(window, Self::observe_pending_input)
2302 .detach();
2303 }
2304
2305 let show_indent_guides =
2306 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2307 Some(false)
2308 } else {
2309 None
2310 };
2311
2312 let breakpoint_store = match (&mode, project.as_ref()) {
2313 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2314 _ => None,
2315 };
2316
2317 let mut code_action_providers = Vec::new();
2318 let mut load_uncommitted_diff = None;
2319 if let Some(project) = project.clone() {
2320 load_uncommitted_diff = Some(
2321 update_uncommitted_diff_for_buffer(
2322 cx.entity(),
2323 &project,
2324 multi_buffer.read(cx).all_buffers(),
2325 multi_buffer.clone(),
2326 cx,
2327 )
2328 .shared(),
2329 );
2330 code_action_providers.push(Rc::new(project) as Rc<_>);
2331 }
2332
2333 let mut editor = Self {
2334 focus_handle,
2335 show_cursor_when_unfocused: false,
2336 last_focused_descendant: None,
2337 buffer: multi_buffer.clone(),
2338 display_map: display_map.clone(),
2339 placeholder_display_map: None,
2340 selections,
2341 scroll_manager: ScrollManager::new(cx),
2342 columnar_selection_state: None,
2343 add_selections_state: None,
2344 select_next_state: None,
2345 select_prev_state: None,
2346 selection_history: SelectionHistory::default(),
2347 defer_selection_effects: false,
2348 deferred_selection_effects_state: None,
2349 autoclose_regions: Vec::new(),
2350 snippet_stack: InvalidationStack::default(),
2351 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2352 ime_transaction: None,
2353 active_diagnostics: ActiveDiagnostic::None,
2354 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2355 inline_diagnostics_update: Task::ready(()),
2356 inline_diagnostics: Vec::new(),
2357 soft_wrap_mode_override,
2358 diagnostics_max_severity,
2359 hard_wrap: None,
2360 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2361 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2362 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2363 project,
2364 blink_manager: blink_manager.clone(),
2365 show_local_selections: true,
2366 show_scrollbars: ScrollbarAxes {
2367 horizontal: full_mode,
2368 vertical: full_mode,
2369 },
2370 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2371 offset_content: !matches!(mode, EditorMode::SingleLine),
2372 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2373 show_gutter: full_mode,
2374 show_line_numbers: (!full_mode).then_some(false),
2375 use_relative_line_numbers: None,
2376 disable_expand_excerpt_buttons: !full_mode,
2377 delegate_expand_excerpts: false,
2378 delegate_stage_and_restore: false,
2379 show_git_diff_gutter: None,
2380 show_code_actions: None,
2381 show_runnables: None,
2382 show_breakpoints: None,
2383 show_diff_review_button: false,
2384 show_wrap_guides: None,
2385 show_indent_guides,
2386 buffers_with_disabled_indent_guides: HashSet::default(),
2387 highlight_order: 0,
2388 highlighted_rows: HashMap::default(),
2389 background_highlights: HashMap::default(),
2390 gutter_highlights: HashMap::default(),
2391 scrollbar_marker_state: ScrollbarMarkerState::default(),
2392 active_indent_guides_state: ActiveIndentGuidesState::default(),
2393 nav_history: None,
2394 context_menu: RefCell::new(None),
2395 context_menu_options: None,
2396 mouse_context_menu: None,
2397 completion_tasks: Vec::new(),
2398 inline_blame_popover: None,
2399 inline_blame_popover_show_task: None,
2400 signature_help_state: SignatureHelpState::default(),
2401 auto_signature_help: None,
2402 find_all_references_task_sources: Vec::new(),
2403 next_completion_id: 0,
2404 next_inlay_id: 0,
2405 code_action_providers,
2406 available_code_actions: None,
2407 code_actions_task: None,
2408 quick_selection_highlight_task: None,
2409 debounced_selection_highlight_task: None,
2410 debounced_selection_highlight_complete: false,
2411 document_highlights_task: None,
2412 linked_editing_range_task: None,
2413 pending_rename: None,
2414 searchable: !is_minimap,
2415 cursor_shape: EditorSettings::get_global(cx)
2416 .cursor_shape
2417 .unwrap_or_default(),
2418 cursor_offset_on_selection: false,
2419 current_line_highlight: None,
2420 autoindent_mode: Some(AutoindentMode::EachLine),
2421 collapse_matches: false,
2422 workspace: None,
2423 input_enabled: !is_minimap,
2424 use_modal_editing: full_mode,
2425 read_only: is_minimap,
2426 use_autoclose: true,
2427 use_auto_surround: true,
2428 auto_replace_emoji_shortcode: false,
2429 jsx_tag_auto_close_enabled_in_any_buffer: false,
2430 leader_id: None,
2431 remote_id: None,
2432 hover_state: HoverState::default(),
2433 pending_mouse_down: None,
2434 prev_pressure_stage: None,
2435 hovered_link_state: None,
2436 edit_prediction_provider: None,
2437 active_edit_prediction: None,
2438 stale_edit_prediction_in_menu: None,
2439 edit_prediction_preview: EditPredictionPreview::Inactive {
2440 released_too_fast: false,
2441 },
2442 inline_diagnostics_enabled: full_mode,
2443 diagnostics_enabled: full_mode,
2444 word_completions_enabled: full_mode,
2445 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2446 gutter_hovered: false,
2447 pixel_position_of_newest_cursor: None,
2448 last_bounds: None,
2449 last_position_map: None,
2450 expect_bounds_change: None,
2451 gutter_dimensions: GutterDimensions::default(),
2452 style: None,
2453 show_cursor_names: false,
2454 hovered_cursors: HashMap::default(),
2455 next_editor_action_id: EditorActionId::default(),
2456 editor_actions: Rc::default(),
2457 edit_predictions_hidden_for_vim_mode: false,
2458 show_edit_predictions_override: None,
2459 show_completions_on_input_override: None,
2460 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2461 edit_prediction_settings: EditPredictionSettings::Disabled,
2462 edit_prediction_indent_conflict: false,
2463 edit_prediction_requires_modifier_in_indent_conflict: true,
2464 custom_context_menu: None,
2465 show_git_blame_gutter: false,
2466 show_git_blame_inline: false,
2467 show_selection_menu: None,
2468 show_git_blame_inline_delay_task: None,
2469 git_blame_inline_enabled: full_mode
2470 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2471 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2472 buffer_serialization: is_minimap.not().then(|| {
2473 BufferSerialization::new(
2474 ProjectSettings::get_global(cx)
2475 .session
2476 .restore_unsaved_buffers,
2477 )
2478 }),
2479 blame: None,
2480 blame_subscription: None,
2481 tasks: BTreeMap::default(),
2482
2483 breakpoint_store,
2484 gutter_breakpoint_indicator: (None, None),
2485 gutter_diff_review_indicator: (None, None),
2486 diff_review_drag_state: None,
2487 diff_review_overlays: Vec::new(),
2488 stored_review_comments: Vec::new(),
2489 next_review_comment_id: 0,
2490 hovered_diff_hunk_row: None,
2491 _subscriptions: (!is_minimap)
2492 .then(|| {
2493 vec![
2494 cx.observe(&multi_buffer, Self::on_buffer_changed),
2495 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2496 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2497 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2498 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2499 cx.observe_global_in::<GlobalTheme>(window, Self::theme_changed),
2500 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2501 cx.observe_window_activation(window, |editor, window, cx| {
2502 let active = window.is_window_active();
2503 editor.blink_manager.update(cx, |blink_manager, cx| {
2504 if active {
2505 blink_manager.enable(cx);
2506 } else {
2507 blink_manager.disable(cx);
2508 }
2509 });
2510 if active {
2511 editor.show_mouse_cursor(cx);
2512 }
2513 }),
2514 ]
2515 })
2516 .unwrap_or_default(),
2517 tasks_update_task: None,
2518 pull_diagnostics_task: Task::ready(()),
2519 colors: None,
2520 refresh_colors_task: Task::ready(()),
2521 inlay_hints: None,
2522 next_color_inlay_id: 0,
2523 post_scroll_update: Task::ready(()),
2524 linked_edit_ranges: Default::default(),
2525 in_project_search: false,
2526 previous_search_ranges: None,
2527 breadcrumb_header: None,
2528 focused_block: None,
2529 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2530 addons: HashMap::default(),
2531 registered_buffers: HashMap::default(),
2532 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2533 selection_mark_mode: false,
2534 toggle_fold_multiple_buffers: Task::ready(()),
2535 serialize_selections: Task::ready(()),
2536 serialize_folds: Task::ready(()),
2537 text_style_refinement: None,
2538 load_diff_task: load_uncommitted_diff,
2539 temporary_diff_override: false,
2540 mouse_cursor_hidden: false,
2541 minimap: None,
2542 hide_mouse_mode: EditorSettings::get_global(cx)
2543 .hide_mouse
2544 .unwrap_or_default(),
2545 change_list: ChangeList::new(),
2546 mode,
2547 selection_drag_state: SelectionDragState::None,
2548 folding_newlines: Task::ready(()),
2549 lookup_key: None,
2550 select_next_is_case_sensitive: None,
2551 on_local_selections_changed: None,
2552 suppress_selection_callback: false,
2553 applicable_language_settings: HashMap::default(),
2554 accent_data: None,
2555 fetched_tree_sitter_chunks: HashMap::default(),
2556 number_deleted_lines: false,
2557 };
2558
2559 if is_minimap {
2560 return editor;
2561 }
2562
2563 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2564 editor.accent_data = editor.fetch_accent_data(cx);
2565
2566 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2567 editor
2568 ._subscriptions
2569 .push(cx.observe(breakpoints, |_, _, cx| {
2570 cx.notify();
2571 }));
2572 }
2573 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2574 editor._subscriptions.extend(project_subscriptions);
2575
2576 editor._subscriptions.push(cx.subscribe_in(
2577 &cx.entity(),
2578 window,
2579 |editor, _, e: &EditorEvent, window, cx| match e {
2580 EditorEvent::ScrollPositionChanged { local, .. } => {
2581 if *local {
2582 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2583 editor.inline_blame_popover.take();
2584 let snapshot = editor.snapshot(window, cx);
2585 let new_anchor = editor
2586 .scroll_manager
2587 .native_anchor(&snapshot.display_snapshot, cx);
2588 editor.update_restoration_data(cx, move |data| {
2589 data.scroll_position = (
2590 new_anchor.top_row(snapshot.buffer_snapshot()),
2591 new_anchor.offset,
2592 );
2593 });
2594
2595 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2596 cx.background_executor()
2597 .timer(Duration::from_millis(50))
2598 .await;
2599 editor
2600 .update_in(cx, |editor, window, cx| {
2601 editor.register_visible_buffers(cx);
2602 editor.refresh_colors_for_visible_range(None, window, cx);
2603 editor.refresh_inlay_hints(
2604 InlayHintRefreshReason::NewLinesShown,
2605 cx,
2606 );
2607 editor.colorize_brackets(false, cx);
2608 })
2609 .ok();
2610 });
2611 }
2612 }
2613 EditorEvent::Edited { .. } => {
2614 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2615 .map(|vim_mode| vim_mode.0)
2616 .unwrap_or(false);
2617 if !vim_mode {
2618 let display_map = editor.display_snapshot(cx);
2619 let selections = editor.selections.all_adjusted_display(&display_map);
2620 let pop_state = editor
2621 .change_list
2622 .last()
2623 .map(|previous| {
2624 previous.len() == selections.len()
2625 && previous.iter().enumerate().all(|(ix, p)| {
2626 p.to_display_point(&display_map).row()
2627 == selections[ix].head().row()
2628 })
2629 })
2630 .unwrap_or(false);
2631 let new_positions = selections
2632 .into_iter()
2633 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2634 .collect();
2635 editor
2636 .change_list
2637 .push_to_change_list(pop_state, new_positions);
2638 }
2639 }
2640 _ => (),
2641 },
2642 ));
2643
2644 if let Some(dap_store) = editor
2645 .project
2646 .as_ref()
2647 .map(|project| project.read(cx).dap_store())
2648 {
2649 let weak_editor = cx.weak_entity();
2650
2651 editor
2652 ._subscriptions
2653 .push(
2654 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2655 let session_entity = cx.entity();
2656 weak_editor
2657 .update(cx, |editor, cx| {
2658 editor._subscriptions.push(
2659 cx.subscribe(&session_entity, Self::on_debug_session_event),
2660 );
2661 })
2662 .ok();
2663 }),
2664 );
2665
2666 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2667 editor
2668 ._subscriptions
2669 .push(cx.subscribe(&session, Self::on_debug_session_event));
2670 }
2671 }
2672
2673 // skip adding the initial selection to selection history
2674 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2675 editor.end_selection(window, cx);
2676 editor.selection_history.mode = SelectionHistoryMode::Normal;
2677
2678 editor.scroll_manager.show_scrollbars(window, cx);
2679 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2680
2681 if full_mode {
2682 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2683 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2684
2685 if editor.git_blame_inline_enabled {
2686 editor.start_git_blame_inline(false, window, cx);
2687 }
2688
2689 editor.go_to_active_debug_line(window, cx);
2690
2691 editor.minimap =
2692 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2693 editor.colors = Some(LspColorData::new(cx));
2694 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2695
2696 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2697 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2698 }
2699 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2700 }
2701
2702 editor
2703 }
2704
2705 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2706 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2707 }
2708
2709 pub fn deploy_mouse_context_menu(
2710 &mut self,
2711 position: gpui::Point<Pixels>,
2712 context_menu: Entity<ContextMenu>,
2713 window: &mut Window,
2714 cx: &mut Context<Self>,
2715 ) {
2716 self.mouse_context_menu = Some(MouseContextMenu::new(
2717 self,
2718 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2719 context_menu,
2720 window,
2721 cx,
2722 ));
2723 }
2724
2725 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2726 self.mouse_context_menu
2727 .as_ref()
2728 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2729 }
2730
2731 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2732 if self
2733 .selections
2734 .pending_anchor()
2735 .is_some_and(|pending_selection| {
2736 let snapshot = self.buffer().read(cx).snapshot(cx);
2737 pending_selection.range().includes(range, &snapshot)
2738 })
2739 {
2740 return true;
2741 }
2742
2743 self.selections
2744 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2745 .into_iter()
2746 .any(|selection| {
2747 // This is needed to cover a corner case, if we just check for an existing
2748 // selection in the fold range, having a cursor at the start of the fold
2749 // marks it as selected. Non-empty selections don't cause this.
2750 let length = selection.end - selection.start;
2751 length > 0
2752 })
2753 }
2754
2755 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2756 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2757 }
2758
2759 fn key_context_internal(
2760 &self,
2761 has_active_edit_prediction: bool,
2762 window: &mut Window,
2763 cx: &mut App,
2764 ) -> KeyContext {
2765 let mut key_context = KeyContext::new_with_defaults();
2766 key_context.add("Editor");
2767 let mode = match self.mode {
2768 EditorMode::SingleLine => "single_line",
2769 EditorMode::AutoHeight { .. } => "auto_height",
2770 EditorMode::Minimap { .. } => "minimap",
2771 EditorMode::Full { .. } => "full",
2772 };
2773
2774 if EditorSettings::jupyter_enabled(cx) {
2775 key_context.add("jupyter");
2776 }
2777
2778 key_context.set("mode", mode);
2779 if self.pending_rename.is_some() {
2780 key_context.add("renaming");
2781 }
2782
2783 if let Some(snippet_stack) = self.snippet_stack.last() {
2784 key_context.add("in_snippet");
2785
2786 if snippet_stack.active_index > 0 {
2787 key_context.add("has_previous_tabstop");
2788 }
2789
2790 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2791 key_context.add("has_next_tabstop");
2792 }
2793 }
2794
2795 match self.context_menu.borrow().as_ref() {
2796 Some(CodeContextMenu::Completions(menu)) => {
2797 if menu.visible() {
2798 key_context.add("menu");
2799 key_context.add("showing_completions");
2800 }
2801 }
2802 Some(CodeContextMenu::CodeActions(menu)) => {
2803 if menu.visible() {
2804 key_context.add("menu");
2805 key_context.add("showing_code_actions")
2806 }
2807 }
2808 None => {}
2809 }
2810
2811 if self.signature_help_state.has_multiple_signatures() {
2812 key_context.add("showing_signature_help");
2813 }
2814
2815 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2816 if !self.focus_handle(cx).contains_focused(window, cx)
2817 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2818 {
2819 for addon in self.addons.values() {
2820 addon.extend_key_context(&mut key_context, cx)
2821 }
2822 }
2823
2824 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2825 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2826 Some(
2827 file.full_path(cx)
2828 .extension()?
2829 .to_string_lossy()
2830 .to_lowercase(),
2831 )
2832 }) {
2833 key_context.set("extension", extension);
2834 }
2835 } else {
2836 key_context.add("multibuffer");
2837 }
2838
2839 if has_active_edit_prediction {
2840 if self.edit_prediction_in_conflict() {
2841 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2842 } else {
2843 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2844 key_context.add("copilot_suggestion");
2845 }
2846 }
2847
2848 if self.selection_mark_mode {
2849 key_context.add("selection_mode");
2850 }
2851
2852 let disjoint = self.selections.disjoint_anchors();
2853 let snapshot = self.snapshot(window, cx);
2854 let snapshot = snapshot.buffer_snapshot();
2855 if self.mode == EditorMode::SingleLine
2856 && let [selection] = disjoint
2857 && selection.start == selection.end
2858 && selection.end.to_offset(snapshot) == snapshot.len()
2859 {
2860 key_context.add("end_of_input");
2861 }
2862
2863 if self.has_any_expanded_diff_hunks(cx) {
2864 key_context.add("diffs_expanded");
2865 }
2866
2867 key_context
2868 }
2869
2870 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2871 self.last_bounds.as_ref()
2872 }
2873
2874 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2875 if self.mouse_cursor_hidden {
2876 self.mouse_cursor_hidden = false;
2877 cx.notify();
2878 }
2879 }
2880
2881 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2882 let hide_mouse_cursor = match origin {
2883 HideMouseCursorOrigin::TypingAction => {
2884 matches!(
2885 self.hide_mouse_mode,
2886 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2887 )
2888 }
2889 HideMouseCursorOrigin::MovementAction => {
2890 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2891 }
2892 };
2893 if self.mouse_cursor_hidden != hide_mouse_cursor {
2894 self.mouse_cursor_hidden = hide_mouse_cursor;
2895 cx.notify();
2896 }
2897 }
2898
2899 pub fn edit_prediction_in_conflict(&self) -> bool {
2900 if !self.show_edit_predictions_in_menu() {
2901 return false;
2902 }
2903
2904 let showing_completions = self
2905 .context_menu
2906 .borrow()
2907 .as_ref()
2908 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2909
2910 showing_completions
2911 || self.edit_prediction_requires_modifier()
2912 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2913 // bindings to insert tab characters.
2914 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2915 }
2916
2917 pub fn accept_edit_prediction_keybind(
2918 &self,
2919 granularity: EditPredictionGranularity,
2920 window: &mut Window,
2921 cx: &mut App,
2922 ) -> AcceptEditPredictionBinding {
2923 let key_context = self.key_context_internal(true, window, cx);
2924 let in_conflict = self.edit_prediction_in_conflict();
2925
2926 let bindings =
2927 match granularity {
2928 EditPredictionGranularity::Word => window
2929 .bindings_for_action_in_context(&AcceptNextWordEditPrediction, key_context),
2930 EditPredictionGranularity::Line => window
2931 .bindings_for_action_in_context(&AcceptNextLineEditPrediction, key_context),
2932 EditPredictionGranularity::Full => {
2933 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2934 }
2935 };
2936
2937 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2938 !in_conflict
2939 || binding
2940 .keystrokes()
2941 .first()
2942 .is_some_and(|keystroke| keystroke.modifiers().modified())
2943 }))
2944 }
2945
2946 pub fn new_file(
2947 workspace: &mut Workspace,
2948 _: &workspace::NewFile,
2949 window: &mut Window,
2950 cx: &mut Context<Workspace>,
2951 ) {
2952 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2953 "Failed to create buffer",
2954 window,
2955 cx,
2956 |e, _, _| match e.error_code() {
2957 ErrorCode::RemoteUpgradeRequired => Some(format!(
2958 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2959 e.error_tag("required").unwrap_or("the latest version")
2960 )),
2961 _ => None,
2962 },
2963 );
2964 }
2965
2966 pub fn new_in_workspace(
2967 workspace: &mut Workspace,
2968 window: &mut Window,
2969 cx: &mut Context<Workspace>,
2970 ) -> Task<Result<Entity<Editor>>> {
2971 let project = workspace.project().clone();
2972 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
2973
2974 cx.spawn_in(window, async move |workspace, cx| {
2975 let buffer = create.await?;
2976 workspace.update_in(cx, |workspace, window, cx| {
2977 let editor =
2978 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2979 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2980 editor
2981 })
2982 })
2983 }
2984
2985 fn new_file_vertical(
2986 workspace: &mut Workspace,
2987 _: &workspace::NewFileSplitVertical,
2988 window: &mut Window,
2989 cx: &mut Context<Workspace>,
2990 ) {
2991 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2992 }
2993
2994 fn new_file_horizontal(
2995 workspace: &mut Workspace,
2996 _: &workspace::NewFileSplitHorizontal,
2997 window: &mut Window,
2998 cx: &mut Context<Workspace>,
2999 ) {
3000 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
3001 }
3002
3003 fn new_file_split(
3004 workspace: &mut Workspace,
3005 action: &workspace::NewFileSplit,
3006 window: &mut Window,
3007 cx: &mut Context<Workspace>,
3008 ) {
3009 Self::new_file_in_direction(workspace, action.0, window, cx)
3010 }
3011
3012 fn new_file_in_direction(
3013 workspace: &mut Workspace,
3014 direction: SplitDirection,
3015 window: &mut Window,
3016 cx: &mut Context<Workspace>,
3017 ) {
3018 let project = workspace.project().clone();
3019 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
3020
3021 cx.spawn_in(window, async move |workspace, cx| {
3022 let buffer = create.await?;
3023 workspace.update_in(cx, move |workspace, window, cx| {
3024 workspace.split_item(
3025 direction,
3026 Box::new(
3027 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
3028 ),
3029 window,
3030 cx,
3031 )
3032 })?;
3033 anyhow::Ok(())
3034 })
3035 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
3036 match e.error_code() {
3037 ErrorCode::RemoteUpgradeRequired => Some(format!(
3038 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
3039 e.error_tag("required").unwrap_or("the latest version")
3040 )),
3041 _ => None,
3042 }
3043 });
3044 }
3045
3046 pub fn leader_id(&self) -> Option<CollaboratorId> {
3047 self.leader_id
3048 }
3049
3050 pub fn buffer(&self) -> &Entity<MultiBuffer> {
3051 &self.buffer
3052 }
3053
3054 pub fn project(&self) -> Option<&Entity<Project>> {
3055 self.project.as_ref()
3056 }
3057
3058 pub fn workspace(&self) -> Option<Entity<Workspace>> {
3059 self.workspace.as_ref()?.0.upgrade()
3060 }
3061
3062 /// Returns the workspace serialization ID if this editor should be serialized.
3063 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
3064 self.workspace
3065 .as_ref()
3066 .filter(|_| self.should_serialize_buffer())
3067 .and_then(|workspace| workspace.1)
3068 }
3069
3070 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
3071 self.buffer().read(cx).title(cx)
3072 }
3073
3074 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
3075 let git_blame_gutter_max_author_length = self
3076 .render_git_blame_gutter(cx)
3077 .then(|| {
3078 if let Some(blame) = self.blame.as_ref() {
3079 let max_author_length =
3080 blame.update(cx, |blame, cx| blame.max_author_length(cx));
3081 Some(max_author_length)
3082 } else {
3083 None
3084 }
3085 })
3086 .flatten();
3087
3088 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3089
3090 EditorSnapshot {
3091 mode: self.mode.clone(),
3092 show_gutter: self.show_gutter,
3093 offset_content: self.offset_content,
3094 show_line_numbers: self.show_line_numbers,
3095 number_deleted_lines: self.number_deleted_lines,
3096 show_git_diff_gutter: self.show_git_diff_gutter,
3097 show_code_actions: self.show_code_actions,
3098 show_runnables: self.show_runnables,
3099 show_breakpoints: self.show_breakpoints,
3100 git_blame_gutter_max_author_length,
3101 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
3102 display_snapshot,
3103 placeholder_display_snapshot: self
3104 .placeholder_display_map
3105 .as_ref()
3106 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
3107 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
3108 is_focused: self.focus_handle.is_focused(window),
3109 current_line_highlight: self
3110 .current_line_highlight
3111 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
3112 gutter_hovered: self.gutter_hovered,
3113 }
3114 }
3115
3116 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
3117 self.buffer.read(cx).language_at(point, cx)
3118 }
3119
3120 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
3121 self.buffer.read(cx).read(cx).file_at(point).cloned()
3122 }
3123
3124 pub fn active_excerpt(
3125 &self,
3126 cx: &App,
3127 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
3128 self.buffer
3129 .read(cx)
3130 .excerpt_containing(self.selections.newest_anchor().head(), cx)
3131 }
3132
3133 pub fn mode(&self) -> &EditorMode {
3134 &self.mode
3135 }
3136
3137 pub fn set_mode(&mut self, mode: EditorMode) {
3138 self.mode = mode;
3139 }
3140
3141 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
3142 self.collaboration_hub.as_deref()
3143 }
3144
3145 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
3146 self.collaboration_hub = Some(hub);
3147 }
3148
3149 pub fn set_in_project_search(&mut self, in_project_search: bool) {
3150 self.in_project_search = in_project_search;
3151 }
3152
3153 pub fn set_custom_context_menu(
3154 &mut self,
3155 f: impl 'static
3156 + Fn(
3157 &mut Self,
3158 DisplayPoint,
3159 &mut Window,
3160 &mut Context<Self>,
3161 ) -> Option<Entity<ui::ContextMenu>>,
3162 ) {
3163 self.custom_context_menu = Some(Box::new(f))
3164 }
3165
3166 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
3167 self.completion_provider = provider;
3168 }
3169
3170 #[cfg(any(test, feature = "test-support"))]
3171 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
3172 self.completion_provider.clone()
3173 }
3174
3175 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
3176 self.semantics_provider.clone()
3177 }
3178
3179 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
3180 self.semantics_provider = provider;
3181 }
3182
3183 pub fn set_edit_prediction_provider<T>(
3184 &mut self,
3185 provider: Option<Entity<T>>,
3186 window: &mut Window,
3187 cx: &mut Context<Self>,
3188 ) where
3189 T: EditPredictionDelegate,
3190 {
3191 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionDelegate {
3192 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
3193 if this.focus_handle.is_focused(window) {
3194 this.update_visible_edit_prediction(window, cx);
3195 }
3196 }),
3197 provider: Arc::new(provider),
3198 });
3199 self.update_edit_prediction_settings(cx);
3200 self.refresh_edit_prediction(false, false, window, cx);
3201 }
3202
3203 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
3204 self.placeholder_display_map
3205 .as_ref()
3206 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
3207 }
3208
3209 pub fn set_placeholder_text(
3210 &mut self,
3211 placeholder_text: &str,
3212 window: &mut Window,
3213 cx: &mut Context<Self>,
3214 ) {
3215 let multibuffer = cx
3216 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3217
3218 let style = window.text_style();
3219
3220 self.placeholder_display_map = Some(cx.new(|cx| {
3221 DisplayMap::new(
3222 multibuffer,
3223 style.font(),
3224 style.font_size.to_pixels(window.rem_size()),
3225 None,
3226 FILE_HEADER_HEIGHT,
3227 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3228 Default::default(),
3229 DiagnosticSeverity::Off,
3230 cx,
3231 )
3232 }));
3233 cx.notify();
3234 }
3235
3236 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3237 self.cursor_shape = cursor_shape;
3238
3239 // Disrupt blink for immediate user feedback that the cursor shape has changed
3240 self.blink_manager.update(cx, BlinkManager::show_cursor);
3241
3242 cx.notify();
3243 }
3244
3245 pub fn cursor_shape(&self) -> CursorShape {
3246 self.cursor_shape
3247 }
3248
3249 pub fn set_cursor_offset_on_selection(&mut self, set_cursor_offset_on_selection: bool) {
3250 self.cursor_offset_on_selection = set_cursor_offset_on_selection;
3251 }
3252
3253 pub fn set_current_line_highlight(
3254 &mut self,
3255 current_line_highlight: Option<CurrentLineHighlight>,
3256 ) {
3257 self.current_line_highlight = current_line_highlight;
3258 }
3259
3260 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3261 self.collapse_matches = collapse_matches;
3262 }
3263
3264 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3265 if self.collapse_matches {
3266 return range.start..range.start;
3267 }
3268 range.clone()
3269 }
3270
3271 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3272 self.display_map.read(cx).clip_at_line_ends
3273 }
3274
3275 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3276 if self.display_map.read(cx).clip_at_line_ends != clip {
3277 self.display_map
3278 .update(cx, |map, _| map.clip_at_line_ends = clip);
3279 }
3280 }
3281
3282 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3283 self.input_enabled = input_enabled;
3284 }
3285
3286 pub fn set_edit_predictions_hidden_for_vim_mode(
3287 &mut self,
3288 hidden: bool,
3289 window: &mut Window,
3290 cx: &mut Context<Self>,
3291 ) {
3292 if hidden != self.edit_predictions_hidden_for_vim_mode {
3293 self.edit_predictions_hidden_for_vim_mode = hidden;
3294 if hidden {
3295 self.update_visible_edit_prediction(window, cx);
3296 } else {
3297 self.refresh_edit_prediction(true, false, window, cx);
3298 }
3299 }
3300 }
3301
3302 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3303 self.menu_edit_predictions_policy = value;
3304 }
3305
3306 pub fn set_autoindent(&mut self, autoindent: bool) {
3307 if autoindent {
3308 self.autoindent_mode = Some(AutoindentMode::EachLine);
3309 } else {
3310 self.autoindent_mode = None;
3311 }
3312 }
3313
3314 pub fn capability(&self, cx: &App) -> Capability {
3315 if self.read_only {
3316 Capability::ReadOnly
3317 } else {
3318 self.buffer.read(cx).capability()
3319 }
3320 }
3321
3322 pub fn read_only(&self, cx: &App) -> bool {
3323 self.read_only || self.buffer.read(cx).read_only()
3324 }
3325
3326 pub fn set_read_only(&mut self, read_only: bool) {
3327 self.read_only = read_only;
3328 }
3329
3330 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3331 self.use_autoclose = autoclose;
3332 }
3333
3334 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3335 self.use_auto_surround = auto_surround;
3336 }
3337
3338 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3339 self.auto_replace_emoji_shortcode = auto_replace;
3340 }
3341
3342 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3343 self.buffer_serialization = should_serialize.then(|| {
3344 BufferSerialization::new(
3345 ProjectSettings::get_global(cx)
3346 .session
3347 .restore_unsaved_buffers,
3348 )
3349 })
3350 }
3351
3352 fn should_serialize_buffer(&self) -> bool {
3353 self.buffer_serialization.is_some()
3354 }
3355
3356 pub fn toggle_edit_predictions(
3357 &mut self,
3358 _: &ToggleEditPrediction,
3359 window: &mut Window,
3360 cx: &mut Context<Self>,
3361 ) {
3362 if self.show_edit_predictions_override.is_some() {
3363 self.set_show_edit_predictions(None, window, cx);
3364 } else {
3365 let show_edit_predictions = !self.edit_predictions_enabled();
3366 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3367 }
3368 }
3369
3370 pub fn set_show_completions_on_input(&mut self, show_completions_on_input: Option<bool>) {
3371 self.show_completions_on_input_override = show_completions_on_input;
3372 }
3373
3374 pub fn set_show_edit_predictions(
3375 &mut self,
3376 show_edit_predictions: Option<bool>,
3377 window: &mut Window,
3378 cx: &mut Context<Self>,
3379 ) {
3380 self.show_edit_predictions_override = show_edit_predictions;
3381 self.update_edit_prediction_settings(cx);
3382
3383 if let Some(false) = show_edit_predictions {
3384 self.discard_edit_prediction(false, cx);
3385 } else {
3386 self.refresh_edit_prediction(false, true, window, cx);
3387 }
3388 }
3389
3390 fn edit_predictions_disabled_in_scope(
3391 &self,
3392 buffer: &Entity<Buffer>,
3393 buffer_position: language::Anchor,
3394 cx: &App,
3395 ) -> bool {
3396 let snapshot = buffer.read(cx).snapshot();
3397 let settings = snapshot.settings_at(buffer_position, cx);
3398
3399 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3400 return false;
3401 };
3402
3403 scope.override_name().is_some_and(|scope_name| {
3404 settings
3405 .edit_predictions_disabled_in
3406 .iter()
3407 .any(|s| s == scope_name)
3408 })
3409 }
3410
3411 pub fn set_use_modal_editing(&mut self, to: bool) {
3412 self.use_modal_editing = to;
3413 }
3414
3415 pub fn use_modal_editing(&self) -> bool {
3416 self.use_modal_editing
3417 }
3418
3419 fn selections_did_change(
3420 &mut self,
3421 local: bool,
3422 old_cursor_position: &Anchor,
3423 effects: SelectionEffects,
3424 window: &mut Window,
3425 cx: &mut Context<Self>,
3426 ) {
3427 window.invalidate_character_coordinates();
3428
3429 // Copy selections to primary selection buffer
3430 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3431 if local {
3432 let selections = self
3433 .selections
3434 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3435 let buffer_handle = self.buffer.read(cx).read(cx);
3436
3437 let mut text = String::new();
3438 for (index, selection) in selections.iter().enumerate() {
3439 let text_for_selection = buffer_handle
3440 .text_for_range(selection.start..selection.end)
3441 .collect::<String>();
3442
3443 text.push_str(&text_for_selection);
3444 if index != selections.len() - 1 {
3445 text.push('\n');
3446 }
3447 }
3448
3449 if !text.is_empty() {
3450 cx.write_to_primary(ClipboardItem::new_string(text));
3451 }
3452 }
3453
3454 let selection_anchors = self.selections.disjoint_anchors_arc();
3455
3456 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3457 self.buffer.update(cx, |buffer, cx| {
3458 buffer.set_active_selections(
3459 &selection_anchors,
3460 self.selections.line_mode(),
3461 self.cursor_shape,
3462 cx,
3463 )
3464 });
3465 }
3466 let display_map = self
3467 .display_map
3468 .update(cx, |display_map, cx| display_map.snapshot(cx));
3469 let buffer = display_map.buffer_snapshot();
3470 if self.selections.count() == 1 {
3471 self.add_selections_state = None;
3472 }
3473 self.select_next_state = None;
3474 self.select_prev_state = None;
3475 self.select_syntax_node_history.try_clear();
3476 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3477 self.snippet_stack.invalidate(&selection_anchors, buffer);
3478 self.take_rename(false, window, cx);
3479
3480 let newest_selection = self.selections.newest_anchor();
3481 let new_cursor_position = newest_selection.head();
3482 let selection_start = newest_selection.start;
3483
3484 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3485 self.push_to_nav_history(
3486 *old_cursor_position,
3487 Some(new_cursor_position.to_point(buffer)),
3488 false,
3489 effects.nav_history == Some(true),
3490 cx,
3491 );
3492 }
3493
3494 if local {
3495 if let Some(buffer_id) = new_cursor_position.text_anchor.buffer_id {
3496 self.register_buffer(buffer_id, cx);
3497 }
3498
3499 let mut context_menu = self.context_menu.borrow_mut();
3500 let completion_menu = match context_menu.as_ref() {
3501 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3502 Some(CodeContextMenu::CodeActions(_)) => {
3503 *context_menu = None;
3504 None
3505 }
3506 None => None,
3507 };
3508 let completion_position = completion_menu.map(|menu| menu.initial_position);
3509 drop(context_menu);
3510
3511 if effects.completions
3512 && let Some(completion_position) = completion_position
3513 {
3514 let start_offset = selection_start.to_offset(buffer);
3515 let position_matches = start_offset == completion_position.to_offset(buffer);
3516 let continue_showing = if let Some((snap, ..)) =
3517 buffer.point_to_buffer_offset(completion_position)
3518 && !snap.capability.editable()
3519 {
3520 false
3521 } else if position_matches {
3522 if self.snippet_stack.is_empty() {
3523 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3524 == Some(CharKind::Word)
3525 } else {
3526 // Snippet choices can be shown even when the cursor is in whitespace.
3527 // Dismissing the menu with actions like backspace is handled by
3528 // invalidation regions.
3529 true
3530 }
3531 } else {
3532 false
3533 };
3534
3535 if continue_showing {
3536 self.open_or_update_completions_menu(None, None, false, window, cx);
3537 } else {
3538 self.hide_context_menu(window, cx);
3539 }
3540 }
3541
3542 hide_hover(self, cx);
3543
3544 if old_cursor_position.to_display_point(&display_map).row()
3545 != new_cursor_position.to_display_point(&display_map).row()
3546 {
3547 self.available_code_actions.take();
3548 }
3549 self.refresh_code_actions(window, cx);
3550 self.refresh_document_highlights(cx);
3551 refresh_linked_ranges(self, window, cx);
3552
3553 self.refresh_selected_text_highlights(false, window, cx);
3554 self.refresh_matching_bracket_highlights(window, cx);
3555 self.update_visible_edit_prediction(window, cx);
3556 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3557 self.inline_blame_popover.take();
3558 if self.git_blame_inline_enabled {
3559 self.start_inline_blame_timer(window, cx);
3560 }
3561 }
3562
3563 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3564
3565 if local && !self.suppress_selection_callback {
3566 if let Some(callback) = self.on_local_selections_changed.as_ref() {
3567 let cursor_position = self.selections.newest::<Point>(&display_map).head();
3568 callback(cursor_position, window, cx);
3569 }
3570 }
3571
3572 cx.emit(EditorEvent::SelectionsChanged { local });
3573
3574 let selections = &self.selections.disjoint_anchors_arc();
3575 if selections.len() == 1 {
3576 cx.emit(SearchEvent::ActiveMatchChanged)
3577 }
3578 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3579 let inmemory_selections = selections
3580 .iter()
3581 .map(|s| {
3582 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3583 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3584 })
3585 .collect();
3586 self.update_restoration_data(cx, |data| {
3587 data.selections = inmemory_selections;
3588 });
3589
3590 if WorkspaceSettings::get(None, cx).restore_on_startup
3591 != RestoreOnStartupBehavior::EmptyTab
3592 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3593 {
3594 let snapshot = self.buffer().read(cx).snapshot(cx);
3595 let selections = selections.clone();
3596 let background_executor = cx.background_executor().clone();
3597 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3598 self.serialize_selections = cx.background_spawn(async move {
3599 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3600 let db_selections = selections
3601 .iter()
3602 .map(|selection| {
3603 (
3604 selection.start.to_offset(&snapshot).0,
3605 selection.end.to_offset(&snapshot).0,
3606 )
3607 })
3608 .collect();
3609
3610 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3611 .await
3612 .with_context(|| {
3613 format!(
3614 "persisting editor selections for editor {editor_id}, \
3615 workspace {workspace_id:?}"
3616 )
3617 })
3618 .log_err();
3619 });
3620 }
3621 }
3622
3623 cx.notify();
3624 }
3625
3626 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3627 use text::ToOffset as _;
3628 use text::ToPoint as _;
3629
3630 if self.mode.is_minimap()
3631 || WorkspaceSettings::get(None, cx).restore_on_startup
3632 == RestoreOnStartupBehavior::EmptyTab
3633 {
3634 return;
3635 }
3636
3637 if !self.buffer().read(cx).is_singleton() {
3638 return;
3639 }
3640
3641 let display_snapshot = self
3642 .display_map
3643 .update(cx, |display_map, cx| display_map.snapshot(cx));
3644 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3645 return;
3646 };
3647 let inmemory_folds = display_snapshot
3648 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3649 .map(|fold| {
3650 fold.range.start.text_anchor.to_point(&snapshot)
3651 ..fold.range.end.text_anchor.to_point(&snapshot)
3652 })
3653 .collect();
3654 self.update_restoration_data(cx, |data| {
3655 data.folds = inmemory_folds;
3656 });
3657
3658 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3659 return;
3660 };
3661 let background_executor = cx.background_executor().clone();
3662 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3663 const FINGERPRINT_LEN: usize = 32;
3664 let db_folds = display_snapshot
3665 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3666 .map(|fold| {
3667 let start = fold.range.start.text_anchor.to_offset(&snapshot);
3668 let end = fold.range.end.text_anchor.to_offset(&snapshot);
3669
3670 // Extract fingerprints - content at fold boundaries for validation on restore
3671 // Both fingerprints must be INSIDE the fold to avoid capturing surrounding
3672 // content that might change independently.
3673 // start_fp: first min(32, fold_len) bytes of fold content
3674 // end_fp: last min(32, fold_len) bytes of fold content
3675 // Clip to character boundaries to handle multibyte UTF-8 characters.
3676 let fold_len = end - start;
3677 let start_fp_end = snapshot
3678 .clip_offset(start + std::cmp::min(FINGERPRINT_LEN, fold_len), Bias::Left);
3679 let start_fp: String = snapshot.text_for_range(start..start_fp_end).collect();
3680 let end_fp_start = snapshot
3681 .clip_offset(end.saturating_sub(FINGERPRINT_LEN).max(start), Bias::Right);
3682 let end_fp: String = snapshot.text_for_range(end_fp_start..end).collect();
3683
3684 (start, end, start_fp, end_fp)
3685 })
3686 .collect::<Vec<_>>();
3687 self.serialize_folds = cx.background_spawn(async move {
3688 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3689 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3690 .await
3691 .with_context(|| {
3692 format!(
3693 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3694 )
3695 })
3696 .log_err();
3697 });
3698 }
3699
3700 pub fn sync_selections(
3701 &mut self,
3702 other: Entity<Editor>,
3703 cx: &mut Context<Self>,
3704 ) -> gpui::Subscription {
3705 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3706 if !other_selections.is_empty() {
3707 self.selections
3708 .change_with(&self.display_snapshot(cx), |selections| {
3709 selections.select_anchors(other_selections);
3710 });
3711 }
3712
3713 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3714 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3715 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3716 if other_selections.is_empty() {
3717 return;
3718 }
3719 let snapshot = this.display_snapshot(cx);
3720 this.selections.change_with(&snapshot, |selections| {
3721 selections.select_anchors(other_selections);
3722 });
3723 }
3724 });
3725
3726 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3727 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3728 let these_selections = this.selections.disjoint_anchors().to_vec();
3729 if these_selections.is_empty() {
3730 return;
3731 }
3732 other.update(cx, |other_editor, cx| {
3733 let snapshot = other_editor.display_snapshot(cx);
3734 other_editor
3735 .selections
3736 .change_with(&snapshot, |selections| {
3737 selections.select_anchors(these_selections);
3738 })
3739 });
3740 }
3741 });
3742
3743 Subscription::join(other_subscription, this_subscription)
3744 }
3745
3746 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3747 if self.buffer().read(cx).is_singleton() {
3748 return;
3749 }
3750 let snapshot = self.buffer.read(cx).snapshot(cx);
3751 let buffer_ids: HashSet<BufferId> = self
3752 .selections
3753 .disjoint_anchor_ranges()
3754 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3755 .collect();
3756 for buffer_id in buffer_ids {
3757 self.unfold_buffer(buffer_id, cx);
3758 }
3759 }
3760
3761 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3762 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3763 /// effects of selection change occur at the end of the transaction.
3764 pub fn change_selections<R>(
3765 &mut self,
3766 effects: SelectionEffects,
3767 window: &mut Window,
3768 cx: &mut Context<Self>,
3769 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3770 ) -> R {
3771 let snapshot = self.display_snapshot(cx);
3772 if let Some(state) = &mut self.deferred_selection_effects_state {
3773 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3774 state.effects.completions = effects.completions;
3775 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3776 let (changed, result) = self.selections.change_with(&snapshot, change);
3777 state.changed |= changed;
3778 return result;
3779 }
3780 let mut state = DeferredSelectionEffectsState {
3781 changed: false,
3782 effects,
3783 old_cursor_position: self.selections.newest_anchor().head(),
3784 history_entry: SelectionHistoryEntry {
3785 selections: self.selections.disjoint_anchors_arc(),
3786 select_next_state: self.select_next_state.clone(),
3787 select_prev_state: self.select_prev_state.clone(),
3788 add_selections_state: self.add_selections_state.clone(),
3789 },
3790 };
3791 let (changed, result) = self.selections.change_with(&snapshot, change);
3792 state.changed = state.changed || changed;
3793 if self.defer_selection_effects {
3794 self.deferred_selection_effects_state = Some(state);
3795 } else {
3796 self.apply_selection_effects(state, window, cx);
3797 }
3798 result
3799 }
3800
3801 /// Defers the effects of selection change, so that the effects of multiple calls to
3802 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3803 /// to selection history and the state of popovers based on selection position aren't
3804 /// erroneously updated.
3805 pub fn with_selection_effects_deferred<R>(
3806 &mut self,
3807 window: &mut Window,
3808 cx: &mut Context<Self>,
3809 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3810 ) -> R {
3811 let already_deferred = self.defer_selection_effects;
3812 self.defer_selection_effects = true;
3813 let result = update(self, window, cx);
3814 if !already_deferred {
3815 self.defer_selection_effects = false;
3816 if let Some(state) = self.deferred_selection_effects_state.take() {
3817 self.apply_selection_effects(state, window, cx);
3818 }
3819 }
3820 result
3821 }
3822
3823 fn apply_selection_effects(
3824 &mut self,
3825 state: DeferredSelectionEffectsState,
3826 window: &mut Window,
3827 cx: &mut Context<Self>,
3828 ) {
3829 if state.changed {
3830 self.selection_history.push(state.history_entry);
3831
3832 if let Some(autoscroll) = state.effects.scroll {
3833 self.request_autoscroll(autoscroll, cx);
3834 }
3835
3836 let old_cursor_position = &state.old_cursor_position;
3837
3838 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3839
3840 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3841 self.show_signature_help_auto(window, cx);
3842 }
3843 }
3844 }
3845
3846 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3847 where
3848 I: IntoIterator<Item = (Range<S>, T)>,
3849 S: ToOffset,
3850 T: Into<Arc<str>>,
3851 {
3852 if self.read_only(cx) {
3853 return;
3854 }
3855
3856 self.buffer
3857 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3858 }
3859
3860 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3861 where
3862 I: IntoIterator<Item = (Range<S>, T)>,
3863 S: ToOffset,
3864 T: Into<Arc<str>>,
3865 {
3866 if self.read_only(cx) {
3867 return;
3868 }
3869
3870 self.buffer.update(cx, |buffer, cx| {
3871 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3872 });
3873 }
3874
3875 pub fn edit_with_block_indent<I, S, T>(
3876 &mut self,
3877 edits: I,
3878 original_indent_columns: Vec<Option<u32>>,
3879 cx: &mut Context<Self>,
3880 ) where
3881 I: IntoIterator<Item = (Range<S>, T)>,
3882 S: ToOffset,
3883 T: Into<Arc<str>>,
3884 {
3885 if self.read_only(cx) {
3886 return;
3887 }
3888
3889 self.buffer.update(cx, |buffer, cx| {
3890 buffer.edit(
3891 edits,
3892 Some(AutoindentMode::Block {
3893 original_indent_columns,
3894 }),
3895 cx,
3896 )
3897 });
3898 }
3899
3900 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3901 self.hide_context_menu(window, cx);
3902
3903 match phase {
3904 SelectPhase::Begin {
3905 position,
3906 add,
3907 click_count,
3908 } => self.begin_selection(position, add, click_count, window, cx),
3909 SelectPhase::BeginColumnar {
3910 position,
3911 goal_column,
3912 reset,
3913 mode,
3914 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3915 SelectPhase::Extend {
3916 position,
3917 click_count,
3918 } => self.extend_selection(position, click_count, window, cx),
3919 SelectPhase::Update {
3920 position,
3921 goal_column,
3922 scroll_delta,
3923 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3924 SelectPhase::End => self.end_selection(window, cx),
3925 }
3926 }
3927
3928 fn extend_selection(
3929 &mut self,
3930 position: DisplayPoint,
3931 click_count: usize,
3932 window: &mut Window,
3933 cx: &mut Context<Self>,
3934 ) {
3935 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3936 let tail = self
3937 .selections
3938 .newest::<MultiBufferOffset>(&display_map)
3939 .tail();
3940 let click_count = click_count.max(match self.selections.select_mode() {
3941 SelectMode::Character => 1,
3942 SelectMode::Word(_) => 2,
3943 SelectMode::Line(_) => 3,
3944 SelectMode::All => 4,
3945 });
3946 self.begin_selection(position, false, click_count, window, cx);
3947
3948 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3949
3950 let current_selection = match self.selections.select_mode() {
3951 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3952 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3953 };
3954
3955 let mut pending_selection = self
3956 .selections
3957 .pending_anchor()
3958 .cloned()
3959 .expect("extend_selection not called with pending selection");
3960
3961 if pending_selection
3962 .start
3963 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3964 == Ordering::Greater
3965 {
3966 pending_selection.start = current_selection.start;
3967 }
3968 if pending_selection
3969 .end
3970 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3971 == Ordering::Less
3972 {
3973 pending_selection.end = current_selection.end;
3974 pending_selection.reversed = true;
3975 }
3976
3977 let mut pending_mode = self.selections.pending_mode().unwrap();
3978 match &mut pending_mode {
3979 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3980 _ => {}
3981 }
3982
3983 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3984 SelectionEffects::scroll(Autoscroll::fit())
3985 } else {
3986 SelectionEffects::no_scroll()
3987 };
3988
3989 self.change_selections(effects, window, cx, |s| {
3990 s.set_pending(pending_selection.clone(), pending_mode);
3991 s.set_is_extending(true);
3992 });
3993 }
3994
3995 fn begin_selection(
3996 &mut self,
3997 position: DisplayPoint,
3998 add: bool,
3999 click_count: usize,
4000 window: &mut Window,
4001 cx: &mut Context<Self>,
4002 ) {
4003 if !self.focus_handle.is_focused(window) {
4004 self.last_focused_descendant = None;
4005 window.focus(&self.focus_handle, cx);
4006 }
4007
4008 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4009 let buffer = display_map.buffer_snapshot();
4010 let position = display_map.clip_point(position, Bias::Left);
4011
4012 let start;
4013 let end;
4014 let mode;
4015 let mut auto_scroll;
4016 match click_count {
4017 1 => {
4018 start = buffer.anchor_before(position.to_point(&display_map));
4019 end = start;
4020 mode = SelectMode::Character;
4021 auto_scroll = true;
4022 }
4023 2 => {
4024 let position = display_map
4025 .clip_point(position, Bias::Left)
4026 .to_offset(&display_map, Bias::Left);
4027 let (range, _) = buffer.surrounding_word(position, None);
4028 start = buffer.anchor_before(range.start);
4029 end = buffer.anchor_before(range.end);
4030 mode = SelectMode::Word(start..end);
4031 auto_scroll = true;
4032 }
4033 3 => {
4034 let position = display_map
4035 .clip_point(position, Bias::Left)
4036 .to_point(&display_map);
4037 let line_start = display_map.prev_line_boundary(position).0;
4038 let next_line_start = buffer.clip_point(
4039 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4040 Bias::Left,
4041 );
4042 start = buffer.anchor_before(line_start);
4043 end = buffer.anchor_before(next_line_start);
4044 mode = SelectMode::Line(start..end);
4045 auto_scroll = true;
4046 }
4047 _ => {
4048 start = buffer.anchor_before(MultiBufferOffset(0));
4049 end = buffer.anchor_before(buffer.len());
4050 mode = SelectMode::All;
4051 auto_scroll = false;
4052 }
4053 }
4054 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
4055
4056 let point_to_delete: Option<usize> = {
4057 let selected_points: Vec<Selection<Point>> =
4058 self.selections.disjoint_in_range(start..end, &display_map);
4059
4060 if !add || click_count > 1 {
4061 None
4062 } else if !selected_points.is_empty() {
4063 Some(selected_points[0].id)
4064 } else {
4065 let clicked_point_already_selected =
4066 self.selections.disjoint_anchors().iter().find(|selection| {
4067 selection.start.to_point(buffer) == start.to_point(buffer)
4068 || selection.end.to_point(buffer) == end.to_point(buffer)
4069 });
4070
4071 clicked_point_already_selected.map(|selection| selection.id)
4072 }
4073 };
4074
4075 let selections_count = self.selections.count();
4076 let effects = if auto_scroll {
4077 SelectionEffects::default()
4078 } else {
4079 SelectionEffects::no_scroll()
4080 };
4081
4082 self.change_selections(effects, window, cx, |s| {
4083 if let Some(point_to_delete) = point_to_delete {
4084 s.delete(point_to_delete);
4085
4086 if selections_count == 1 {
4087 s.set_pending_anchor_range(start..end, mode);
4088 }
4089 } else {
4090 if !add {
4091 s.clear_disjoint();
4092 }
4093
4094 s.set_pending_anchor_range(start..end, mode);
4095 }
4096 });
4097 }
4098
4099 fn begin_columnar_selection(
4100 &mut self,
4101 position: DisplayPoint,
4102 goal_column: u32,
4103 reset: bool,
4104 mode: ColumnarMode,
4105 window: &mut Window,
4106 cx: &mut Context<Self>,
4107 ) {
4108 if !self.focus_handle.is_focused(window) {
4109 self.last_focused_descendant = None;
4110 window.focus(&self.focus_handle, cx);
4111 }
4112
4113 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4114
4115 if reset {
4116 let pointer_position = display_map
4117 .buffer_snapshot()
4118 .anchor_before(position.to_point(&display_map));
4119
4120 self.change_selections(
4121 SelectionEffects::scroll(Autoscroll::newest()),
4122 window,
4123 cx,
4124 |s| {
4125 s.clear_disjoint();
4126 s.set_pending_anchor_range(
4127 pointer_position..pointer_position,
4128 SelectMode::Character,
4129 );
4130 },
4131 );
4132 };
4133
4134 let tail = self.selections.newest::<Point>(&display_map).tail();
4135 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
4136 self.columnar_selection_state = match mode {
4137 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
4138 selection_tail: selection_anchor,
4139 display_point: if reset {
4140 if position.column() != goal_column {
4141 Some(DisplayPoint::new(position.row(), goal_column))
4142 } else {
4143 None
4144 }
4145 } else {
4146 None
4147 },
4148 }),
4149 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
4150 selection_tail: selection_anchor,
4151 }),
4152 };
4153
4154 if !reset {
4155 self.select_columns(position, goal_column, &display_map, window, cx);
4156 }
4157 }
4158
4159 fn update_selection(
4160 &mut self,
4161 position: DisplayPoint,
4162 goal_column: u32,
4163 scroll_delta: gpui::Point<f32>,
4164 window: &mut Window,
4165 cx: &mut Context<Self>,
4166 ) {
4167 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4168
4169 if self.columnar_selection_state.is_some() {
4170 self.select_columns(position, goal_column, &display_map, window, cx);
4171 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
4172 let buffer = display_map.buffer_snapshot();
4173 let head;
4174 let tail;
4175 let mode = self.selections.pending_mode().unwrap();
4176 match &mode {
4177 SelectMode::Character => {
4178 head = position.to_point(&display_map);
4179 tail = pending.tail().to_point(buffer);
4180 }
4181 SelectMode::Word(original_range) => {
4182 let offset = display_map
4183 .clip_point(position, Bias::Left)
4184 .to_offset(&display_map, Bias::Left);
4185 let original_range = original_range.to_offset(buffer);
4186
4187 let head_offset = if buffer.is_inside_word(offset, None)
4188 || original_range.contains(&offset)
4189 {
4190 let (word_range, _) = buffer.surrounding_word(offset, None);
4191 if word_range.start < original_range.start {
4192 word_range.start
4193 } else {
4194 word_range.end
4195 }
4196 } else {
4197 offset
4198 };
4199
4200 head = head_offset.to_point(buffer);
4201 if head_offset <= original_range.start {
4202 tail = original_range.end.to_point(buffer);
4203 } else {
4204 tail = original_range.start.to_point(buffer);
4205 }
4206 }
4207 SelectMode::Line(original_range) => {
4208 let original_range = original_range.to_point(display_map.buffer_snapshot());
4209
4210 let position = display_map
4211 .clip_point(position, Bias::Left)
4212 .to_point(&display_map);
4213 let line_start = display_map.prev_line_boundary(position).0;
4214 let next_line_start = buffer.clip_point(
4215 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4216 Bias::Left,
4217 );
4218
4219 if line_start < original_range.start {
4220 head = line_start
4221 } else {
4222 head = next_line_start
4223 }
4224
4225 if head <= original_range.start {
4226 tail = original_range.end;
4227 } else {
4228 tail = original_range.start;
4229 }
4230 }
4231 SelectMode::All => {
4232 return;
4233 }
4234 };
4235
4236 if head < tail {
4237 pending.start = buffer.anchor_before(head);
4238 pending.end = buffer.anchor_before(tail);
4239 pending.reversed = true;
4240 } else {
4241 pending.start = buffer.anchor_before(tail);
4242 pending.end = buffer.anchor_before(head);
4243 pending.reversed = false;
4244 }
4245
4246 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4247 s.set_pending(pending.clone(), mode);
4248 });
4249 } else {
4250 log::error!("update_selection dispatched with no pending selection");
4251 return;
4252 }
4253
4254 self.apply_scroll_delta(scroll_delta, window, cx);
4255 cx.notify();
4256 }
4257
4258 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4259 self.columnar_selection_state.take();
4260 if let Some(pending_mode) = self.selections.pending_mode() {
4261 let selections = self
4262 .selections
4263 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4264 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4265 s.select(selections);
4266 s.clear_pending();
4267 if s.is_extending() {
4268 s.set_is_extending(false);
4269 } else {
4270 s.set_select_mode(pending_mode);
4271 }
4272 });
4273 }
4274 }
4275
4276 fn select_columns(
4277 &mut self,
4278 head: DisplayPoint,
4279 goal_column: u32,
4280 display_map: &DisplaySnapshot,
4281 window: &mut Window,
4282 cx: &mut Context<Self>,
4283 ) {
4284 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4285 return;
4286 };
4287
4288 let tail = match columnar_state {
4289 ColumnarSelectionState::FromMouse {
4290 selection_tail,
4291 display_point,
4292 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4293 ColumnarSelectionState::FromSelection { selection_tail } => {
4294 selection_tail.to_display_point(display_map)
4295 }
4296 };
4297
4298 let start_row = cmp::min(tail.row(), head.row());
4299 let end_row = cmp::max(tail.row(), head.row());
4300 let start_column = cmp::min(tail.column(), goal_column);
4301 let end_column = cmp::max(tail.column(), goal_column);
4302 let reversed = start_column < tail.column();
4303
4304 let selection_ranges = (start_row.0..=end_row.0)
4305 .map(DisplayRow)
4306 .filter_map(|row| {
4307 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4308 || start_column <= display_map.line_len(row))
4309 && !display_map.is_block_line(row)
4310 {
4311 let start = display_map
4312 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4313 .to_point(display_map);
4314 let end = display_map
4315 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4316 .to_point(display_map);
4317 if reversed {
4318 Some(end..start)
4319 } else {
4320 Some(start..end)
4321 }
4322 } else {
4323 None
4324 }
4325 })
4326 .collect::<Vec<_>>();
4327 if selection_ranges.is_empty() {
4328 return;
4329 }
4330
4331 let ranges = match columnar_state {
4332 ColumnarSelectionState::FromMouse { .. } => {
4333 let mut non_empty_ranges = selection_ranges
4334 .iter()
4335 .filter(|selection_range| selection_range.start != selection_range.end)
4336 .peekable();
4337 if non_empty_ranges.peek().is_some() {
4338 non_empty_ranges.cloned().collect()
4339 } else {
4340 selection_ranges
4341 }
4342 }
4343 _ => selection_ranges,
4344 };
4345
4346 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4347 s.select_ranges(ranges);
4348 });
4349 cx.notify();
4350 }
4351
4352 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4353 self.selections
4354 .all_adjusted(snapshot)
4355 .iter()
4356 .any(|selection| !selection.is_empty())
4357 }
4358
4359 pub fn has_pending_nonempty_selection(&self) -> bool {
4360 let pending_nonempty_selection = match self.selections.pending_anchor() {
4361 Some(Selection { start, end, .. }) => start != end,
4362 None => false,
4363 };
4364
4365 pending_nonempty_selection
4366 || (self.columnar_selection_state.is_some()
4367 && self.selections.disjoint_anchors().len() > 1)
4368 }
4369
4370 pub fn has_pending_selection(&self) -> bool {
4371 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4372 }
4373
4374 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4375 self.selection_mark_mode = false;
4376 self.selection_drag_state = SelectionDragState::None;
4377
4378 if self.dismiss_menus_and_popups(true, window, cx) {
4379 cx.notify();
4380 return;
4381 }
4382 if self.clear_expanded_diff_hunks(cx) {
4383 cx.notify();
4384 return;
4385 }
4386 if self.show_git_blame_gutter {
4387 self.show_git_blame_gutter = false;
4388 cx.notify();
4389 return;
4390 }
4391
4392 if self.mode.is_full()
4393 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4394 {
4395 cx.notify();
4396 return;
4397 }
4398
4399 cx.propagate();
4400 }
4401
4402 pub fn dismiss_menus_and_popups(
4403 &mut self,
4404 is_user_requested: bool,
4405 window: &mut Window,
4406 cx: &mut Context<Self>,
4407 ) -> bool {
4408 let mut dismissed = false;
4409
4410 dismissed |= self.take_rename(false, window, cx).is_some();
4411 dismissed |= self.hide_blame_popover(true, cx);
4412 dismissed |= hide_hover(self, cx);
4413 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4414 dismissed |= self.hide_context_menu(window, cx).is_some();
4415 dismissed |= self.mouse_context_menu.take().is_some();
4416 dismissed |= is_user_requested && self.discard_edit_prediction(true, cx);
4417 dismissed |= self.snippet_stack.pop().is_some();
4418 if self.diff_review_drag_state.is_some() {
4419 self.cancel_diff_review_drag(cx);
4420 dismissed = true;
4421 }
4422 if !self.diff_review_overlays.is_empty() {
4423 self.dismiss_all_diff_review_overlays(cx);
4424 dismissed = true;
4425 }
4426
4427 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4428 self.dismiss_diagnostics(cx);
4429 dismissed = true;
4430 }
4431
4432 dismissed
4433 }
4434
4435 fn linked_editing_ranges_for(
4436 &self,
4437 selection: Range<text::Anchor>,
4438 cx: &App,
4439 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4440 if self.linked_edit_ranges.is_empty() {
4441 return None;
4442 }
4443 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4444 selection.end.buffer_id.and_then(|end_buffer_id| {
4445 if selection.start.buffer_id != Some(end_buffer_id) {
4446 return None;
4447 }
4448 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4449 let snapshot = buffer.read(cx).snapshot();
4450 self.linked_edit_ranges
4451 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4452 .map(|ranges| (ranges, snapshot, buffer))
4453 })?;
4454 use text::ToOffset as TO;
4455 // find offset from the start of current range to current cursor position
4456 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4457
4458 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4459 let start_difference = start_offset - start_byte_offset;
4460 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4461 let end_difference = end_offset - start_byte_offset;
4462 // Current range has associated linked ranges.
4463 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4464 for range in linked_ranges.iter() {
4465 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4466 let end_offset = start_offset + end_difference;
4467 let start_offset = start_offset + start_difference;
4468 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4469 continue;
4470 }
4471 if self.selections.disjoint_anchor_ranges().any(|s| {
4472 if s.start.text_anchor.buffer_id != selection.start.buffer_id
4473 || s.end.text_anchor.buffer_id != selection.end.buffer_id
4474 {
4475 return false;
4476 }
4477 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4478 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4479 }) {
4480 continue;
4481 }
4482 let start = buffer_snapshot.anchor_after(start_offset);
4483 let end = buffer_snapshot.anchor_after(end_offset);
4484 linked_edits
4485 .entry(buffer.clone())
4486 .or_default()
4487 .push(start..end);
4488 }
4489 Some(linked_edits)
4490 }
4491
4492 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4493 let text: Arc<str> = text.into();
4494
4495 if self.read_only(cx) {
4496 return;
4497 }
4498
4499 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4500
4501 self.unfold_buffers_with_selections(cx);
4502
4503 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4504 let mut bracket_inserted = false;
4505 let mut edits = Vec::new();
4506 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4507 let mut new_selections = Vec::with_capacity(selections.len());
4508 let mut new_autoclose_regions = Vec::new();
4509 let snapshot = self.buffer.read(cx).read(cx);
4510 let mut clear_linked_edit_ranges = false;
4511 let mut all_selections_read_only = true;
4512 let mut has_adjacent_edits = false;
4513 let mut in_adjacent_group = false;
4514
4515 let mut regions = self
4516 .selections_with_autoclose_regions(selections, &snapshot)
4517 .peekable();
4518
4519 while let Some((selection, autoclose_region)) = regions.next() {
4520 if snapshot
4521 .point_to_buffer_point(selection.head())
4522 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4523 {
4524 continue;
4525 }
4526 if snapshot
4527 .point_to_buffer_point(selection.tail())
4528 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4529 {
4530 // note, ideally we'd clip the tail to the closest writeable region towards the head
4531 continue;
4532 }
4533 all_selections_read_only = false;
4534
4535 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4536 // Determine if the inserted text matches the opening or closing
4537 // bracket of any of this language's bracket pairs.
4538 let mut bracket_pair = None;
4539 let mut is_bracket_pair_start = false;
4540 let mut is_bracket_pair_end = false;
4541 if !text.is_empty() {
4542 let mut bracket_pair_matching_end = None;
4543 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4544 // and they are removing the character that triggered IME popup.
4545 for (pair, enabled) in scope.brackets() {
4546 if !pair.close && !pair.surround {
4547 continue;
4548 }
4549
4550 if enabled && pair.start.ends_with(text.as_ref()) {
4551 let prefix_len = pair.start.len() - text.len();
4552 let preceding_text_matches_prefix = prefix_len == 0
4553 || (selection.start.column >= (prefix_len as u32)
4554 && snapshot.contains_str_at(
4555 Point::new(
4556 selection.start.row,
4557 selection.start.column - (prefix_len as u32),
4558 ),
4559 &pair.start[..prefix_len],
4560 ));
4561 if preceding_text_matches_prefix {
4562 bracket_pair = Some(pair.clone());
4563 is_bracket_pair_start = true;
4564 break;
4565 }
4566 }
4567 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4568 {
4569 // take first bracket pair matching end, but don't break in case a later bracket
4570 // pair matches start
4571 bracket_pair_matching_end = Some(pair.clone());
4572 }
4573 }
4574 if let Some(end) = bracket_pair_matching_end
4575 && bracket_pair.is_none()
4576 {
4577 bracket_pair = Some(end);
4578 is_bracket_pair_end = true;
4579 }
4580 }
4581
4582 if let Some(bracket_pair) = bracket_pair {
4583 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4584 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4585 let auto_surround =
4586 self.use_auto_surround && snapshot_settings.use_auto_surround;
4587 if selection.is_empty() {
4588 if is_bracket_pair_start {
4589 // If the inserted text is a suffix of an opening bracket and the
4590 // selection is preceded by the rest of the opening bracket, then
4591 // insert the closing bracket.
4592 let following_text_allows_autoclose = snapshot
4593 .chars_at(selection.start)
4594 .next()
4595 .is_none_or(|c| scope.should_autoclose_before(c));
4596
4597 let preceding_text_allows_autoclose = selection.start.column == 0
4598 || snapshot
4599 .reversed_chars_at(selection.start)
4600 .next()
4601 .is_none_or(|c| {
4602 bracket_pair.start != bracket_pair.end
4603 || !snapshot
4604 .char_classifier_at(selection.start)
4605 .is_word(c)
4606 });
4607
4608 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4609 && bracket_pair.start.len() == 1
4610 {
4611 let target = bracket_pair.start.chars().next().unwrap();
4612 let mut byte_offset = 0u32;
4613 let current_line_count = snapshot
4614 .reversed_chars_at(selection.start)
4615 .take_while(|&c| c != '\n')
4616 .filter(|c| {
4617 byte_offset += c.len_utf8() as u32;
4618 if *c != target {
4619 return false;
4620 }
4621
4622 let point = Point::new(
4623 selection.start.row,
4624 selection.start.column.saturating_sub(byte_offset),
4625 );
4626
4627 let is_enabled = snapshot
4628 .language_scope_at(point)
4629 .and_then(|scope| {
4630 scope
4631 .brackets()
4632 .find(|(pair, _)| {
4633 pair.start == bracket_pair.start
4634 })
4635 .map(|(_, enabled)| enabled)
4636 })
4637 .unwrap_or(true);
4638
4639 let is_delimiter = snapshot
4640 .language_scope_at(Point::new(
4641 point.row,
4642 point.column + 1,
4643 ))
4644 .and_then(|scope| {
4645 scope
4646 .brackets()
4647 .find(|(pair, _)| {
4648 pair.start == bracket_pair.start
4649 })
4650 .map(|(_, enabled)| !enabled)
4651 })
4652 .unwrap_or(false);
4653
4654 is_enabled && !is_delimiter
4655 })
4656 .count();
4657 current_line_count % 2 == 1
4658 } else {
4659 false
4660 };
4661
4662 if autoclose
4663 && bracket_pair.close
4664 && following_text_allows_autoclose
4665 && preceding_text_allows_autoclose
4666 && !is_closing_quote
4667 {
4668 let anchor = snapshot.anchor_before(selection.end);
4669 new_selections.push((selection.map(|_| anchor), text.len()));
4670 new_autoclose_regions.push((
4671 anchor,
4672 text.len(),
4673 selection.id,
4674 bracket_pair.clone(),
4675 ));
4676 edits.push((
4677 selection.range(),
4678 format!("{}{}", text, bracket_pair.end).into(),
4679 ));
4680 bracket_inserted = true;
4681 continue;
4682 }
4683 }
4684
4685 if let Some(region) = autoclose_region {
4686 // If the selection is followed by an auto-inserted closing bracket,
4687 // then don't insert that closing bracket again; just move the selection
4688 // past the closing bracket.
4689 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4690 && text.as_ref() == region.pair.end.as_str()
4691 && snapshot.contains_str_at(region.range.end, text.as_ref());
4692 if should_skip {
4693 let anchor = snapshot.anchor_after(selection.end);
4694 new_selections
4695 .push((selection.map(|_| anchor), region.pair.end.len()));
4696 continue;
4697 }
4698 }
4699
4700 let always_treat_brackets_as_autoclosed = snapshot
4701 .language_settings_at(selection.start, cx)
4702 .always_treat_brackets_as_autoclosed;
4703 if always_treat_brackets_as_autoclosed
4704 && is_bracket_pair_end
4705 && snapshot.contains_str_at(selection.end, text.as_ref())
4706 {
4707 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4708 // and the inserted text is a closing bracket and the selection is followed
4709 // by the closing bracket then move the selection past the closing bracket.
4710 let anchor = snapshot.anchor_after(selection.end);
4711 new_selections.push((selection.map(|_| anchor), text.len()));
4712 continue;
4713 }
4714 }
4715 // If an opening bracket is 1 character long and is typed while
4716 // text is selected, then surround that text with the bracket pair.
4717 else if auto_surround
4718 && bracket_pair.surround
4719 && is_bracket_pair_start
4720 && bracket_pair.start.chars().count() == 1
4721 {
4722 edits.push((selection.start..selection.start, text.clone()));
4723 edits.push((
4724 selection.end..selection.end,
4725 bracket_pair.end.as_str().into(),
4726 ));
4727 bracket_inserted = true;
4728 new_selections.push((
4729 Selection {
4730 id: selection.id,
4731 start: snapshot.anchor_after(selection.start),
4732 end: snapshot.anchor_before(selection.end),
4733 reversed: selection.reversed,
4734 goal: selection.goal,
4735 },
4736 0,
4737 ));
4738 continue;
4739 }
4740 }
4741 }
4742
4743 if self.auto_replace_emoji_shortcode
4744 && selection.is_empty()
4745 && text.as_ref().ends_with(':')
4746 && let Some(possible_emoji_short_code) =
4747 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4748 && !possible_emoji_short_code.is_empty()
4749 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4750 {
4751 let emoji_shortcode_start = Point::new(
4752 selection.start.row,
4753 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4754 );
4755
4756 // Remove shortcode from buffer
4757 edits.push((
4758 emoji_shortcode_start..selection.start,
4759 "".to_string().into(),
4760 ));
4761 new_selections.push((
4762 Selection {
4763 id: selection.id,
4764 start: snapshot.anchor_after(emoji_shortcode_start),
4765 end: snapshot.anchor_before(selection.start),
4766 reversed: selection.reversed,
4767 goal: selection.goal,
4768 },
4769 0,
4770 ));
4771
4772 // Insert emoji
4773 let selection_start_anchor = snapshot.anchor_after(selection.start);
4774 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4775 edits.push((selection.start..selection.end, emoji.to_string().into()));
4776
4777 continue;
4778 }
4779
4780 let next_is_adjacent = regions
4781 .peek()
4782 .is_some_and(|(next, _)| selection.end == next.start);
4783
4784 // If not handling any auto-close operation, then just replace the selected
4785 // text with the given input and move the selection to the end of the
4786 // newly inserted text.
4787 let anchor = if in_adjacent_group || next_is_adjacent {
4788 // After edits the right bias would shift those anchor to the next visible fragment
4789 // but we want to resolve to the previous one
4790 snapshot.anchor_before(selection.end)
4791 } else {
4792 snapshot.anchor_after(selection.end)
4793 };
4794
4795 if !self.linked_edit_ranges.is_empty() {
4796 let start_anchor = snapshot.anchor_before(selection.start);
4797
4798 let is_word_char = text.chars().next().is_none_or(|char| {
4799 let classifier = snapshot
4800 .char_classifier_at(start_anchor.to_offset(&snapshot))
4801 .scope_context(Some(CharScopeContext::LinkedEdit));
4802 classifier.is_word(char)
4803 });
4804
4805 if is_word_char {
4806 if let Some(ranges) = self
4807 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4808 {
4809 for (buffer, edits) in ranges {
4810 linked_edits
4811 .entry(buffer.clone())
4812 .or_default()
4813 .extend(edits.into_iter().map(|range| (range, text.clone())));
4814 }
4815 }
4816 } else {
4817 clear_linked_edit_ranges = true;
4818 }
4819 }
4820
4821 new_selections.push((selection.map(|_| anchor), 0));
4822 edits.push((selection.start..selection.end, text.clone()));
4823
4824 has_adjacent_edits |= next_is_adjacent;
4825 in_adjacent_group = next_is_adjacent;
4826 }
4827
4828 if all_selections_read_only {
4829 return;
4830 }
4831
4832 drop(regions);
4833 drop(snapshot);
4834
4835 self.transact(window, cx, |this, window, cx| {
4836 if clear_linked_edit_ranges {
4837 this.linked_edit_ranges.clear();
4838 }
4839 let initial_buffer_versions =
4840 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4841
4842 this.buffer.update(cx, |buffer, cx| {
4843 if has_adjacent_edits {
4844 buffer.edit_non_coalesce(edits, this.autoindent_mode.clone(), cx);
4845 } else {
4846 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4847 }
4848 });
4849 for (buffer, edits) in linked_edits {
4850 buffer.update(cx, |buffer, cx| {
4851 let snapshot = buffer.snapshot();
4852 let edits = edits
4853 .into_iter()
4854 .map(|(range, text)| {
4855 use text::ToPoint as TP;
4856 let end_point = TP::to_point(&range.end, &snapshot);
4857 let start_point = TP::to_point(&range.start, &snapshot);
4858 (start_point..end_point, text)
4859 })
4860 .sorted_by_key(|(range, _)| range.start);
4861 buffer.edit(edits, None, cx);
4862 })
4863 }
4864 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4865 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4866 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4867 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
4868 new_anchor_selections,
4869 &map,
4870 )
4871 .zip(new_selection_deltas)
4872 .map(|(selection, delta)| Selection {
4873 id: selection.id,
4874 start: selection.start + delta,
4875 end: selection.end + delta,
4876 reversed: selection.reversed,
4877 goal: SelectionGoal::None,
4878 })
4879 .collect::<Vec<_>>();
4880
4881 let mut i = 0;
4882 for (position, delta, selection_id, pair) in new_autoclose_regions {
4883 let position = position.to_offset(map.buffer_snapshot()) + delta;
4884 let start = map.buffer_snapshot().anchor_before(position);
4885 let end = map.buffer_snapshot().anchor_after(position);
4886 while let Some(existing_state) = this.autoclose_regions.get(i) {
4887 match existing_state
4888 .range
4889 .start
4890 .cmp(&start, map.buffer_snapshot())
4891 {
4892 Ordering::Less => i += 1,
4893 Ordering::Greater => break,
4894 Ordering::Equal => {
4895 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4896 Ordering::Less => i += 1,
4897 Ordering::Equal => break,
4898 Ordering::Greater => break,
4899 }
4900 }
4901 }
4902 }
4903 this.autoclose_regions.insert(
4904 i,
4905 AutocloseRegion {
4906 selection_id,
4907 range: start..end,
4908 pair,
4909 },
4910 );
4911 }
4912
4913 let had_active_edit_prediction = this.has_active_edit_prediction();
4914 this.change_selections(
4915 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4916 window,
4917 cx,
4918 |s| s.select(new_selections),
4919 );
4920
4921 if !bracket_inserted
4922 && let Some(on_type_format_task) =
4923 this.trigger_on_type_formatting(text.to_string(), window, cx)
4924 {
4925 on_type_format_task.detach_and_log_err(cx);
4926 }
4927
4928 let editor_settings = EditorSettings::get_global(cx);
4929 if bracket_inserted
4930 && (editor_settings.auto_signature_help
4931 || editor_settings.show_signature_help_after_edits)
4932 {
4933 this.show_signature_help(&ShowSignatureHelp, window, cx);
4934 }
4935
4936 let trigger_in_words =
4937 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4938 if this.hard_wrap.is_some() {
4939 let latest: Range<Point> = this.selections.newest(&map).range();
4940 if latest.is_empty()
4941 && this
4942 .buffer()
4943 .read(cx)
4944 .snapshot(cx)
4945 .line_len(MultiBufferRow(latest.start.row))
4946 == latest.start.column
4947 {
4948 this.rewrap_impl(
4949 RewrapOptions {
4950 override_language_settings: true,
4951 preserve_existing_whitespace: true,
4952 },
4953 cx,
4954 )
4955 }
4956 }
4957 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4958 refresh_linked_ranges(this, window, cx);
4959 this.refresh_edit_prediction(true, false, window, cx);
4960 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4961 });
4962 }
4963
4964 fn find_possible_emoji_shortcode_at_position(
4965 snapshot: &MultiBufferSnapshot,
4966 position: Point,
4967 ) -> Option<String> {
4968 let mut chars = Vec::new();
4969 let mut found_colon = false;
4970 for char in snapshot.reversed_chars_at(position).take(100) {
4971 // Found a possible emoji shortcode in the middle of the buffer
4972 if found_colon {
4973 if char.is_whitespace() {
4974 chars.reverse();
4975 return Some(chars.iter().collect());
4976 }
4977 // If the previous character is not a whitespace, we are in the middle of a word
4978 // and we only want to complete the shortcode if the word is made up of other emojis
4979 let mut containing_word = String::new();
4980 for ch in snapshot
4981 .reversed_chars_at(position)
4982 .skip(chars.len() + 1)
4983 .take(100)
4984 {
4985 if ch.is_whitespace() {
4986 break;
4987 }
4988 containing_word.push(ch);
4989 }
4990 let containing_word = containing_word.chars().rev().collect::<String>();
4991 if util::word_consists_of_emojis(containing_word.as_str()) {
4992 chars.reverse();
4993 return Some(chars.iter().collect());
4994 }
4995 }
4996
4997 if char.is_whitespace() || !char.is_ascii() {
4998 return None;
4999 }
5000 if char == ':' {
5001 found_colon = true;
5002 } else {
5003 chars.push(char);
5004 }
5005 }
5006 // Found a possible emoji shortcode at the beginning of the buffer
5007 chars.reverse();
5008 Some(chars.iter().collect())
5009 }
5010
5011 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
5012 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5013 self.transact(window, cx, |this, window, cx| {
5014 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
5015 let selections = this
5016 .selections
5017 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
5018 let multi_buffer = this.buffer.read(cx);
5019 let buffer = multi_buffer.snapshot(cx);
5020 selections
5021 .iter()
5022 .map(|selection| {
5023 let start_point = selection.start.to_point(&buffer);
5024 let mut existing_indent =
5025 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
5026 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
5027 let start = selection.start;
5028 let end = selection.end;
5029 let selection_is_empty = start == end;
5030 let language_scope = buffer.language_scope_at(start);
5031 let (delimiter, newline_config) = if let Some(language) = &language_scope {
5032 let needs_extra_newline = NewlineConfig::insert_extra_newline_brackets(
5033 &buffer,
5034 start..end,
5035 language,
5036 )
5037 || NewlineConfig::insert_extra_newline_tree_sitter(
5038 &buffer,
5039 start..end,
5040 );
5041
5042 let mut newline_config = NewlineConfig::Newline {
5043 additional_indent: IndentSize::spaces(0),
5044 extra_line_additional_indent: if needs_extra_newline {
5045 Some(IndentSize::spaces(0))
5046 } else {
5047 None
5048 },
5049 prevent_auto_indent: false,
5050 };
5051
5052 let comment_delimiter = maybe!({
5053 if !selection_is_empty {
5054 return None;
5055 }
5056
5057 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5058 return None;
5059 }
5060
5061 return comment_delimiter_for_newline(
5062 &start_point,
5063 &buffer,
5064 language,
5065 );
5066 });
5067
5068 let doc_delimiter = maybe!({
5069 if !selection_is_empty {
5070 return None;
5071 }
5072
5073 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5074 return None;
5075 }
5076
5077 return documentation_delimiter_for_newline(
5078 &start_point,
5079 &buffer,
5080 language,
5081 &mut newline_config,
5082 );
5083 });
5084
5085 let list_delimiter = maybe!({
5086 if !selection_is_empty {
5087 return None;
5088 }
5089
5090 if !multi_buffer.language_settings(cx).extend_list_on_newline {
5091 return None;
5092 }
5093
5094 return list_delimiter_for_newline(
5095 &start_point,
5096 &buffer,
5097 language,
5098 &mut newline_config,
5099 );
5100 });
5101
5102 (
5103 comment_delimiter.or(doc_delimiter).or(list_delimiter),
5104 newline_config,
5105 )
5106 } else {
5107 (
5108 None,
5109 NewlineConfig::Newline {
5110 additional_indent: IndentSize::spaces(0),
5111 extra_line_additional_indent: None,
5112 prevent_auto_indent: false,
5113 },
5114 )
5115 };
5116
5117 let (edit_start, new_text, prevent_auto_indent) = match &newline_config {
5118 NewlineConfig::ClearCurrentLine => {
5119 let row_start =
5120 buffer.point_to_offset(Point::new(start_point.row, 0));
5121 (row_start, String::new(), false)
5122 }
5123 NewlineConfig::UnindentCurrentLine { continuation } => {
5124 let row_start =
5125 buffer.point_to_offset(Point::new(start_point.row, 0));
5126 let tab_size = buffer.language_settings_at(start, cx).tab_size;
5127 let tab_size_indent = IndentSize::spaces(tab_size.get());
5128 let reduced_indent =
5129 existing_indent.with_delta(Ordering::Less, tab_size_indent);
5130 let mut new_text = String::new();
5131 new_text.extend(reduced_indent.chars());
5132 new_text.push_str(continuation);
5133 (row_start, new_text, true)
5134 }
5135 NewlineConfig::Newline {
5136 additional_indent,
5137 extra_line_additional_indent,
5138 prevent_auto_indent,
5139 } => {
5140 let capacity_for_delimiter =
5141 delimiter.as_deref().map(str::len).unwrap_or_default();
5142 let extra_line_len = extra_line_additional_indent
5143 .map(|i| 1 + existing_indent.len as usize + i.len as usize)
5144 .unwrap_or(0);
5145 let mut new_text = String::with_capacity(
5146 1 + capacity_for_delimiter
5147 + existing_indent.len as usize
5148 + additional_indent.len as usize
5149 + extra_line_len,
5150 );
5151 new_text.push('\n');
5152 new_text.extend(existing_indent.chars());
5153 new_text.extend(additional_indent.chars());
5154 if let Some(delimiter) = &delimiter {
5155 new_text.push_str(delimiter);
5156 }
5157 if let Some(extra_indent) = extra_line_additional_indent {
5158 new_text.push('\n');
5159 new_text.extend(existing_indent.chars());
5160 new_text.extend(extra_indent.chars());
5161 }
5162 (start, new_text, *prevent_auto_indent)
5163 }
5164 };
5165
5166 let anchor = buffer.anchor_after(end);
5167 let new_selection = selection.map(|_| anchor);
5168 (
5169 ((edit_start..end, new_text), prevent_auto_indent),
5170 (newline_config.has_extra_line(), new_selection),
5171 )
5172 })
5173 .unzip()
5174 };
5175
5176 let mut auto_indent_edits = Vec::new();
5177 let mut edits = Vec::new();
5178 for (edit, prevent_auto_indent) in edits_with_flags {
5179 if prevent_auto_indent {
5180 edits.push(edit);
5181 } else {
5182 auto_indent_edits.push(edit);
5183 }
5184 }
5185 if !edits.is_empty() {
5186 this.edit(edits, cx);
5187 }
5188 if !auto_indent_edits.is_empty() {
5189 this.edit_with_autoindent(auto_indent_edits, cx);
5190 }
5191
5192 let buffer = this.buffer.read(cx).snapshot(cx);
5193 let new_selections = selection_info
5194 .into_iter()
5195 .map(|(extra_newline_inserted, new_selection)| {
5196 let mut cursor = new_selection.end.to_point(&buffer);
5197 if extra_newline_inserted {
5198 cursor.row -= 1;
5199 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
5200 }
5201 new_selection.map(|_| cursor)
5202 })
5203 .collect();
5204
5205 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
5206 this.refresh_edit_prediction(true, false, window, cx);
5207 if let Some(task) = this.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5208 task.detach_and_log_err(cx);
5209 }
5210 });
5211 }
5212
5213 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
5214 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5215
5216 let buffer = self.buffer.read(cx);
5217 let snapshot = buffer.snapshot(cx);
5218
5219 let mut edits = Vec::new();
5220 let mut rows = Vec::new();
5221
5222 for (rows_inserted, selection) in self
5223 .selections
5224 .all_adjusted(&self.display_snapshot(cx))
5225 .into_iter()
5226 .enumerate()
5227 {
5228 let cursor = selection.head();
5229 let row = cursor.row;
5230
5231 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
5232
5233 let newline = "\n".to_string();
5234 edits.push((start_of_line..start_of_line, newline));
5235
5236 rows.push(row + rows_inserted as u32);
5237 }
5238
5239 self.transact(window, cx, |editor, window, cx| {
5240 editor.edit(edits, cx);
5241
5242 editor.change_selections(Default::default(), window, cx, |s| {
5243 let mut index = 0;
5244 s.move_cursors_with(|map, _, _| {
5245 let row = rows[index];
5246 index += 1;
5247
5248 let point = Point::new(row, 0);
5249 let boundary = map.next_line_boundary(point).1;
5250 let clipped = map.clip_point(boundary, Bias::Left);
5251
5252 (clipped, SelectionGoal::None)
5253 });
5254 });
5255
5256 let mut indent_edits = Vec::new();
5257 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5258 for row in rows {
5259 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5260 for (row, indent) in indents {
5261 if indent.len == 0 {
5262 continue;
5263 }
5264
5265 let text = match indent.kind {
5266 IndentKind::Space => " ".repeat(indent.len as usize),
5267 IndentKind::Tab => "\t".repeat(indent.len as usize),
5268 };
5269 let point = Point::new(row.0, 0);
5270 indent_edits.push((point..point, text));
5271 }
5272 }
5273 editor.edit(indent_edits, cx);
5274 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5275 format.detach_and_log_err(cx);
5276 }
5277 });
5278 }
5279
5280 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5281 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5282
5283 let buffer = self.buffer.read(cx);
5284 let snapshot = buffer.snapshot(cx);
5285
5286 let mut edits = Vec::new();
5287 let mut rows = Vec::new();
5288 let mut rows_inserted = 0;
5289
5290 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5291 let cursor = selection.head();
5292 let row = cursor.row;
5293
5294 let point = Point::new(row + 1, 0);
5295 let start_of_line = snapshot.clip_point(point, Bias::Left);
5296
5297 let newline = "\n".to_string();
5298 edits.push((start_of_line..start_of_line, newline));
5299
5300 rows_inserted += 1;
5301 rows.push(row + rows_inserted);
5302 }
5303
5304 self.transact(window, cx, |editor, window, cx| {
5305 editor.edit(edits, cx);
5306
5307 editor.change_selections(Default::default(), window, cx, |s| {
5308 let mut index = 0;
5309 s.move_cursors_with(|map, _, _| {
5310 let row = rows[index];
5311 index += 1;
5312
5313 let point = Point::new(row, 0);
5314 let boundary = map.next_line_boundary(point).1;
5315 let clipped = map.clip_point(boundary, Bias::Left);
5316
5317 (clipped, SelectionGoal::None)
5318 });
5319 });
5320
5321 let mut indent_edits = Vec::new();
5322 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5323 for row in rows {
5324 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5325 for (row, indent) in indents {
5326 if indent.len == 0 {
5327 continue;
5328 }
5329
5330 let text = match indent.kind {
5331 IndentKind::Space => " ".repeat(indent.len as usize),
5332 IndentKind::Tab => "\t".repeat(indent.len as usize),
5333 };
5334 let point = Point::new(row.0, 0);
5335 indent_edits.push((point..point, text));
5336 }
5337 }
5338 editor.edit(indent_edits, cx);
5339 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5340 format.detach_and_log_err(cx);
5341 }
5342 });
5343 }
5344
5345 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5346 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5347 original_indent_columns: Vec::new(),
5348 });
5349 self.insert_with_autoindent_mode(text, autoindent, window, cx);
5350 }
5351
5352 fn insert_with_autoindent_mode(
5353 &mut self,
5354 text: &str,
5355 autoindent_mode: Option<AutoindentMode>,
5356 window: &mut Window,
5357 cx: &mut Context<Self>,
5358 ) {
5359 if self.read_only(cx) {
5360 return;
5361 }
5362
5363 let text: Arc<str> = text.into();
5364 self.transact(window, cx, |this, window, cx| {
5365 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5366 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5367 let anchors = {
5368 let snapshot = buffer.read(cx);
5369 old_selections
5370 .iter()
5371 .map(|s| {
5372 let anchor = snapshot.anchor_after(s.head());
5373 s.map(|_| anchor)
5374 })
5375 .collect::<Vec<_>>()
5376 };
5377 buffer.edit(
5378 old_selections
5379 .iter()
5380 .map(|s| (s.start..s.end, text.clone())),
5381 autoindent_mode,
5382 cx,
5383 );
5384 anchors
5385 });
5386
5387 this.change_selections(Default::default(), window, cx, |s| {
5388 s.select_anchors(selection_anchors);
5389 });
5390
5391 cx.notify();
5392 });
5393 }
5394
5395 fn trigger_completion_on_input(
5396 &mut self,
5397 text: &str,
5398 trigger_in_words: bool,
5399 window: &mut Window,
5400 cx: &mut Context<Self>,
5401 ) {
5402 let completions_source = self
5403 .context_menu
5404 .borrow()
5405 .as_ref()
5406 .and_then(|menu| match menu {
5407 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5408 CodeContextMenu::CodeActions(_) => None,
5409 });
5410
5411 match completions_source {
5412 Some(CompletionsMenuSource::Words { .. }) => {
5413 self.open_or_update_completions_menu(
5414 Some(CompletionsMenuSource::Words {
5415 ignore_threshold: false,
5416 }),
5417 None,
5418 trigger_in_words,
5419 window,
5420 cx,
5421 );
5422 }
5423 _ => self.open_or_update_completions_menu(
5424 None,
5425 Some(text.to_owned()).filter(|x| !x.is_empty()),
5426 true,
5427 window,
5428 cx,
5429 ),
5430 }
5431 }
5432
5433 /// If any empty selections is touching the start of its innermost containing autoclose
5434 /// region, expand it to select the brackets.
5435 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5436 let selections = self
5437 .selections
5438 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5439 let buffer = self.buffer.read(cx).read(cx);
5440 let new_selections = self
5441 .selections_with_autoclose_regions(selections, &buffer)
5442 .map(|(mut selection, region)| {
5443 if !selection.is_empty() {
5444 return selection;
5445 }
5446
5447 if let Some(region) = region {
5448 let mut range = region.range.to_offset(&buffer);
5449 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5450 range.start -= region.pair.start.len();
5451 if buffer.contains_str_at(range.start, ®ion.pair.start)
5452 && buffer.contains_str_at(range.end, ®ion.pair.end)
5453 {
5454 range.end += region.pair.end.len();
5455 selection.start = range.start;
5456 selection.end = range.end;
5457
5458 return selection;
5459 }
5460 }
5461 }
5462
5463 let always_treat_brackets_as_autoclosed = buffer
5464 .language_settings_at(selection.start, cx)
5465 .always_treat_brackets_as_autoclosed;
5466
5467 if !always_treat_brackets_as_autoclosed {
5468 return selection;
5469 }
5470
5471 if let Some(scope) = buffer.language_scope_at(selection.start) {
5472 for (pair, enabled) in scope.brackets() {
5473 if !enabled || !pair.close {
5474 continue;
5475 }
5476
5477 if buffer.contains_str_at(selection.start, &pair.end) {
5478 let pair_start_len = pair.start.len();
5479 if buffer.contains_str_at(
5480 selection.start.saturating_sub_usize(pair_start_len),
5481 &pair.start,
5482 ) {
5483 selection.start -= pair_start_len;
5484 selection.end += pair.end.len();
5485
5486 return selection;
5487 }
5488 }
5489 }
5490 }
5491
5492 selection
5493 })
5494 .collect();
5495
5496 drop(buffer);
5497 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5498 selections.select(new_selections)
5499 });
5500 }
5501
5502 /// Iterate the given selections, and for each one, find the smallest surrounding
5503 /// autoclose region. This uses the ordering of the selections and the autoclose
5504 /// regions to avoid repeated comparisons.
5505 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5506 &'a self,
5507 selections: impl IntoIterator<Item = Selection<D>>,
5508 buffer: &'a MultiBufferSnapshot,
5509 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5510 let mut i = 0;
5511 let mut regions = self.autoclose_regions.as_slice();
5512 selections.into_iter().map(move |selection| {
5513 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5514
5515 let mut enclosing = None;
5516 while let Some(pair_state) = regions.get(i) {
5517 if pair_state.range.end.to_offset(buffer) < range.start {
5518 regions = ®ions[i + 1..];
5519 i = 0;
5520 } else if pair_state.range.start.to_offset(buffer) > range.end {
5521 break;
5522 } else {
5523 if pair_state.selection_id == selection.id {
5524 enclosing = Some(pair_state);
5525 }
5526 i += 1;
5527 }
5528 }
5529
5530 (selection, enclosing)
5531 })
5532 }
5533
5534 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5535 fn invalidate_autoclose_regions(
5536 &mut self,
5537 mut selections: &[Selection<Anchor>],
5538 buffer: &MultiBufferSnapshot,
5539 ) {
5540 self.autoclose_regions.retain(|state| {
5541 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5542 return false;
5543 }
5544
5545 let mut i = 0;
5546 while let Some(selection) = selections.get(i) {
5547 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5548 selections = &selections[1..];
5549 continue;
5550 }
5551 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5552 break;
5553 }
5554 if selection.id == state.selection_id {
5555 return true;
5556 } else {
5557 i += 1;
5558 }
5559 }
5560 false
5561 });
5562 }
5563
5564 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5565 let offset = position.to_offset(buffer);
5566 let (word_range, kind) =
5567 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5568 if offset > word_range.start && kind == Some(CharKind::Word) {
5569 Some(
5570 buffer
5571 .text_for_range(word_range.start..offset)
5572 .collect::<String>(),
5573 )
5574 } else {
5575 None
5576 }
5577 }
5578
5579 pub fn visible_excerpts(
5580 &self,
5581 lsp_related_only: bool,
5582 cx: &mut Context<Editor>,
5583 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5584 let project = self.project().cloned();
5585 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
5586 let multi_buffer = self.buffer().read(cx);
5587 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5588 let multi_buffer_visible_start = self
5589 .scroll_manager
5590 .native_anchor(&display_snapshot, cx)
5591 .anchor
5592 .to_point(&multi_buffer_snapshot);
5593 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5594 multi_buffer_visible_start
5595 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5596 Bias::Left,
5597 );
5598 multi_buffer_snapshot
5599 .range_to_buffer_ranges(multi_buffer_visible_start..=multi_buffer_visible_end)
5600 .into_iter()
5601 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5602 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5603 if !lsp_related_only {
5604 return Some((
5605 excerpt_id,
5606 (
5607 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5608 buffer.version().clone(),
5609 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5610 ),
5611 ));
5612 }
5613
5614 let project = project.as_ref()?.read(cx);
5615 let buffer_file = project::File::from_dyn(buffer.file())?;
5616 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5617 let worktree_entry = buffer_worktree
5618 .read(cx)
5619 .entry_for_id(buffer_file.project_entry_id()?)?;
5620 if worktree_entry.is_ignored {
5621 None
5622 } else {
5623 Some((
5624 excerpt_id,
5625 (
5626 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5627 buffer.version().clone(),
5628 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5629 ),
5630 ))
5631 }
5632 })
5633 .collect()
5634 }
5635
5636 pub fn text_layout_details(&self, window: &mut Window, cx: &mut App) -> TextLayoutDetails {
5637 TextLayoutDetails {
5638 text_system: window.text_system().clone(),
5639 editor_style: self.style.clone().unwrap(),
5640 rem_size: window.rem_size(),
5641 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
5642 visible_rows: self.visible_line_count(),
5643 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5644 }
5645 }
5646
5647 fn trigger_on_type_formatting(
5648 &self,
5649 input: String,
5650 window: &mut Window,
5651 cx: &mut Context<Self>,
5652 ) -> Option<Task<Result<()>>> {
5653 if input.chars().count() != 1 {
5654 return None;
5655 }
5656
5657 let project = self.project()?;
5658 let position = self.selections.newest_anchor().head();
5659 let (buffer, buffer_position) = self
5660 .buffer
5661 .read(cx)
5662 .text_anchor_for_position(position, cx)?;
5663
5664 let settings = language_settings::language_settings(
5665 buffer
5666 .read(cx)
5667 .language_at(buffer_position)
5668 .map(|l| l.name()),
5669 buffer.read(cx).file(),
5670 cx,
5671 );
5672 if !settings.use_on_type_format {
5673 return None;
5674 }
5675
5676 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5677 // hence we do LSP request & edit on host side only — add formats to host's history.
5678 let push_to_lsp_host_history = true;
5679 // If this is not the host, append its history with new edits.
5680 let push_to_client_history = project.read(cx).is_via_collab();
5681
5682 let on_type_formatting = project.update(cx, |project, cx| {
5683 project.on_type_format(
5684 buffer.clone(),
5685 buffer_position,
5686 input,
5687 push_to_lsp_host_history,
5688 cx,
5689 )
5690 });
5691 Some(cx.spawn_in(window, async move |editor, cx| {
5692 if let Some(transaction) = on_type_formatting.await? {
5693 if push_to_client_history {
5694 buffer.update(cx, |buffer, _| {
5695 buffer.push_transaction(transaction, Instant::now());
5696 buffer.finalize_last_transaction();
5697 });
5698 }
5699 editor.update(cx, |editor, cx| {
5700 editor.refresh_document_highlights(cx);
5701 })?;
5702 }
5703 Ok(())
5704 }))
5705 }
5706
5707 pub fn show_word_completions(
5708 &mut self,
5709 _: &ShowWordCompletions,
5710 window: &mut Window,
5711 cx: &mut Context<Self>,
5712 ) {
5713 self.open_or_update_completions_menu(
5714 Some(CompletionsMenuSource::Words {
5715 ignore_threshold: true,
5716 }),
5717 None,
5718 false,
5719 window,
5720 cx,
5721 );
5722 }
5723
5724 pub fn show_completions(
5725 &mut self,
5726 _: &ShowCompletions,
5727 window: &mut Window,
5728 cx: &mut Context<Self>,
5729 ) {
5730 self.open_or_update_completions_menu(None, None, false, window, cx);
5731 }
5732
5733 fn open_or_update_completions_menu(
5734 &mut self,
5735 requested_source: Option<CompletionsMenuSource>,
5736 trigger: Option<String>,
5737 trigger_in_words: bool,
5738 window: &mut Window,
5739 cx: &mut Context<Self>,
5740 ) {
5741 if self.pending_rename.is_some() {
5742 return;
5743 }
5744
5745 let completions_source = self
5746 .context_menu
5747 .borrow()
5748 .as_ref()
5749 .and_then(|menu| match menu {
5750 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5751 CodeContextMenu::CodeActions(_) => None,
5752 });
5753
5754 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5755
5756 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5757 // inserted and selected. To handle that case, the start of the selection is used so that
5758 // the menu starts with all choices.
5759 let position = self
5760 .selections
5761 .newest_anchor()
5762 .start
5763 .bias_right(&multibuffer_snapshot);
5764 if position.diff_base_anchor.is_some() {
5765 return;
5766 }
5767 let buffer_position = multibuffer_snapshot.anchor_before(position);
5768 let Some(buffer) = buffer_position
5769 .text_anchor
5770 .buffer_id
5771 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5772 else {
5773 return;
5774 };
5775 let buffer_snapshot = buffer.read(cx).snapshot();
5776
5777 let menu_is_open = matches!(
5778 self.context_menu.borrow().as_ref(),
5779 Some(CodeContextMenu::Completions(_))
5780 );
5781
5782 let language = buffer_snapshot
5783 .language_at(buffer_position.text_anchor)
5784 .map(|language| language.name());
5785
5786 let language_settings = language_settings(language.clone(), buffer_snapshot.file(), cx);
5787 let completion_settings = language_settings.completions.clone();
5788
5789 let show_completions_on_input = self
5790 .show_completions_on_input_override
5791 .unwrap_or(language_settings.show_completions_on_input);
5792 if !menu_is_open && trigger.is_some() && !show_completions_on_input {
5793 return;
5794 }
5795
5796 let query: Option<Arc<String>> =
5797 Self::completion_query(&multibuffer_snapshot, buffer_position)
5798 .map(|query| query.into());
5799
5800 drop(multibuffer_snapshot);
5801
5802 // Hide the current completions menu when query is empty. Without this, cached
5803 // completions from before the trigger char may be reused (#32774).
5804 if query.is_none() && menu_is_open {
5805 self.hide_context_menu(window, cx);
5806 }
5807
5808 let mut ignore_word_threshold = false;
5809 let provider = match requested_source {
5810 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5811 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5812 ignore_word_threshold = ignore_threshold;
5813 None
5814 }
5815 Some(CompletionsMenuSource::SnippetChoices)
5816 | Some(CompletionsMenuSource::SnippetsOnly) => {
5817 log::error!("bug: SnippetChoices requested_source is not handled");
5818 None
5819 }
5820 };
5821
5822 let sort_completions = provider
5823 .as_ref()
5824 .is_some_and(|provider| provider.sort_completions());
5825
5826 let filter_completions = provider
5827 .as_ref()
5828 .is_none_or(|provider| provider.filter_completions());
5829
5830 let was_snippets_only = matches!(
5831 completions_source,
5832 Some(CompletionsMenuSource::SnippetsOnly)
5833 );
5834
5835 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5836 if filter_completions {
5837 menu.filter(
5838 query.clone().unwrap_or_default(),
5839 buffer_position.text_anchor,
5840 &buffer,
5841 provider.clone(),
5842 window,
5843 cx,
5844 );
5845 }
5846 // When `is_incomplete` is false, no need to re-query completions when the current query
5847 // is a suffix of the initial query.
5848 let was_complete = !menu.is_incomplete;
5849 if was_complete && !was_snippets_only {
5850 // If the new query is a suffix of the old query (typing more characters) and
5851 // the previous result was complete, the existing completions can be filtered.
5852 //
5853 // Note that snippet completions are always complete.
5854 let query_matches = match (&menu.initial_query, &query) {
5855 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5856 (None, _) => true,
5857 _ => false,
5858 };
5859 if query_matches {
5860 let position_matches = if menu.initial_position == position {
5861 true
5862 } else {
5863 let snapshot = self.buffer.read(cx).read(cx);
5864 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5865 };
5866 if position_matches {
5867 return;
5868 }
5869 }
5870 }
5871 };
5872
5873 let Anchor {
5874 excerpt_id: buffer_excerpt_id,
5875 text_anchor: buffer_position,
5876 ..
5877 } = buffer_position;
5878
5879 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5880 buffer_snapshot.surrounding_word(buffer_position, None)
5881 {
5882 let word_to_exclude = buffer_snapshot
5883 .text_for_range(word_range.clone())
5884 .collect::<String>();
5885 (
5886 buffer_snapshot.anchor_before(word_range.start)
5887 ..buffer_snapshot.anchor_after(buffer_position),
5888 Some(word_to_exclude),
5889 )
5890 } else {
5891 (buffer_position..buffer_position, None)
5892 };
5893
5894 let show_completion_documentation = buffer_snapshot
5895 .settings_at(buffer_position, cx)
5896 .show_completion_documentation;
5897
5898 // The document can be large, so stay in reasonable bounds when searching for words,
5899 // otherwise completion pop-up might be slow to appear.
5900 const WORD_LOOKUP_ROWS: u32 = 5_000;
5901 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5902 let min_word_search = buffer_snapshot.clip_point(
5903 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5904 Bias::Left,
5905 );
5906 let max_word_search = buffer_snapshot.clip_point(
5907 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5908 Bias::Right,
5909 );
5910 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5911 ..buffer_snapshot.point_to_offset(max_word_search);
5912
5913 let skip_digits = query
5914 .as_ref()
5915 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5916
5917 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
5918 trigger.as_ref().is_none_or(|trigger| {
5919 provider.is_completion_trigger(
5920 &buffer,
5921 position.text_anchor,
5922 trigger,
5923 trigger_in_words,
5924 cx,
5925 )
5926 })
5927 });
5928
5929 let provider_responses = if let Some(provider) = &provider
5930 && load_provider_completions
5931 {
5932 let trigger_character =
5933 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
5934 let completion_context = CompletionContext {
5935 trigger_kind: match &trigger_character {
5936 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
5937 None => CompletionTriggerKind::INVOKED,
5938 },
5939 trigger_character,
5940 };
5941
5942 provider.completions(
5943 buffer_excerpt_id,
5944 &buffer,
5945 buffer_position,
5946 completion_context,
5947 window,
5948 cx,
5949 )
5950 } else {
5951 Task::ready(Ok(Vec::new()))
5952 };
5953
5954 let load_word_completions = if !self.word_completions_enabled {
5955 false
5956 } else if requested_source
5957 == Some(CompletionsMenuSource::Words {
5958 ignore_threshold: true,
5959 })
5960 {
5961 true
5962 } else {
5963 load_provider_completions
5964 && completion_settings.words != WordsCompletionMode::Disabled
5965 && (ignore_word_threshold || {
5966 let words_min_length = completion_settings.words_min_length;
5967 // check whether word has at least `words_min_length` characters
5968 let query_chars = query.iter().flat_map(|q| q.chars());
5969 query_chars.take(words_min_length).count() == words_min_length
5970 })
5971 };
5972
5973 let mut words = if load_word_completions {
5974 cx.background_spawn({
5975 let buffer_snapshot = buffer_snapshot.clone();
5976 async move {
5977 buffer_snapshot.words_in_range(WordsQuery {
5978 fuzzy_contents: None,
5979 range: word_search_range,
5980 skip_digits,
5981 })
5982 }
5983 })
5984 } else {
5985 Task::ready(BTreeMap::default())
5986 };
5987
5988 let snippets = if let Some(provider) = &provider
5989 && provider.show_snippets()
5990 && let Some(project) = self.project()
5991 {
5992 let char_classifier = buffer_snapshot
5993 .char_classifier_at(buffer_position)
5994 .scope_context(Some(CharScopeContext::Completion));
5995 project.update(cx, |project, cx| {
5996 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
5997 })
5998 } else {
5999 Task::ready(Ok(CompletionResponse {
6000 completions: Vec::new(),
6001 display_options: Default::default(),
6002 is_incomplete: false,
6003 }))
6004 };
6005
6006 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
6007
6008 let id = post_inc(&mut self.next_completion_id);
6009 let task = cx.spawn_in(window, async move |editor, cx| {
6010 let Ok(()) = editor.update(cx, |this, _| {
6011 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
6012 }) else {
6013 return;
6014 };
6015
6016 // TODO: Ideally completions from different sources would be selectively re-queried, so
6017 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
6018 let mut completions = Vec::new();
6019 let mut is_incomplete = false;
6020 let mut display_options: Option<CompletionDisplayOptions> = None;
6021 if let Some(provider_responses) = provider_responses.await.log_err()
6022 && !provider_responses.is_empty()
6023 {
6024 for response in provider_responses {
6025 completions.extend(response.completions);
6026 is_incomplete = is_incomplete || response.is_incomplete;
6027 match display_options.as_mut() {
6028 None => {
6029 display_options = Some(response.display_options);
6030 }
6031 Some(options) => options.merge(&response.display_options),
6032 }
6033 }
6034 if completion_settings.words == WordsCompletionMode::Fallback {
6035 words = Task::ready(BTreeMap::default());
6036 }
6037 }
6038 let display_options = display_options.unwrap_or_default();
6039
6040 let mut words = words.await;
6041 if let Some(word_to_exclude) = &word_to_exclude {
6042 words.remove(word_to_exclude);
6043 }
6044 for lsp_completion in &completions {
6045 words.remove(&lsp_completion.new_text);
6046 }
6047 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
6048 replace_range: word_replace_range.clone(),
6049 new_text: word.clone(),
6050 label: CodeLabel::plain(word, None),
6051 match_start: None,
6052 snippet_deduplication_key: None,
6053 icon_path: None,
6054 documentation: None,
6055 source: CompletionSource::BufferWord {
6056 word_range,
6057 resolved: false,
6058 },
6059 insert_text_mode: Some(InsertTextMode::AS_IS),
6060 confirm: None,
6061 }));
6062
6063 completions.extend(
6064 snippets
6065 .await
6066 .into_iter()
6067 .flat_map(|response| response.completions),
6068 );
6069
6070 let menu = if completions.is_empty() {
6071 None
6072 } else {
6073 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
6074 let languages = editor
6075 .workspace
6076 .as_ref()
6077 .and_then(|(workspace, _)| workspace.upgrade())
6078 .map(|workspace| workspace.read(cx).app_state().languages.clone());
6079 let menu = CompletionsMenu::new(
6080 id,
6081 requested_source.unwrap_or(if load_provider_completions {
6082 CompletionsMenuSource::Normal
6083 } else {
6084 CompletionsMenuSource::SnippetsOnly
6085 }),
6086 sort_completions,
6087 show_completion_documentation,
6088 position,
6089 query.clone(),
6090 is_incomplete,
6091 buffer.clone(),
6092 completions.into(),
6093 editor
6094 .context_menu()
6095 .borrow_mut()
6096 .as_ref()
6097 .map(|menu| menu.primary_scroll_handle()),
6098 display_options,
6099 snippet_sort_order,
6100 languages,
6101 language,
6102 cx,
6103 );
6104
6105 let query = if filter_completions { query } else { None };
6106 let matches_task = menu.do_async_filtering(
6107 query.unwrap_or_default(),
6108 buffer_position,
6109 &buffer,
6110 cx,
6111 );
6112 (menu, matches_task)
6113 }) else {
6114 return;
6115 };
6116
6117 let matches = matches_task.await;
6118
6119 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
6120 // Newer menu already set, so exit.
6121 if let Some(CodeContextMenu::Completions(prev_menu)) =
6122 editor.context_menu.borrow().as_ref()
6123 && prev_menu.id > id
6124 {
6125 return;
6126 };
6127
6128 // Only valid to take prev_menu because either the new menu is immediately set
6129 // below, or the menu is hidden.
6130 if let Some(CodeContextMenu::Completions(prev_menu)) =
6131 editor.context_menu.borrow_mut().take()
6132 {
6133 let position_matches =
6134 if prev_menu.initial_position == menu.initial_position {
6135 true
6136 } else {
6137 let snapshot = editor.buffer.read(cx).read(cx);
6138 prev_menu.initial_position.to_offset(&snapshot)
6139 == menu.initial_position.to_offset(&snapshot)
6140 };
6141 if position_matches {
6142 // Preserve markdown cache before `set_filter_results` because it will
6143 // try to populate the documentation cache.
6144 menu.preserve_markdown_cache(prev_menu);
6145 }
6146 };
6147
6148 menu.set_filter_results(matches, provider, window, cx);
6149 }) else {
6150 return;
6151 };
6152
6153 menu.visible().then_some(menu)
6154 };
6155
6156 editor
6157 .update_in(cx, |editor, window, cx| {
6158 if editor.focus_handle.is_focused(window)
6159 && let Some(menu) = menu
6160 {
6161 *editor.context_menu.borrow_mut() =
6162 Some(CodeContextMenu::Completions(menu));
6163
6164 crate::hover_popover::hide_hover(editor, cx);
6165 if editor.show_edit_predictions_in_menu() {
6166 editor.update_visible_edit_prediction(window, cx);
6167 } else {
6168 editor.discard_edit_prediction(false, cx);
6169 }
6170
6171 cx.notify();
6172 return;
6173 }
6174
6175 if editor.completion_tasks.len() <= 1 {
6176 // If there are no more completion tasks and the last menu was empty, we should hide it.
6177 let was_hidden = editor.hide_context_menu(window, cx).is_none();
6178 // If it was already hidden and we don't show edit predictions in the menu,
6179 // we should also show the edit prediction when available.
6180 if was_hidden && editor.show_edit_predictions_in_menu() {
6181 editor.update_visible_edit_prediction(window, cx);
6182 }
6183 }
6184 })
6185 .ok();
6186 });
6187
6188 self.completion_tasks.push((id, task));
6189 }
6190
6191 #[cfg(feature = "test-support")]
6192 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
6193 let menu = self.context_menu.borrow();
6194 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
6195 let completions = menu.completions.borrow();
6196 Some(completions.to_vec())
6197 } else {
6198 None
6199 }
6200 }
6201
6202 pub fn with_completions_menu_matching_id<R>(
6203 &self,
6204 id: CompletionId,
6205 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
6206 ) -> R {
6207 let mut context_menu = self.context_menu.borrow_mut();
6208 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
6209 return f(None);
6210 };
6211 if completions_menu.id != id {
6212 return f(None);
6213 }
6214 f(Some(completions_menu))
6215 }
6216
6217 pub fn confirm_completion(
6218 &mut self,
6219 action: &ConfirmCompletion,
6220 window: &mut Window,
6221 cx: &mut Context<Self>,
6222 ) -> Option<Task<Result<()>>> {
6223 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6224 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
6225 }
6226
6227 pub fn confirm_completion_insert(
6228 &mut self,
6229 _: &ConfirmCompletionInsert,
6230 window: &mut Window,
6231 cx: &mut Context<Self>,
6232 ) -> Option<Task<Result<()>>> {
6233 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6234 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
6235 }
6236
6237 pub fn confirm_completion_replace(
6238 &mut self,
6239 _: &ConfirmCompletionReplace,
6240 window: &mut Window,
6241 cx: &mut Context<Self>,
6242 ) -> Option<Task<Result<()>>> {
6243 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6244 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
6245 }
6246
6247 pub fn compose_completion(
6248 &mut self,
6249 action: &ComposeCompletion,
6250 window: &mut Window,
6251 cx: &mut Context<Self>,
6252 ) -> Option<Task<Result<()>>> {
6253 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6254 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
6255 }
6256
6257 fn do_completion(
6258 &mut self,
6259 item_ix: Option<usize>,
6260 intent: CompletionIntent,
6261 window: &mut Window,
6262 cx: &mut Context<Editor>,
6263 ) -> Option<Task<Result<()>>> {
6264 use language::ToOffset as _;
6265
6266 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
6267 else {
6268 return None;
6269 };
6270
6271 let candidate_id = {
6272 let entries = completions_menu.entries.borrow();
6273 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6274 if self.show_edit_predictions_in_menu() {
6275 self.discard_edit_prediction(true, cx);
6276 }
6277 mat.candidate_id
6278 };
6279
6280 let completion = completions_menu
6281 .completions
6282 .borrow()
6283 .get(candidate_id)?
6284 .clone();
6285 cx.stop_propagation();
6286
6287 let buffer_handle = completions_menu.buffer.clone();
6288
6289 let CompletionEdit {
6290 new_text,
6291 snippet,
6292 replace_range,
6293 } = process_completion_for_edit(
6294 &completion,
6295 intent,
6296 &buffer_handle,
6297 &completions_menu.initial_position.text_anchor,
6298 cx,
6299 );
6300
6301 let buffer = buffer_handle.read(cx);
6302 let snapshot = self.buffer.read(cx).snapshot(cx);
6303 let newest_anchor = self.selections.newest_anchor();
6304 let replace_range_multibuffer = {
6305 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6306 excerpt.map_range_from_buffer(replace_range.clone())
6307 };
6308 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6309 return None;
6310 }
6311
6312 let old_text = buffer
6313 .text_for_range(replace_range.clone())
6314 .collect::<String>();
6315 let lookbehind = newest_anchor
6316 .start
6317 .text_anchor
6318 .to_offset(buffer)
6319 .saturating_sub(replace_range.start.0);
6320 let lookahead = replace_range
6321 .end
6322 .0
6323 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6324 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6325 let suffix = &old_text[lookbehind.min(old_text.len())..];
6326
6327 let selections = self
6328 .selections
6329 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6330 let mut ranges = Vec::new();
6331 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6332
6333 for selection in &selections {
6334 let range = if selection.id == newest_anchor.id {
6335 replace_range_multibuffer.clone()
6336 } else {
6337 let mut range = selection.range();
6338
6339 // if prefix is present, don't duplicate it
6340 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6341 range.start = range.start.saturating_sub_usize(lookbehind);
6342
6343 // if suffix is also present, mimic the newest cursor and replace it
6344 if selection.id != newest_anchor.id
6345 && snapshot.contains_str_at(range.end, suffix)
6346 {
6347 range.end += lookahead;
6348 }
6349 }
6350 range
6351 };
6352
6353 ranges.push(range.clone());
6354
6355 if !self.linked_edit_ranges.is_empty() {
6356 let start_anchor = snapshot.anchor_before(range.start);
6357 let end_anchor = snapshot.anchor_after(range.end);
6358 if let Some(ranges) = self
6359 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6360 {
6361 for (buffer, edits) in ranges {
6362 linked_edits
6363 .entry(buffer.clone())
6364 .or_default()
6365 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6366 }
6367 }
6368 }
6369 }
6370
6371 let common_prefix_len = old_text
6372 .chars()
6373 .zip(new_text.chars())
6374 .take_while(|(a, b)| a == b)
6375 .map(|(a, _)| a.len_utf8())
6376 .sum::<usize>();
6377
6378 cx.emit(EditorEvent::InputHandled {
6379 utf16_range_to_replace: None,
6380 text: new_text[common_prefix_len..].into(),
6381 });
6382
6383 self.transact(window, cx, |editor, window, cx| {
6384 if let Some(mut snippet) = snippet {
6385 snippet.text = new_text.to_string();
6386 editor
6387 .insert_snippet(&ranges, snippet, window, cx)
6388 .log_err();
6389 } else {
6390 editor.buffer.update(cx, |multi_buffer, cx| {
6391 let auto_indent = match completion.insert_text_mode {
6392 Some(InsertTextMode::AS_IS) => None,
6393 _ => editor.autoindent_mode.clone(),
6394 };
6395 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6396 multi_buffer.edit(edits, auto_indent, cx);
6397 });
6398 }
6399 for (buffer, edits) in linked_edits {
6400 buffer.update(cx, |buffer, cx| {
6401 let snapshot = buffer.snapshot();
6402 let edits = edits
6403 .into_iter()
6404 .map(|(range, text)| {
6405 use text::ToPoint as TP;
6406 let end_point = TP::to_point(&range.end, &snapshot);
6407 let start_point = TP::to_point(&range.start, &snapshot);
6408 (start_point..end_point, text)
6409 })
6410 .sorted_by_key(|(range, _)| range.start);
6411 buffer.edit(edits, None, cx);
6412 })
6413 }
6414
6415 editor.refresh_edit_prediction(true, false, window, cx);
6416 });
6417 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6418
6419 let show_new_completions_on_confirm = completion
6420 .confirm
6421 .as_ref()
6422 .is_some_and(|confirm| confirm(intent, window, cx));
6423 if show_new_completions_on_confirm {
6424 self.open_or_update_completions_menu(None, None, false, window, cx);
6425 }
6426
6427 let provider = self.completion_provider.as_ref()?;
6428
6429 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6430 let command = lsp_store.as_ref().and_then(|lsp_store| {
6431 let CompletionSource::Lsp {
6432 lsp_completion,
6433 server_id,
6434 ..
6435 } = &completion.source
6436 else {
6437 return None;
6438 };
6439 let lsp_command = lsp_completion.command.as_ref()?;
6440 let available_commands = lsp_store
6441 .read(cx)
6442 .lsp_server_capabilities
6443 .get(server_id)
6444 .and_then(|server_capabilities| {
6445 server_capabilities
6446 .execute_command_provider
6447 .as_ref()
6448 .map(|options| options.commands.as_slice())
6449 })?;
6450 if available_commands.contains(&lsp_command.command) {
6451 Some(CodeAction {
6452 server_id: *server_id,
6453 range: language::Anchor::MIN..language::Anchor::MIN,
6454 lsp_action: LspAction::Command(lsp_command.clone()),
6455 resolved: false,
6456 })
6457 } else {
6458 None
6459 }
6460 });
6461
6462 drop(completion);
6463 let apply_edits = provider.apply_additional_edits_for_completion(
6464 buffer_handle.clone(),
6465 completions_menu.completions.clone(),
6466 candidate_id,
6467 true,
6468 cx,
6469 );
6470
6471 let editor_settings = EditorSettings::get_global(cx);
6472 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6473 // After the code completion is finished, users often want to know what signatures are needed.
6474 // so we should automatically call signature_help
6475 self.show_signature_help(&ShowSignatureHelp, window, cx);
6476 }
6477
6478 Some(cx.spawn_in(window, async move |editor, cx| {
6479 apply_edits.await?;
6480
6481 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6482 let title = command.lsp_action.title().to_owned();
6483 let project_transaction = lsp_store
6484 .update(cx, |lsp_store, cx| {
6485 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6486 })
6487 .await
6488 .context("applying post-completion command")?;
6489 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6490 Self::open_project_transaction(
6491 &editor,
6492 workspace.downgrade(),
6493 project_transaction,
6494 title,
6495 cx,
6496 )
6497 .await?;
6498 }
6499 }
6500
6501 Ok(())
6502 }))
6503 }
6504
6505 pub fn toggle_code_actions(
6506 &mut self,
6507 action: &ToggleCodeActions,
6508 window: &mut Window,
6509 cx: &mut Context<Self>,
6510 ) {
6511 let quick_launch = action.quick_launch;
6512 let mut context_menu = self.context_menu.borrow_mut();
6513 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6514 if code_actions.deployed_from == action.deployed_from {
6515 // Toggle if we're selecting the same one
6516 *context_menu = None;
6517 cx.notify();
6518 return;
6519 } else {
6520 // Otherwise, clear it and start a new one
6521 *context_menu = None;
6522 cx.notify();
6523 }
6524 }
6525 drop(context_menu);
6526 let snapshot = self.snapshot(window, cx);
6527 let deployed_from = action.deployed_from.clone();
6528 let action = action.clone();
6529 self.completion_tasks.clear();
6530 self.discard_edit_prediction(false, cx);
6531
6532 let multibuffer_point = match &action.deployed_from {
6533 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6534 DisplayPoint::new(*row, 0).to_point(&snapshot)
6535 }
6536 _ => self
6537 .selections
6538 .newest::<Point>(&snapshot.display_snapshot)
6539 .head(),
6540 };
6541 let Some((buffer, buffer_row)) = snapshot
6542 .buffer_snapshot()
6543 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6544 .and_then(|(buffer_snapshot, range)| {
6545 self.buffer()
6546 .read(cx)
6547 .buffer(buffer_snapshot.remote_id())
6548 .map(|buffer| (buffer, range.start.row))
6549 })
6550 else {
6551 return;
6552 };
6553 let buffer_id = buffer.read(cx).remote_id();
6554 let tasks = self
6555 .tasks
6556 .get(&(buffer_id, buffer_row))
6557 .map(|t| Arc::new(t.to_owned()));
6558
6559 if !self.focus_handle.is_focused(window) {
6560 return;
6561 }
6562 let project = self.project.clone();
6563
6564 let code_actions_task = match deployed_from {
6565 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6566 _ => self.code_actions(buffer_row, window, cx),
6567 };
6568
6569 let runnable_task = match deployed_from {
6570 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6571 _ => {
6572 let mut task_context_task = Task::ready(None);
6573 if let Some(tasks) = &tasks
6574 && let Some(project) = project
6575 {
6576 task_context_task =
6577 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6578 }
6579
6580 cx.spawn_in(window, {
6581 let buffer = buffer.clone();
6582 async move |editor, cx| {
6583 let task_context = task_context_task.await;
6584
6585 let resolved_tasks =
6586 tasks
6587 .zip(task_context.clone())
6588 .map(|(tasks, task_context)| ResolvedTasks {
6589 templates: tasks.resolve(&task_context).collect(),
6590 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6591 multibuffer_point.row,
6592 tasks.column,
6593 )),
6594 });
6595 let debug_scenarios = editor
6596 .update(cx, |editor, cx| {
6597 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6598 })?
6599 .await;
6600 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6601 }
6602 })
6603 }
6604 };
6605
6606 cx.spawn_in(window, async move |editor, cx| {
6607 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6608 let code_actions = code_actions_task.await;
6609 let spawn_straight_away = quick_launch
6610 && resolved_tasks
6611 .as_ref()
6612 .is_some_and(|tasks| tasks.templates.len() == 1)
6613 && code_actions
6614 .as_ref()
6615 .is_none_or(|actions| actions.is_empty())
6616 && debug_scenarios.is_empty();
6617
6618 editor.update_in(cx, |editor, window, cx| {
6619 crate::hover_popover::hide_hover(editor, cx);
6620 let actions = CodeActionContents::new(
6621 resolved_tasks,
6622 code_actions,
6623 debug_scenarios,
6624 task_context.unwrap_or_default(),
6625 );
6626
6627 // Don't show the menu if there are no actions available
6628 if actions.is_empty() {
6629 cx.notify();
6630 return Task::ready(Ok(()));
6631 }
6632
6633 *editor.context_menu.borrow_mut() =
6634 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6635 buffer,
6636 actions,
6637 selected_item: Default::default(),
6638 scroll_handle: UniformListScrollHandle::default(),
6639 deployed_from,
6640 }));
6641 cx.notify();
6642 if spawn_straight_away
6643 && let Some(task) = editor.confirm_code_action(
6644 &ConfirmCodeAction { item_ix: Some(0) },
6645 window,
6646 cx,
6647 )
6648 {
6649 return task;
6650 }
6651
6652 Task::ready(Ok(()))
6653 })
6654 })
6655 .detach_and_log_err(cx);
6656 }
6657
6658 fn debug_scenarios(
6659 &mut self,
6660 resolved_tasks: &Option<ResolvedTasks>,
6661 buffer: &Entity<Buffer>,
6662 cx: &mut App,
6663 ) -> Task<Vec<task::DebugScenario>> {
6664 maybe!({
6665 let project = self.project()?;
6666 let dap_store = project.read(cx).dap_store();
6667 let mut scenarios = vec![];
6668 let resolved_tasks = resolved_tasks.as_ref()?;
6669 let buffer = buffer.read(cx);
6670 let language = buffer.language()?;
6671 let file = buffer.file();
6672 let debug_adapter = language_settings(language.name().into(), file, cx)
6673 .debuggers
6674 .first()
6675 .map(SharedString::from)
6676 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6677
6678 dap_store.update(cx, |dap_store, cx| {
6679 for (_, task) in &resolved_tasks.templates {
6680 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6681 task.original_task().clone(),
6682 debug_adapter.clone().into(),
6683 task.display_label().to_owned().into(),
6684 cx,
6685 );
6686 scenarios.push(maybe_scenario);
6687 }
6688 });
6689 Some(cx.background_spawn(async move {
6690 futures::future::join_all(scenarios)
6691 .await
6692 .into_iter()
6693 .flatten()
6694 .collect::<Vec<_>>()
6695 }))
6696 })
6697 .unwrap_or_else(|| Task::ready(vec![]))
6698 }
6699
6700 fn code_actions(
6701 &mut self,
6702 buffer_row: u32,
6703 window: &mut Window,
6704 cx: &mut Context<Self>,
6705 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6706 let mut task = self.code_actions_task.take();
6707 cx.spawn_in(window, async move |editor, cx| {
6708 while let Some(prev_task) = task {
6709 prev_task.await.log_err();
6710 task = editor
6711 .update(cx, |this, _| this.code_actions_task.take())
6712 .ok()?;
6713 }
6714
6715 editor
6716 .update(cx, |editor, cx| {
6717 editor
6718 .available_code_actions
6719 .clone()
6720 .and_then(|(location, code_actions)| {
6721 let snapshot = location.buffer.read(cx).snapshot();
6722 let point_range = location.range.to_point(&snapshot);
6723 let point_range = point_range.start.row..=point_range.end.row;
6724 if point_range.contains(&buffer_row) {
6725 Some(code_actions)
6726 } else {
6727 None
6728 }
6729 })
6730 })
6731 .ok()
6732 .flatten()
6733 })
6734 }
6735
6736 pub fn confirm_code_action(
6737 &mut self,
6738 action: &ConfirmCodeAction,
6739 window: &mut Window,
6740 cx: &mut Context<Self>,
6741 ) -> Option<Task<Result<()>>> {
6742 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6743
6744 let actions_menu =
6745 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6746 menu
6747 } else {
6748 return None;
6749 };
6750
6751 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6752 let action = actions_menu.actions.get(action_ix)?;
6753 let title = action.label();
6754 let buffer = actions_menu.buffer;
6755 let workspace = self.workspace()?;
6756
6757 match action {
6758 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6759 workspace.update(cx, |workspace, cx| {
6760 workspace.schedule_resolved_task(
6761 task_source_kind,
6762 resolved_task,
6763 false,
6764 window,
6765 cx,
6766 );
6767
6768 Some(Task::ready(Ok(())))
6769 })
6770 }
6771 CodeActionsItem::CodeAction {
6772 excerpt_id,
6773 action,
6774 provider,
6775 } => {
6776 let apply_code_action =
6777 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6778 let workspace = workspace.downgrade();
6779 Some(cx.spawn_in(window, async move |editor, cx| {
6780 let project_transaction = apply_code_action.await?;
6781 Self::open_project_transaction(
6782 &editor,
6783 workspace,
6784 project_transaction,
6785 title,
6786 cx,
6787 )
6788 .await
6789 }))
6790 }
6791 CodeActionsItem::DebugScenario(scenario) => {
6792 let context = actions_menu.actions.context;
6793
6794 workspace.update(cx, |workspace, cx| {
6795 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6796 workspace.start_debug_session(
6797 scenario,
6798 context,
6799 Some(buffer),
6800 None,
6801 window,
6802 cx,
6803 );
6804 });
6805 Some(Task::ready(Ok(())))
6806 }
6807 }
6808 }
6809
6810 fn open_transaction_for_hidden_buffers(
6811 workspace: Entity<Workspace>,
6812 transaction: ProjectTransaction,
6813 title: String,
6814 window: &mut Window,
6815 cx: &mut Context<Self>,
6816 ) {
6817 if transaction.0.is_empty() {
6818 return;
6819 }
6820
6821 let edited_buffers_already_open = {
6822 let other_editors: Vec<Entity<Editor>> = workspace
6823 .read(cx)
6824 .panes()
6825 .iter()
6826 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
6827 .filter(|editor| editor.entity_id() != cx.entity_id())
6828 .collect();
6829
6830 transaction.0.keys().all(|buffer| {
6831 other_editors.iter().any(|editor| {
6832 let multi_buffer = editor.read(cx).buffer();
6833 multi_buffer.read(cx).is_singleton()
6834 && multi_buffer
6835 .read(cx)
6836 .as_singleton()
6837 .map_or(false, |singleton| {
6838 singleton.entity_id() == buffer.entity_id()
6839 })
6840 })
6841 })
6842 };
6843 if !edited_buffers_already_open {
6844 let workspace = workspace.downgrade();
6845 cx.defer_in(window, move |_, window, cx| {
6846 cx.spawn_in(window, async move |editor, cx| {
6847 Self::open_project_transaction(&editor, workspace, transaction, title, cx)
6848 .await
6849 .ok()
6850 })
6851 .detach();
6852 });
6853 }
6854 }
6855
6856 pub async fn open_project_transaction(
6857 editor: &WeakEntity<Editor>,
6858 workspace: WeakEntity<Workspace>,
6859 transaction: ProjectTransaction,
6860 title: String,
6861 cx: &mut AsyncWindowContext,
6862 ) -> Result<()> {
6863 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6864 cx.update(|_, cx| {
6865 entries.sort_unstable_by_key(|(buffer, _)| {
6866 buffer.read(cx).file().map(|f| f.path().clone())
6867 });
6868 })?;
6869 if entries.is_empty() {
6870 return Ok(());
6871 }
6872
6873 // If the project transaction's edits are all contained within this editor, then
6874 // avoid opening a new editor to display them.
6875
6876 if let [(buffer, transaction)] = &*entries {
6877 let excerpt = editor.update(cx, |editor, cx| {
6878 editor
6879 .buffer()
6880 .read(cx)
6881 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6882 })?;
6883 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6884 && excerpted_buffer == *buffer
6885 {
6886 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6887 let excerpt_range = excerpt_range.to_offset(buffer);
6888 buffer
6889 .edited_ranges_for_transaction::<usize>(transaction)
6890 .all(|range| {
6891 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6892 })
6893 });
6894
6895 if all_edits_within_excerpt {
6896 return Ok(());
6897 }
6898 }
6899 }
6900
6901 let mut ranges_to_highlight = Vec::new();
6902 let excerpt_buffer = cx.new(|cx| {
6903 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6904 for (buffer_handle, transaction) in &entries {
6905 let edited_ranges = buffer_handle
6906 .read(cx)
6907 .edited_ranges_for_transaction::<Point>(transaction)
6908 .collect::<Vec<_>>();
6909 let (ranges, _) = multibuffer.set_excerpts_for_path(
6910 PathKey::for_buffer(buffer_handle, cx),
6911 buffer_handle.clone(),
6912 edited_ranges,
6913 multibuffer_context_lines(cx),
6914 cx,
6915 );
6916
6917 ranges_to_highlight.extend(ranges);
6918 }
6919 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6920 multibuffer
6921 });
6922
6923 workspace.update_in(cx, |workspace, window, cx| {
6924 let project = workspace.project().clone();
6925 let editor =
6926 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6927 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6928 editor.update(cx, |editor, cx| {
6929 editor.highlight_background::<Self>(
6930 &ranges_to_highlight,
6931 |_, theme| theme.colors().editor_highlighted_line_background,
6932 cx,
6933 );
6934 });
6935 })?;
6936
6937 Ok(())
6938 }
6939
6940 pub fn clear_code_action_providers(&mut self) {
6941 self.code_action_providers.clear();
6942 self.available_code_actions.take();
6943 }
6944
6945 pub fn add_code_action_provider(
6946 &mut self,
6947 provider: Rc<dyn CodeActionProvider>,
6948 window: &mut Window,
6949 cx: &mut Context<Self>,
6950 ) {
6951 if self
6952 .code_action_providers
6953 .iter()
6954 .any(|existing_provider| existing_provider.id() == provider.id())
6955 {
6956 return;
6957 }
6958
6959 self.code_action_providers.push(provider);
6960 self.refresh_code_actions(window, cx);
6961 }
6962
6963 pub fn remove_code_action_provider(
6964 &mut self,
6965 id: Arc<str>,
6966 window: &mut Window,
6967 cx: &mut Context<Self>,
6968 ) {
6969 self.code_action_providers
6970 .retain(|provider| provider.id() != id);
6971 self.refresh_code_actions(window, cx);
6972 }
6973
6974 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6975 !self.code_action_providers.is_empty()
6976 && EditorSettings::get_global(cx).toolbar.code_actions
6977 }
6978
6979 pub fn has_available_code_actions(&self) -> bool {
6980 self.available_code_actions
6981 .as_ref()
6982 .is_some_and(|(_, actions)| !actions.is_empty())
6983 }
6984
6985 fn render_inline_code_actions(
6986 &self,
6987 icon_size: ui::IconSize,
6988 display_row: DisplayRow,
6989 is_active: bool,
6990 cx: &mut Context<Self>,
6991 ) -> AnyElement {
6992 let show_tooltip = !self.context_menu_visible();
6993 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6994 .icon_size(icon_size)
6995 .shape(ui::IconButtonShape::Square)
6996 .icon_color(ui::Color::Hidden)
6997 .toggle_state(is_active)
6998 .when(show_tooltip, |this| {
6999 this.tooltip({
7000 let focus_handle = self.focus_handle.clone();
7001 move |_window, cx| {
7002 Tooltip::for_action_in(
7003 "Toggle Code Actions",
7004 &ToggleCodeActions {
7005 deployed_from: None,
7006 quick_launch: false,
7007 },
7008 &focus_handle,
7009 cx,
7010 )
7011 }
7012 })
7013 })
7014 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
7015 window.focus(&editor.focus_handle(cx), cx);
7016 editor.toggle_code_actions(
7017 &crate::actions::ToggleCodeActions {
7018 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
7019 display_row,
7020 )),
7021 quick_launch: false,
7022 },
7023 window,
7024 cx,
7025 );
7026 }))
7027 .into_any_element()
7028 }
7029
7030 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
7031 &self.context_menu
7032 }
7033
7034 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7035 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
7036 cx.background_executor()
7037 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
7038 .await;
7039
7040 let (start_buffer, start, _, end, newest_selection) = this
7041 .update(cx, |this, cx| {
7042 let newest_selection = this.selections.newest_anchor().clone();
7043 if newest_selection.head().diff_base_anchor.is_some() {
7044 return None;
7045 }
7046 let display_snapshot = this.display_snapshot(cx);
7047 let newest_selection_adjusted =
7048 this.selections.newest_adjusted(&display_snapshot);
7049 let buffer = this.buffer.read(cx);
7050
7051 let (start_buffer, start) =
7052 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
7053 let (end_buffer, end) =
7054 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
7055
7056 Some((start_buffer, start, end_buffer, end, newest_selection))
7057 })?
7058 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
7059 .context(
7060 "Expected selection to lie in a single buffer when refreshing code actions",
7061 )?;
7062 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
7063 let providers = this.code_action_providers.clone();
7064 let tasks = this
7065 .code_action_providers
7066 .iter()
7067 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
7068 .collect::<Vec<_>>();
7069 (providers, tasks)
7070 })?;
7071
7072 let mut actions = Vec::new();
7073 for (provider, provider_actions) in
7074 providers.into_iter().zip(future::join_all(tasks).await)
7075 {
7076 if let Some(provider_actions) = provider_actions.log_err() {
7077 actions.extend(provider_actions.into_iter().map(|action| {
7078 AvailableCodeAction {
7079 excerpt_id: newest_selection.start.excerpt_id,
7080 action,
7081 provider: provider.clone(),
7082 }
7083 }));
7084 }
7085 }
7086
7087 this.update(cx, |this, cx| {
7088 this.available_code_actions = if actions.is_empty() {
7089 None
7090 } else {
7091 Some((
7092 Location {
7093 buffer: start_buffer,
7094 range: start..end,
7095 },
7096 actions.into(),
7097 ))
7098 };
7099 cx.notify();
7100 })
7101 }));
7102 }
7103
7104 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7105 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
7106 self.show_git_blame_inline = false;
7107
7108 self.show_git_blame_inline_delay_task =
7109 Some(cx.spawn_in(window, async move |this, cx| {
7110 cx.background_executor().timer(delay).await;
7111
7112 this.update(cx, |this, cx| {
7113 this.show_git_blame_inline = true;
7114 cx.notify();
7115 })
7116 .log_err();
7117 }));
7118 }
7119 }
7120
7121 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
7122 let snapshot = self.snapshot(window, cx);
7123 let cursor = self
7124 .selections
7125 .newest::<Point>(&snapshot.display_snapshot)
7126 .head();
7127 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
7128 else {
7129 return;
7130 };
7131
7132 if self.blame.is_none() {
7133 self.start_git_blame(true, window, cx);
7134 }
7135 let Some(blame) = self.blame.as_ref() else {
7136 return;
7137 };
7138
7139 let row_info = RowInfo {
7140 buffer_id: Some(buffer.remote_id()),
7141 buffer_row: Some(point.row),
7142 ..Default::default()
7143 };
7144 let Some((buffer, blame_entry)) = blame
7145 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
7146 .flatten()
7147 else {
7148 return;
7149 };
7150
7151 let anchor = self.selections.newest_anchor().head();
7152 let position = self.to_pixel_point(anchor, &snapshot, window, cx);
7153 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
7154 self.show_blame_popover(
7155 buffer,
7156 &blame_entry,
7157 position + last_bounds.origin,
7158 true,
7159 cx,
7160 );
7161 };
7162 }
7163
7164 fn show_blame_popover(
7165 &mut self,
7166 buffer: BufferId,
7167 blame_entry: &BlameEntry,
7168 position: gpui::Point<Pixels>,
7169 ignore_timeout: bool,
7170 cx: &mut Context<Self>,
7171 ) {
7172 if let Some(state) = &mut self.inline_blame_popover {
7173 state.hide_task.take();
7174 } else {
7175 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
7176 let blame_entry = blame_entry.clone();
7177 let show_task = cx.spawn(async move |editor, cx| {
7178 if !ignore_timeout {
7179 cx.background_executor()
7180 .timer(std::time::Duration::from_millis(blame_popover_delay))
7181 .await;
7182 }
7183 editor
7184 .update(cx, |editor, cx| {
7185 editor.inline_blame_popover_show_task.take();
7186 let Some(blame) = editor.blame.as_ref() else {
7187 return;
7188 };
7189 let blame = blame.read(cx);
7190 let details = blame.details_for_entry(buffer, &blame_entry);
7191 let markdown = cx.new(|cx| {
7192 Markdown::new(
7193 details
7194 .as_ref()
7195 .map(|message| message.message.clone())
7196 .unwrap_or_default(),
7197 None,
7198 None,
7199 cx,
7200 )
7201 });
7202 editor.inline_blame_popover = Some(InlineBlamePopover {
7203 position,
7204 hide_task: None,
7205 popover_bounds: None,
7206 popover_state: InlineBlamePopoverState {
7207 scroll_handle: ScrollHandle::new(),
7208 commit_message: details,
7209 markdown,
7210 },
7211 keyboard_grace: ignore_timeout,
7212 });
7213 cx.notify();
7214 })
7215 .ok();
7216 });
7217 self.inline_blame_popover_show_task = Some(show_task);
7218 }
7219 }
7220
7221 pub fn has_mouse_context_menu(&self) -> bool {
7222 self.mouse_context_menu.is_some()
7223 }
7224
7225 pub fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
7226 self.inline_blame_popover_show_task.take();
7227 if let Some(state) = &mut self.inline_blame_popover {
7228 let hide_task = cx.spawn(async move |editor, cx| {
7229 if !ignore_timeout {
7230 cx.background_executor()
7231 .timer(std::time::Duration::from_millis(100))
7232 .await;
7233 }
7234 editor
7235 .update(cx, |editor, cx| {
7236 editor.inline_blame_popover.take();
7237 cx.notify();
7238 })
7239 .ok();
7240 });
7241 state.hide_task = Some(hide_task);
7242 true
7243 } else {
7244 false
7245 }
7246 }
7247
7248 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
7249 if self.pending_rename.is_some() {
7250 return None;
7251 }
7252
7253 let provider = self.semantics_provider.clone()?;
7254 let buffer = self.buffer.read(cx);
7255 let newest_selection = self.selections.newest_anchor().clone();
7256 let cursor_position = newest_selection.head();
7257 let (cursor_buffer, cursor_buffer_position) =
7258 buffer.text_anchor_for_position(cursor_position, cx)?;
7259 let (tail_buffer, tail_buffer_position) =
7260 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
7261 if cursor_buffer != tail_buffer {
7262 return None;
7263 }
7264
7265 let snapshot = cursor_buffer.read(cx).snapshot();
7266 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
7267 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
7268 if start_word_range != end_word_range {
7269 self.document_highlights_task.take();
7270 self.clear_background_highlights::<DocumentHighlightRead>(cx);
7271 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
7272 return None;
7273 }
7274
7275 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
7276 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
7277 cx.background_executor()
7278 .timer(Duration::from_millis(debounce))
7279 .await;
7280
7281 let highlights = if let Some(highlights) = cx.update(|cx| {
7282 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
7283 }) {
7284 highlights.await.log_err()
7285 } else {
7286 None
7287 };
7288
7289 if let Some(highlights) = highlights {
7290 this.update(cx, |this, cx| {
7291 if this.pending_rename.is_some() {
7292 return;
7293 }
7294
7295 let buffer = this.buffer.read(cx);
7296 if buffer
7297 .text_anchor_for_position(cursor_position, cx)
7298 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
7299 {
7300 return;
7301 }
7302
7303 let cursor_buffer_snapshot = cursor_buffer.read(cx);
7304 let mut write_ranges = Vec::new();
7305 let mut read_ranges = Vec::new();
7306 for highlight in highlights {
7307 let buffer_id = cursor_buffer.read(cx).remote_id();
7308 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
7309 {
7310 let start = highlight
7311 .range
7312 .start
7313 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
7314 let end = highlight
7315 .range
7316 .end
7317 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
7318 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
7319 continue;
7320 }
7321
7322 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
7323 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7324 write_ranges.push(range);
7325 } else {
7326 read_ranges.push(range);
7327 }
7328 }
7329 }
7330
7331 this.highlight_background::<DocumentHighlightRead>(
7332 &read_ranges,
7333 |_, theme| theme.colors().editor_document_highlight_read_background,
7334 cx,
7335 );
7336 this.highlight_background::<DocumentHighlightWrite>(
7337 &write_ranges,
7338 |_, theme| theme.colors().editor_document_highlight_write_background,
7339 cx,
7340 );
7341 cx.notify();
7342 })
7343 .log_err();
7344 }
7345 }));
7346 None
7347 }
7348
7349 fn prepare_highlight_query_from_selection(
7350 &mut self,
7351 window: &Window,
7352 cx: &mut Context<Editor>,
7353 ) -> Option<(String, Range<Anchor>)> {
7354 if matches!(self.mode, EditorMode::SingleLine) {
7355 return None;
7356 }
7357 if !EditorSettings::get_global(cx).selection_highlight {
7358 return None;
7359 }
7360 if self.selections.count() != 1 || self.selections.line_mode() {
7361 return None;
7362 }
7363 let snapshot = self.snapshot(window, cx);
7364 let selection = self.selections.newest::<Point>(&snapshot);
7365 // If the selection spans multiple rows OR it is empty
7366 if selection.start.row != selection.end.row
7367 || selection.start.column == selection.end.column
7368 {
7369 return None;
7370 }
7371 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7372 let query = snapshot
7373 .buffer_snapshot()
7374 .text_for_range(selection_anchor_range.clone())
7375 .collect::<String>();
7376 if query.trim().is_empty() {
7377 return None;
7378 }
7379 Some((query, selection_anchor_range))
7380 }
7381
7382 #[ztracing::instrument(skip_all)]
7383 fn update_selection_occurrence_highlights(
7384 &mut self,
7385 query_text: String,
7386 query_range: Range<Anchor>,
7387 multi_buffer_range_to_query: Range<Point>,
7388 use_debounce: bool,
7389 window: &mut Window,
7390 cx: &mut Context<Editor>,
7391 ) -> Task<()> {
7392 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7393 cx.spawn_in(window, async move |editor, cx| {
7394 if use_debounce {
7395 cx.background_executor()
7396 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7397 .await;
7398 }
7399 let match_task = cx.background_spawn(async move {
7400 let buffer_ranges = multi_buffer_snapshot
7401 .range_to_buffer_ranges(
7402 multi_buffer_range_to_query.start..=multi_buffer_range_to_query.end,
7403 )
7404 .into_iter()
7405 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7406 let mut match_ranges = Vec::new();
7407 let Ok(regex) = project::search::SearchQuery::text(
7408 query_text.clone(),
7409 false,
7410 false,
7411 false,
7412 Default::default(),
7413 Default::default(),
7414 false,
7415 None,
7416 ) else {
7417 return Vec::default();
7418 };
7419 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7420 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7421 match_ranges.extend(
7422 regex
7423 .search(
7424 buffer_snapshot,
7425 Some(search_range.start.0..search_range.end.0),
7426 )
7427 .await
7428 .into_iter()
7429 .filter_map(|match_range| {
7430 let match_start = buffer_snapshot
7431 .anchor_after(search_range.start + match_range.start);
7432 let match_end = buffer_snapshot
7433 .anchor_before(search_range.start + match_range.end);
7434 let match_anchor_range =
7435 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7436 (match_anchor_range != query_range).then_some(match_anchor_range)
7437 }),
7438 );
7439 }
7440 match_ranges
7441 });
7442 let match_ranges = match_task.await;
7443 editor
7444 .update_in(cx, |editor, _, cx| {
7445 if use_debounce {
7446 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7447 editor.debounced_selection_highlight_complete = true;
7448 } else if editor.debounced_selection_highlight_complete {
7449 return;
7450 }
7451 if !match_ranges.is_empty() {
7452 editor.highlight_background::<SelectedTextHighlight>(
7453 &match_ranges,
7454 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7455 cx,
7456 )
7457 }
7458 })
7459 .log_err();
7460 })
7461 }
7462
7463 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7464 struct NewlineFold;
7465 let type_id = std::any::TypeId::of::<NewlineFold>();
7466 if !self.mode.is_single_line() {
7467 return;
7468 }
7469 let snapshot = self.snapshot(window, cx);
7470 if snapshot.buffer_snapshot().max_point().row == 0 {
7471 return;
7472 }
7473 let task = cx.background_spawn(async move {
7474 let new_newlines = snapshot
7475 .buffer_chars_at(MultiBufferOffset(0))
7476 .filter_map(|(c, i)| {
7477 if c == '\n' {
7478 Some(
7479 snapshot.buffer_snapshot().anchor_after(i)
7480 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7481 )
7482 } else {
7483 None
7484 }
7485 })
7486 .collect::<Vec<_>>();
7487 let existing_newlines = snapshot
7488 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7489 .filter_map(|fold| {
7490 if fold.placeholder.type_tag == Some(type_id) {
7491 Some(fold.range.start..fold.range.end)
7492 } else {
7493 None
7494 }
7495 })
7496 .collect::<Vec<_>>();
7497
7498 (new_newlines, existing_newlines)
7499 });
7500 self.folding_newlines = cx.spawn(async move |this, cx| {
7501 let (new_newlines, existing_newlines) = task.await;
7502 if new_newlines == existing_newlines {
7503 return;
7504 }
7505 let placeholder = FoldPlaceholder {
7506 render: Arc::new(move |_, _, cx| {
7507 div()
7508 .bg(cx.theme().status().hint_background)
7509 .border_b_1()
7510 .size_full()
7511 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7512 .border_color(cx.theme().status().hint)
7513 .child("\\n")
7514 .into_any()
7515 }),
7516 constrain_width: false,
7517 merge_adjacent: false,
7518 type_tag: Some(type_id),
7519 };
7520 let creases = new_newlines
7521 .into_iter()
7522 .map(|range| Crease::simple(range, placeholder.clone()))
7523 .collect();
7524 this.update(cx, |this, cx| {
7525 this.display_map.update(cx, |display_map, cx| {
7526 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7527 display_map.fold(creases, cx);
7528 });
7529 })
7530 .ok();
7531 });
7532 }
7533
7534 #[ztracing::instrument(skip_all)]
7535 fn refresh_selected_text_highlights(
7536 &mut self,
7537 on_buffer_edit: bool,
7538 window: &mut Window,
7539 cx: &mut Context<Editor>,
7540 ) {
7541 let Some((query_text, query_range)) =
7542 self.prepare_highlight_query_from_selection(window, cx)
7543 else {
7544 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7545 self.quick_selection_highlight_task.take();
7546 self.debounced_selection_highlight_task.take();
7547 self.debounced_selection_highlight_complete = false;
7548 return;
7549 };
7550 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7551 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7552 let query_changed = self
7553 .quick_selection_highlight_task
7554 .as_ref()
7555 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range);
7556 if query_changed {
7557 self.debounced_selection_highlight_complete = false;
7558 }
7559 if on_buffer_edit || query_changed {
7560 let multi_buffer_visible_start = self
7561 .scroll_manager
7562 .native_anchor(&display_snapshot, cx)
7563 .anchor
7564 .to_point(&multi_buffer_snapshot);
7565 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7566 multi_buffer_visible_start
7567 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7568 Bias::Left,
7569 );
7570 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7571 self.quick_selection_highlight_task = Some((
7572 query_range.clone(),
7573 self.update_selection_occurrence_highlights(
7574 query_text.clone(),
7575 query_range.clone(),
7576 multi_buffer_visible_range,
7577 false,
7578 window,
7579 cx,
7580 ),
7581 ));
7582 }
7583 if on_buffer_edit
7584 || self
7585 .debounced_selection_highlight_task
7586 .as_ref()
7587 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7588 {
7589 let multi_buffer_start = multi_buffer_snapshot
7590 .anchor_before(MultiBufferOffset(0))
7591 .to_point(&multi_buffer_snapshot);
7592 let multi_buffer_end = multi_buffer_snapshot
7593 .anchor_after(multi_buffer_snapshot.len())
7594 .to_point(&multi_buffer_snapshot);
7595 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7596 self.debounced_selection_highlight_task = Some((
7597 query_range.clone(),
7598 self.update_selection_occurrence_highlights(
7599 query_text,
7600 query_range,
7601 multi_buffer_full_range,
7602 true,
7603 window,
7604 cx,
7605 ),
7606 ));
7607 }
7608 }
7609
7610 pub fn refresh_edit_prediction(
7611 &mut self,
7612 debounce: bool,
7613 user_requested: bool,
7614 window: &mut Window,
7615 cx: &mut Context<Self>,
7616 ) -> Option<()> {
7617 if DisableAiSettings::get_global(cx).disable_ai {
7618 return None;
7619 }
7620
7621 let provider = self.edit_prediction_provider()?;
7622 let cursor = self.selections.newest_anchor().head();
7623 let (buffer, cursor_buffer_position) =
7624 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7625
7626 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7627 self.discard_edit_prediction(false, cx);
7628 return None;
7629 }
7630
7631 self.update_visible_edit_prediction(window, cx);
7632
7633 if !user_requested
7634 && (!self.should_show_edit_predictions()
7635 || !self.is_focused(window)
7636 || buffer.read(cx).is_empty())
7637 {
7638 self.discard_edit_prediction(false, cx);
7639 return None;
7640 }
7641
7642 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7643 Some(())
7644 }
7645
7646 fn show_edit_predictions_in_menu(&self) -> bool {
7647 match self.edit_prediction_settings {
7648 EditPredictionSettings::Disabled => false,
7649 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7650 }
7651 }
7652
7653 pub fn edit_predictions_enabled(&self) -> bool {
7654 match self.edit_prediction_settings {
7655 EditPredictionSettings::Disabled => false,
7656 EditPredictionSettings::Enabled { .. } => true,
7657 }
7658 }
7659
7660 fn edit_prediction_requires_modifier(&self) -> bool {
7661 match self.edit_prediction_settings {
7662 EditPredictionSettings::Disabled => false,
7663 EditPredictionSettings::Enabled {
7664 preview_requires_modifier,
7665 ..
7666 } => preview_requires_modifier,
7667 }
7668 }
7669
7670 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7671 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7672 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7673 self.discard_edit_prediction(false, cx);
7674 } else {
7675 let selection = self.selections.newest_anchor();
7676 let cursor = selection.head();
7677
7678 if let Some((buffer, cursor_buffer_position)) =
7679 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7680 {
7681 self.edit_prediction_settings =
7682 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7683 }
7684 }
7685 }
7686
7687 fn edit_prediction_settings_at_position(
7688 &self,
7689 buffer: &Entity<Buffer>,
7690 buffer_position: language::Anchor,
7691 cx: &App,
7692 ) -> EditPredictionSettings {
7693 if !self.mode.is_full()
7694 || !self.show_edit_predictions_override.unwrap_or(true)
7695 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7696 {
7697 return EditPredictionSettings::Disabled;
7698 }
7699
7700 let buffer = buffer.read(cx);
7701
7702 let file = buffer.file();
7703
7704 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7705 return EditPredictionSettings::Disabled;
7706 };
7707
7708 let by_provider = matches!(
7709 self.menu_edit_predictions_policy,
7710 MenuEditPredictionsPolicy::ByProvider
7711 );
7712
7713 let show_in_menu = by_provider
7714 && self
7715 .edit_prediction_provider
7716 .as_ref()
7717 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
7718
7719 let preview_requires_modifier =
7720 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7721
7722 EditPredictionSettings::Enabled {
7723 show_in_menu,
7724 preview_requires_modifier,
7725 }
7726 }
7727
7728 fn should_show_edit_predictions(&self) -> bool {
7729 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7730 }
7731
7732 pub fn edit_prediction_preview_is_active(&self) -> bool {
7733 matches!(
7734 self.edit_prediction_preview,
7735 EditPredictionPreview::Active { .. }
7736 )
7737 }
7738
7739 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7740 let cursor = self.selections.newest_anchor().head();
7741 if let Some((buffer, cursor_position)) =
7742 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7743 {
7744 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7745 } else {
7746 false
7747 }
7748 }
7749
7750 pub fn supports_minimap(&self, cx: &App) -> bool {
7751 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7752 }
7753
7754 fn edit_predictions_enabled_in_buffer(
7755 &self,
7756 buffer: &Entity<Buffer>,
7757 buffer_position: language::Anchor,
7758 cx: &App,
7759 ) -> bool {
7760 maybe!({
7761 if self.read_only(cx) {
7762 return Some(false);
7763 }
7764 let provider = self.edit_prediction_provider()?;
7765 if !provider.is_enabled(buffer, buffer_position, cx) {
7766 return Some(false);
7767 }
7768 let buffer = buffer.read(cx);
7769 let Some(file) = buffer.file() else {
7770 return Some(true);
7771 };
7772 let settings = all_language_settings(Some(file), cx);
7773 Some(settings.edit_predictions_enabled_for_file(file, cx))
7774 })
7775 .unwrap_or(false)
7776 }
7777
7778 pub fn show_edit_prediction(
7779 &mut self,
7780 _: &ShowEditPrediction,
7781 window: &mut Window,
7782 cx: &mut Context<Self>,
7783 ) {
7784 if !self.has_active_edit_prediction() {
7785 self.refresh_edit_prediction(false, true, window, cx);
7786 return;
7787 }
7788
7789 self.update_visible_edit_prediction(window, cx);
7790 }
7791
7792 pub fn display_cursor_names(
7793 &mut self,
7794 _: &DisplayCursorNames,
7795 window: &mut Window,
7796 cx: &mut Context<Self>,
7797 ) {
7798 self.show_cursor_names(window, cx);
7799 }
7800
7801 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7802 self.show_cursor_names = true;
7803 cx.notify();
7804 cx.spawn_in(window, async move |this, cx| {
7805 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7806 this.update(cx, |this, cx| {
7807 this.show_cursor_names = false;
7808 cx.notify()
7809 })
7810 .ok()
7811 })
7812 .detach();
7813 }
7814
7815 pub fn accept_partial_edit_prediction(
7816 &mut self,
7817 granularity: EditPredictionGranularity,
7818 window: &mut Window,
7819 cx: &mut Context<Self>,
7820 ) {
7821 if self.show_edit_predictions_in_menu() {
7822 self.hide_context_menu(window, cx);
7823 }
7824
7825 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7826 return;
7827 };
7828
7829 if !matches!(granularity, EditPredictionGranularity::Full) && self.selections.count() != 1 {
7830 return;
7831 }
7832
7833 match &active_edit_prediction.completion {
7834 EditPrediction::MoveWithin { target, .. } => {
7835 let target = *target;
7836
7837 if matches!(granularity, EditPredictionGranularity::Full) {
7838 if let Some(position_map) = &self.last_position_map {
7839 let target_row = target.to_display_point(&position_map.snapshot).row();
7840 let is_visible = position_map.visible_row_range.contains(&target_row);
7841
7842 if is_visible || !self.edit_prediction_requires_modifier() {
7843 self.unfold_ranges(&[target..target], true, false, cx);
7844 self.change_selections(
7845 SelectionEffects::scroll(Autoscroll::newest()),
7846 window,
7847 cx,
7848 |selections| {
7849 selections.select_anchor_ranges([target..target]);
7850 },
7851 );
7852 self.clear_row_highlights::<EditPredictionPreview>();
7853 self.edit_prediction_preview
7854 .set_previous_scroll_position(None);
7855 } else {
7856 // Highlight and request scroll
7857 self.edit_prediction_preview
7858 .set_previous_scroll_position(Some(
7859 position_map.snapshot.scroll_anchor,
7860 ));
7861 self.highlight_rows::<EditPredictionPreview>(
7862 target..target,
7863 cx.theme().colors().editor_highlighted_line_background,
7864 RowHighlightOptions {
7865 autoscroll: true,
7866 ..Default::default()
7867 },
7868 cx,
7869 );
7870 self.request_autoscroll(Autoscroll::fit(), cx);
7871 }
7872 }
7873 } else {
7874 self.change_selections(
7875 SelectionEffects::scroll(Autoscroll::newest()),
7876 window,
7877 cx,
7878 |selections| {
7879 selections.select_anchor_ranges([target..target]);
7880 },
7881 );
7882 }
7883 }
7884 EditPrediction::MoveOutside { snapshot, target } => {
7885 if let Some(workspace) = self.workspace() {
7886 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7887 .detach_and_log_err(cx);
7888 }
7889 }
7890 EditPrediction::Edit {
7891 edits,
7892 cursor_position,
7893 ..
7894 } => {
7895 self.report_edit_prediction_event(
7896 active_edit_prediction.completion_id.clone(),
7897 true,
7898 cx,
7899 );
7900
7901 match granularity {
7902 EditPredictionGranularity::Full => {
7903 if let Some(provider) = self.edit_prediction_provider() {
7904 provider.accept(cx);
7905 }
7906
7907 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7908
7909 // Compute fallback cursor position BEFORE applying the edit,
7910 // so the anchor tracks through the edit correctly
7911 let fallback_cursor_target = {
7912 let snapshot = self.buffer.read(cx).snapshot(cx);
7913 edits.last().unwrap().0.end.bias_right(&snapshot)
7914 };
7915
7916 self.buffer.update(cx, |buffer, cx| {
7917 buffer.edit(edits.iter().cloned(), None, cx)
7918 });
7919
7920 // Resolve cursor position after the edit is applied
7921 let cursor_target = if let Some((anchor, offset)) = cursor_position {
7922 // The anchor tracks through the edit, then we add the offset
7923 let snapshot = self.buffer.read(cx).snapshot(cx);
7924 let base_offset = anchor.to_offset(&snapshot).0;
7925 let target_offset =
7926 MultiBufferOffset((base_offset + offset).min(snapshot.len().0));
7927 snapshot.anchor_after(target_offset)
7928 } else {
7929 fallback_cursor_target
7930 };
7931
7932 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7933 s.select_anchor_ranges([cursor_target..cursor_target]);
7934 });
7935
7936 let selections = self.selections.disjoint_anchors_arc();
7937 if let Some(transaction_id_now) =
7938 self.buffer.read(cx).last_transaction_id(cx)
7939 {
7940 if transaction_id_prev != Some(transaction_id_now) {
7941 self.selection_history
7942 .insert_transaction(transaction_id_now, selections);
7943 }
7944 }
7945
7946 self.update_visible_edit_prediction(window, cx);
7947 if self.active_edit_prediction.is_none() {
7948 self.refresh_edit_prediction(true, true, window, cx);
7949 }
7950 cx.notify();
7951 }
7952 _ => {
7953 let snapshot = self.buffer.read(cx).snapshot(cx);
7954 let cursor_offset = self
7955 .selections
7956 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
7957 .head();
7958
7959 let insertion = edits.iter().find_map(|(range, text)| {
7960 let range = range.to_offset(&snapshot);
7961 if range.is_empty() && range.start == cursor_offset {
7962 Some(text)
7963 } else {
7964 None
7965 }
7966 });
7967
7968 if let Some(text) = insertion {
7969 let text_to_insert = match granularity {
7970 EditPredictionGranularity::Word => {
7971 let mut partial = text
7972 .chars()
7973 .by_ref()
7974 .take_while(|c| c.is_alphabetic())
7975 .collect::<String>();
7976 if partial.is_empty() {
7977 partial = text
7978 .chars()
7979 .by_ref()
7980 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7981 .collect::<String>();
7982 }
7983 partial
7984 }
7985 EditPredictionGranularity::Line => {
7986 if let Some(line) = text.split_inclusive('\n').next() {
7987 line.to_string()
7988 } else {
7989 text.to_string()
7990 }
7991 }
7992 EditPredictionGranularity::Full => unreachable!(),
7993 };
7994
7995 cx.emit(EditorEvent::InputHandled {
7996 utf16_range_to_replace: None,
7997 text: text_to_insert.clone().into(),
7998 });
7999
8000 self.insert_with_autoindent_mode(&text_to_insert, None, window, cx);
8001 self.refresh_edit_prediction(true, true, window, cx);
8002 cx.notify();
8003 } else {
8004 self.accept_partial_edit_prediction(
8005 EditPredictionGranularity::Full,
8006 window,
8007 cx,
8008 );
8009 }
8010 }
8011 }
8012 }
8013 }
8014
8015 self.edit_prediction_requires_modifier_in_indent_conflict = false;
8016 }
8017
8018 pub fn accept_next_word_edit_prediction(
8019 &mut self,
8020 _: &AcceptNextWordEditPrediction,
8021 window: &mut Window,
8022 cx: &mut Context<Self>,
8023 ) {
8024 self.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
8025 }
8026
8027 pub fn accept_next_line_edit_prediction(
8028 &mut self,
8029 _: &AcceptNextLineEditPrediction,
8030 window: &mut Window,
8031 cx: &mut Context<Self>,
8032 ) {
8033 self.accept_partial_edit_prediction(EditPredictionGranularity::Line, window, cx);
8034 }
8035
8036 pub fn accept_edit_prediction(
8037 &mut self,
8038 _: &AcceptEditPrediction,
8039 window: &mut Window,
8040 cx: &mut Context<Self>,
8041 ) {
8042 self.accept_partial_edit_prediction(EditPredictionGranularity::Full, window, cx);
8043 }
8044
8045 fn discard_edit_prediction(
8046 &mut self,
8047 should_report_edit_prediction_event: bool,
8048 cx: &mut Context<Self>,
8049 ) -> bool {
8050 if should_report_edit_prediction_event {
8051 let completion_id = self
8052 .active_edit_prediction
8053 .as_ref()
8054 .and_then(|active_completion| active_completion.completion_id.clone());
8055
8056 self.report_edit_prediction_event(completion_id, false, cx);
8057 }
8058
8059 if let Some(provider) = self.edit_prediction_provider() {
8060 let reason = if should_report_edit_prediction_event {
8061 EditPredictionDismissReason::Rejected
8062 } else {
8063 EditPredictionDismissReason::Ignored
8064 };
8065 provider.discard(reason, cx);
8066 }
8067
8068 self.take_active_edit_prediction(cx)
8069 }
8070
8071 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
8072 let Some(provider) = self.edit_prediction_provider() else {
8073 return;
8074 };
8075
8076 let Some((_, buffer, _)) = self
8077 .buffer
8078 .read(cx)
8079 .excerpt_containing(self.selections.newest_anchor().head(), cx)
8080 else {
8081 return;
8082 };
8083
8084 let extension = buffer
8085 .read(cx)
8086 .file()
8087 .and_then(|file| Some(file.path().extension()?.to_string()));
8088
8089 let event_type = match accepted {
8090 true => "Edit Prediction Accepted",
8091 false => "Edit Prediction Discarded",
8092 };
8093 telemetry::event!(
8094 event_type,
8095 provider = provider.name(),
8096 prediction_id = id,
8097 suggestion_accepted = accepted,
8098 file_extension = extension,
8099 );
8100 }
8101
8102 fn open_editor_at_anchor(
8103 snapshot: &language::BufferSnapshot,
8104 target: language::Anchor,
8105 workspace: &Entity<Workspace>,
8106 window: &mut Window,
8107 cx: &mut App,
8108 ) -> Task<Result<()>> {
8109 workspace.update(cx, |workspace, cx| {
8110 let path = snapshot.file().map(|file| file.full_path(cx));
8111 let Some(path) =
8112 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
8113 else {
8114 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
8115 };
8116 let target = text::ToPoint::to_point(&target, snapshot);
8117 let item = workspace.open_path(path, None, true, window, cx);
8118 window.spawn(cx, async move |cx| {
8119 let Some(editor) = item.await?.downcast::<Editor>() else {
8120 return Ok(());
8121 };
8122 editor
8123 .update_in(cx, |editor, window, cx| {
8124 editor.go_to_singleton_buffer_point(target, window, cx);
8125 })
8126 .ok();
8127 anyhow::Ok(())
8128 })
8129 })
8130 }
8131
8132 pub fn has_active_edit_prediction(&self) -> bool {
8133 self.active_edit_prediction.is_some()
8134 }
8135
8136 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
8137 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
8138 return false;
8139 };
8140
8141 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
8142 self.clear_highlights::<EditPredictionHighlight>(cx);
8143 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
8144 true
8145 }
8146
8147 /// Returns true when we're displaying the edit prediction popover below the cursor
8148 /// like we are not previewing and the LSP autocomplete menu is visible
8149 /// or we are in `when_holding_modifier` mode.
8150 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
8151 if self.edit_prediction_preview_is_active()
8152 || !self.show_edit_predictions_in_menu()
8153 || !self.edit_predictions_enabled()
8154 {
8155 return false;
8156 }
8157
8158 if self.has_visible_completions_menu() {
8159 return true;
8160 }
8161
8162 has_completion && self.edit_prediction_requires_modifier()
8163 }
8164
8165 fn handle_modifiers_changed(
8166 &mut self,
8167 modifiers: Modifiers,
8168 position_map: &PositionMap,
8169 window: &mut Window,
8170 cx: &mut Context<Self>,
8171 ) {
8172 // Ensure that the edit prediction preview is updated, even when not
8173 // enabled, if there's an active edit prediction preview.
8174 if self.show_edit_predictions_in_menu()
8175 || matches!(
8176 self.edit_prediction_preview,
8177 EditPredictionPreview::Active { .. }
8178 )
8179 {
8180 self.update_edit_prediction_preview(&modifiers, window, cx);
8181 }
8182
8183 self.update_selection_mode(&modifiers, position_map, window, cx);
8184
8185 let mouse_position = window.mouse_position();
8186 if !position_map.text_hitbox.is_hovered(window) {
8187 return;
8188 }
8189
8190 self.update_hovered_link(
8191 position_map.point_for_position(mouse_position),
8192 &position_map.snapshot,
8193 modifiers,
8194 window,
8195 cx,
8196 )
8197 }
8198
8199 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8200 match EditorSettings::get_global(cx).multi_cursor_modifier {
8201 MultiCursorModifier::Alt => modifiers.secondary(),
8202 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
8203 }
8204 }
8205
8206 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8207 match EditorSettings::get_global(cx).multi_cursor_modifier {
8208 MultiCursorModifier::Alt => modifiers.alt,
8209 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
8210 }
8211 }
8212
8213 fn columnar_selection_mode(
8214 modifiers: &Modifiers,
8215 cx: &mut Context<Self>,
8216 ) -> Option<ColumnarMode> {
8217 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
8218 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
8219 Some(ColumnarMode::FromMouse)
8220 } else if Self::is_alt_pressed(modifiers, cx) {
8221 Some(ColumnarMode::FromSelection)
8222 } else {
8223 None
8224 }
8225 } else {
8226 None
8227 }
8228 }
8229
8230 fn update_selection_mode(
8231 &mut self,
8232 modifiers: &Modifiers,
8233 position_map: &PositionMap,
8234 window: &mut Window,
8235 cx: &mut Context<Self>,
8236 ) {
8237 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
8238 return;
8239 };
8240 if self.selections.pending_anchor().is_none() {
8241 return;
8242 }
8243
8244 let mouse_position = window.mouse_position();
8245 let point_for_position = position_map.point_for_position(mouse_position);
8246 let position = point_for_position.previous_valid;
8247
8248 self.select(
8249 SelectPhase::BeginColumnar {
8250 position,
8251 reset: false,
8252 mode,
8253 goal_column: point_for_position.exact_unclipped.column(),
8254 },
8255 window,
8256 cx,
8257 );
8258 }
8259
8260 fn update_edit_prediction_preview(
8261 &mut self,
8262 modifiers: &Modifiers,
8263 window: &mut Window,
8264 cx: &mut Context<Self>,
8265 ) {
8266 let mut modifiers_held = false;
8267
8268 // Check bindings for all granularities.
8269 // If the user holds the key for Word, Line, or Full, we want to show the preview.
8270 let granularities = [
8271 EditPredictionGranularity::Full,
8272 EditPredictionGranularity::Line,
8273 EditPredictionGranularity::Word,
8274 ];
8275
8276 for granularity in granularities {
8277 if let Some(keystroke) = self
8278 .accept_edit_prediction_keybind(granularity, window, cx)
8279 .keystroke()
8280 {
8281 modifiers_held = modifiers_held
8282 || (keystroke.modifiers() == modifiers && keystroke.modifiers().modified());
8283 }
8284 }
8285
8286 if modifiers_held {
8287 if matches!(
8288 self.edit_prediction_preview,
8289 EditPredictionPreview::Inactive { .. }
8290 ) {
8291 self.edit_prediction_preview = EditPredictionPreview::Active {
8292 previous_scroll_position: None,
8293 since: Instant::now(),
8294 };
8295
8296 self.update_visible_edit_prediction(window, cx);
8297 cx.notify();
8298 }
8299 } else if let EditPredictionPreview::Active {
8300 previous_scroll_position,
8301 since,
8302 } = self.edit_prediction_preview
8303 {
8304 if let (Some(previous_scroll_position), Some(position_map)) =
8305 (previous_scroll_position, self.last_position_map.as_ref())
8306 {
8307 self.set_scroll_position(
8308 previous_scroll_position
8309 .scroll_position(&position_map.snapshot.display_snapshot),
8310 window,
8311 cx,
8312 );
8313 }
8314
8315 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8316 released_too_fast: since.elapsed() < Duration::from_millis(200),
8317 };
8318 self.clear_row_highlights::<EditPredictionPreview>();
8319 self.update_visible_edit_prediction(window, cx);
8320 cx.notify();
8321 }
8322 }
8323
8324 fn update_visible_edit_prediction(
8325 &mut self,
8326 _window: &mut Window,
8327 cx: &mut Context<Self>,
8328 ) -> Option<()> {
8329 if DisableAiSettings::get_global(cx).disable_ai {
8330 return None;
8331 }
8332
8333 if self.ime_transaction.is_some() {
8334 self.discard_edit_prediction(false, cx);
8335 return None;
8336 }
8337
8338 let selection = self.selections.newest_anchor();
8339 let cursor = selection.head();
8340 let multibuffer = self.buffer.read(cx).snapshot(cx);
8341 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8342 let excerpt_id = cursor.excerpt_id;
8343
8344 let show_in_menu = self.show_edit_predictions_in_menu();
8345 let completions_menu_has_precedence = !show_in_menu
8346 && (self.context_menu.borrow().is_some()
8347 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8348
8349 if completions_menu_has_precedence
8350 || !offset_selection.is_empty()
8351 || self
8352 .active_edit_prediction
8353 .as_ref()
8354 .is_some_and(|completion| {
8355 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8356 return false;
8357 };
8358 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8359 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8360 !invalidation_range.contains(&offset_selection.head())
8361 })
8362 {
8363 self.discard_edit_prediction(false, cx);
8364 return None;
8365 }
8366
8367 self.take_active_edit_prediction(cx);
8368 let Some(provider) = self.edit_prediction_provider() else {
8369 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8370 return None;
8371 };
8372
8373 let (buffer, cursor_buffer_position) =
8374 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8375
8376 self.edit_prediction_settings =
8377 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8378
8379 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
8380
8381 if self.edit_prediction_indent_conflict {
8382 let cursor_point = cursor.to_point(&multibuffer);
8383 let mut suggested_indent = None;
8384 multibuffer.suggested_indents_callback(
8385 cursor_point.row..cursor_point.row + 1,
8386 |_, indent| {
8387 suggested_indent = Some(indent);
8388 ControlFlow::Break(())
8389 },
8390 cx,
8391 );
8392
8393 if let Some(indent) = suggested_indent
8394 && indent.len == cursor_point.column
8395 {
8396 self.edit_prediction_indent_conflict = false;
8397 }
8398 }
8399
8400 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8401
8402 let (completion_id, edits, predicted_cursor_position, edit_preview) = match edit_prediction
8403 {
8404 edit_prediction_types::EditPrediction::Local {
8405 id,
8406 edits,
8407 cursor_position,
8408 edit_preview,
8409 } => (id, edits, cursor_position, edit_preview),
8410 edit_prediction_types::EditPrediction::Jump {
8411 id,
8412 snapshot,
8413 target,
8414 } => {
8415 if let Some(provider) = &self.edit_prediction_provider {
8416 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8417 }
8418 self.stale_edit_prediction_in_menu = None;
8419 self.active_edit_prediction = Some(EditPredictionState {
8420 inlay_ids: vec![],
8421 completion: EditPrediction::MoveOutside { snapshot, target },
8422 completion_id: id,
8423 invalidation_range: None,
8424 });
8425 cx.notify();
8426 return Some(());
8427 }
8428 };
8429
8430 let edits = edits
8431 .into_iter()
8432 .flat_map(|(range, new_text)| {
8433 Some((
8434 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8435 new_text,
8436 ))
8437 })
8438 .collect::<Vec<_>>();
8439 if edits.is_empty() {
8440 return None;
8441 }
8442
8443 let cursor_position = predicted_cursor_position.and_then(|predicted| {
8444 let anchor = multibuffer.anchor_in_excerpt(excerpt_id, predicted.anchor)?;
8445 Some((anchor, predicted.offset))
8446 });
8447
8448 let first_edit_start = edits.first().unwrap().0.start;
8449 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8450 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8451
8452 let last_edit_end = edits.last().unwrap().0.end;
8453 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8454 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8455
8456 let cursor_row = cursor.to_point(&multibuffer).row;
8457
8458 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8459
8460 let mut inlay_ids = Vec::new();
8461 let invalidation_row_range;
8462 let move_invalidation_row_range = if cursor_row < edit_start_row {
8463 Some(cursor_row..edit_end_row)
8464 } else if cursor_row > edit_end_row {
8465 Some(edit_start_row..cursor_row)
8466 } else {
8467 None
8468 };
8469 let supports_jump = self
8470 .edit_prediction_provider
8471 .as_ref()
8472 .map(|provider| provider.provider.supports_jump_to_edit())
8473 .unwrap_or(true);
8474
8475 let is_move = supports_jump
8476 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8477 let completion = if is_move {
8478 if let Some(provider) = &self.edit_prediction_provider {
8479 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8480 }
8481 invalidation_row_range =
8482 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8483 let target = first_edit_start;
8484 EditPrediction::MoveWithin { target, snapshot }
8485 } else {
8486 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8487 && !self.edit_predictions_hidden_for_vim_mode;
8488
8489 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8490 if provider.show_tab_accept_marker() {
8491 EditDisplayMode::TabAccept
8492 } else {
8493 EditDisplayMode::Inline
8494 }
8495 } else {
8496 EditDisplayMode::DiffPopover
8497 };
8498
8499 if show_completions_in_buffer {
8500 if let Some(provider) = &self.edit_prediction_provider {
8501 let suggestion_display_type = match display_mode {
8502 EditDisplayMode::DiffPopover => SuggestionDisplayType::DiffPopover,
8503 EditDisplayMode::Inline | EditDisplayMode::TabAccept => {
8504 SuggestionDisplayType::GhostText
8505 }
8506 };
8507 provider.provider.did_show(suggestion_display_type, cx);
8508 }
8509 if edits
8510 .iter()
8511 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8512 {
8513 let mut inlays = Vec::new();
8514 for (range, new_text) in &edits {
8515 let inlay = Inlay::edit_prediction(
8516 post_inc(&mut self.next_inlay_id),
8517 range.start,
8518 new_text.as_ref(),
8519 );
8520 inlay_ids.push(inlay.id);
8521 inlays.push(inlay);
8522 }
8523
8524 self.splice_inlays(&[], inlays, cx);
8525 } else {
8526 let background_color = cx.theme().status().deleted_background;
8527 self.highlight_text::<EditPredictionHighlight>(
8528 edits.iter().map(|(range, _)| range.clone()).collect(),
8529 HighlightStyle {
8530 background_color: Some(background_color),
8531 ..Default::default()
8532 },
8533 cx,
8534 );
8535 }
8536 }
8537
8538 invalidation_row_range = edit_start_row..edit_end_row;
8539
8540 EditPrediction::Edit {
8541 edits,
8542 cursor_position,
8543 edit_preview,
8544 display_mode,
8545 snapshot,
8546 }
8547 };
8548
8549 let invalidation_range = multibuffer
8550 .anchor_before(Point::new(invalidation_row_range.start, 0))
8551 ..multibuffer.anchor_after(Point::new(
8552 invalidation_row_range.end,
8553 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8554 ));
8555
8556 self.stale_edit_prediction_in_menu = None;
8557 self.active_edit_prediction = Some(EditPredictionState {
8558 inlay_ids,
8559 completion,
8560 completion_id,
8561 invalidation_range: Some(invalidation_range),
8562 });
8563
8564 cx.notify();
8565
8566 Some(())
8567 }
8568
8569 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8570 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8571 }
8572
8573 fn clear_tasks(&mut self) {
8574 self.tasks.clear()
8575 }
8576
8577 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8578 if self.tasks.insert(key, value).is_some() {
8579 // This case should hopefully be rare, but just in case...
8580 log::error!(
8581 "multiple different run targets found on a single line, only the last target will be rendered"
8582 )
8583 }
8584 }
8585
8586 /// Get all display points of breakpoints that will be rendered within editor
8587 ///
8588 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8589 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8590 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8591 fn active_breakpoints(
8592 &self,
8593 range: Range<DisplayRow>,
8594 window: &mut Window,
8595 cx: &mut Context<Self>,
8596 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8597 let mut breakpoint_display_points = HashMap::default();
8598
8599 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8600 return breakpoint_display_points;
8601 };
8602
8603 let snapshot = self.snapshot(window, cx);
8604
8605 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8606 let Some(project) = self.project() else {
8607 return breakpoint_display_points;
8608 };
8609
8610 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8611 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8612
8613 for (buffer_snapshot, range, excerpt_id) in
8614 multi_buffer_snapshot.range_to_buffer_ranges(range.start..=range.end)
8615 {
8616 let Some(buffer) = project
8617 .read(cx)
8618 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8619 else {
8620 continue;
8621 };
8622 let breakpoints = breakpoint_store.read(cx).breakpoints(
8623 &buffer,
8624 Some(
8625 buffer_snapshot.anchor_before(range.start)
8626 ..buffer_snapshot.anchor_after(range.end),
8627 ),
8628 buffer_snapshot,
8629 cx,
8630 );
8631 for (breakpoint, state) in breakpoints {
8632 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8633 let position = multi_buffer_anchor
8634 .to_point(&multi_buffer_snapshot)
8635 .to_display_point(&snapshot);
8636
8637 breakpoint_display_points.insert(
8638 position.row(),
8639 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8640 );
8641 }
8642 }
8643
8644 breakpoint_display_points
8645 }
8646
8647 fn breakpoint_context_menu(
8648 &self,
8649 anchor: Anchor,
8650 window: &mut Window,
8651 cx: &mut Context<Self>,
8652 ) -> Entity<ui::ContextMenu> {
8653 let weak_editor = cx.weak_entity();
8654 let focus_handle = self.focus_handle(cx);
8655
8656 let row = self
8657 .buffer
8658 .read(cx)
8659 .snapshot(cx)
8660 .summary_for_anchor::<Point>(&anchor)
8661 .row;
8662
8663 let breakpoint = self
8664 .breakpoint_at_row(row, window, cx)
8665 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8666
8667 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8668 "Edit Log Breakpoint"
8669 } else {
8670 "Set Log Breakpoint"
8671 };
8672
8673 let condition_breakpoint_msg = if breakpoint
8674 .as_ref()
8675 .is_some_and(|bp| bp.1.condition.is_some())
8676 {
8677 "Edit Condition Breakpoint"
8678 } else {
8679 "Set Condition Breakpoint"
8680 };
8681
8682 let hit_condition_breakpoint_msg = if breakpoint
8683 .as_ref()
8684 .is_some_and(|bp| bp.1.hit_condition.is_some())
8685 {
8686 "Edit Hit Condition Breakpoint"
8687 } else {
8688 "Set Hit Condition Breakpoint"
8689 };
8690
8691 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8692 "Unset Breakpoint"
8693 } else {
8694 "Set Breakpoint"
8695 };
8696
8697 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8698
8699 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8700 BreakpointState::Enabled => Some("Disable"),
8701 BreakpointState::Disabled => Some("Enable"),
8702 });
8703
8704 let (anchor, breakpoint) =
8705 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8706
8707 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8708 menu.on_blur_subscription(Subscription::new(|| {}))
8709 .context(focus_handle)
8710 .when(run_to_cursor, |this| {
8711 let weak_editor = weak_editor.clone();
8712 this.entry("Run to cursor", None, move |window, cx| {
8713 weak_editor
8714 .update(cx, |editor, cx| {
8715 editor.change_selections(
8716 SelectionEffects::no_scroll(),
8717 window,
8718 cx,
8719 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8720 );
8721 })
8722 .ok();
8723
8724 window.dispatch_action(Box::new(RunToCursor), cx);
8725 })
8726 .separator()
8727 })
8728 .when_some(toggle_state_msg, |this, msg| {
8729 this.entry(msg, None, {
8730 let weak_editor = weak_editor.clone();
8731 let breakpoint = breakpoint.clone();
8732 move |_window, cx| {
8733 weak_editor
8734 .update(cx, |this, cx| {
8735 this.edit_breakpoint_at_anchor(
8736 anchor,
8737 breakpoint.as_ref().clone(),
8738 BreakpointEditAction::InvertState,
8739 cx,
8740 );
8741 })
8742 .log_err();
8743 }
8744 })
8745 })
8746 .entry(set_breakpoint_msg, None, {
8747 let weak_editor = weak_editor.clone();
8748 let breakpoint = breakpoint.clone();
8749 move |_window, cx| {
8750 weak_editor
8751 .update(cx, |this, cx| {
8752 this.edit_breakpoint_at_anchor(
8753 anchor,
8754 breakpoint.as_ref().clone(),
8755 BreakpointEditAction::Toggle,
8756 cx,
8757 );
8758 })
8759 .log_err();
8760 }
8761 })
8762 .entry(log_breakpoint_msg, None, {
8763 let breakpoint = breakpoint.clone();
8764 let weak_editor = weak_editor.clone();
8765 move |window, cx| {
8766 weak_editor
8767 .update(cx, |this, cx| {
8768 this.add_edit_breakpoint_block(
8769 anchor,
8770 breakpoint.as_ref(),
8771 BreakpointPromptEditAction::Log,
8772 window,
8773 cx,
8774 );
8775 })
8776 .log_err();
8777 }
8778 })
8779 .entry(condition_breakpoint_msg, None, {
8780 let breakpoint = breakpoint.clone();
8781 let weak_editor = weak_editor.clone();
8782 move |window, cx| {
8783 weak_editor
8784 .update(cx, |this, cx| {
8785 this.add_edit_breakpoint_block(
8786 anchor,
8787 breakpoint.as_ref(),
8788 BreakpointPromptEditAction::Condition,
8789 window,
8790 cx,
8791 );
8792 })
8793 .log_err();
8794 }
8795 })
8796 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8797 weak_editor
8798 .update(cx, |this, cx| {
8799 this.add_edit_breakpoint_block(
8800 anchor,
8801 breakpoint.as_ref(),
8802 BreakpointPromptEditAction::HitCondition,
8803 window,
8804 cx,
8805 );
8806 })
8807 .log_err();
8808 })
8809 })
8810 }
8811
8812 fn render_breakpoint(
8813 &self,
8814 position: Anchor,
8815 row: DisplayRow,
8816 breakpoint: &Breakpoint,
8817 state: Option<BreakpointSessionState>,
8818 cx: &mut Context<Self>,
8819 ) -> IconButton {
8820 let is_rejected = state.is_some_and(|s| !s.verified);
8821 // Is it a breakpoint that shows up when hovering over gutter?
8822 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8823 (false, false),
8824 |PhantomBreakpointIndicator {
8825 is_active,
8826 display_row,
8827 collides_with_existing_breakpoint,
8828 }| {
8829 (
8830 is_active && display_row == row,
8831 collides_with_existing_breakpoint,
8832 )
8833 },
8834 );
8835
8836 let (color, icon) = {
8837 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8838 (false, false) => ui::IconName::DebugBreakpoint,
8839 (true, false) => ui::IconName::DebugLogBreakpoint,
8840 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8841 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8842 };
8843
8844 let theme_colors = cx.theme().colors();
8845
8846 let color = if is_phantom {
8847 if collides_with_existing {
8848 Color::Custom(
8849 theme_colors
8850 .debugger_accent
8851 .blend(theme_colors.text.opacity(0.6)),
8852 )
8853 } else {
8854 Color::Hint
8855 }
8856 } else if is_rejected {
8857 Color::Disabled
8858 } else {
8859 Color::Debugger
8860 };
8861
8862 (color, icon)
8863 };
8864
8865 let breakpoint = Arc::from(breakpoint.clone());
8866
8867 let alt_as_text = gpui::Keystroke {
8868 modifiers: Modifiers::secondary_key(),
8869 ..Default::default()
8870 };
8871 let primary_action_text = if breakpoint.is_disabled() {
8872 "Enable breakpoint"
8873 } else if is_phantom && !collides_with_existing {
8874 "Set breakpoint"
8875 } else {
8876 "Unset breakpoint"
8877 };
8878 let focus_handle = self.focus_handle.clone();
8879
8880 let meta = if is_rejected {
8881 SharedString::from("No executable code is associated with this line.")
8882 } else if collides_with_existing && !breakpoint.is_disabled() {
8883 SharedString::from(format!(
8884 "{alt_as_text}-click to disable,\nright-click for more options."
8885 ))
8886 } else {
8887 SharedString::from("Right-click for more options.")
8888 };
8889 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8890 .icon_size(IconSize::XSmall)
8891 .size(ui::ButtonSize::None)
8892 .when(is_rejected, |this| {
8893 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8894 })
8895 .icon_color(color)
8896 .style(ButtonStyle::Transparent)
8897 .on_click(cx.listener({
8898 move |editor, event: &ClickEvent, window, cx| {
8899 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8900 BreakpointEditAction::InvertState
8901 } else {
8902 BreakpointEditAction::Toggle
8903 };
8904
8905 window.focus(&editor.focus_handle(cx), cx);
8906 editor.edit_breakpoint_at_anchor(
8907 position,
8908 breakpoint.as_ref().clone(),
8909 edit_action,
8910 cx,
8911 );
8912 }
8913 }))
8914 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8915 editor.set_breakpoint_context_menu(
8916 row,
8917 Some(position),
8918 event.position(),
8919 window,
8920 cx,
8921 );
8922 }))
8923 .tooltip(move |_window, cx| {
8924 Tooltip::with_meta_in(
8925 primary_action_text,
8926 Some(&ToggleBreakpoint),
8927 meta.clone(),
8928 &focus_handle,
8929 cx,
8930 )
8931 })
8932 }
8933
8934 fn build_tasks_context(
8935 project: &Entity<Project>,
8936 buffer: &Entity<Buffer>,
8937 buffer_row: u32,
8938 tasks: &Arc<RunnableTasks>,
8939 cx: &mut Context<Self>,
8940 ) -> Task<Option<task::TaskContext>> {
8941 let position = Point::new(buffer_row, tasks.column);
8942 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8943 let location = Location {
8944 buffer: buffer.clone(),
8945 range: range_start..range_start,
8946 };
8947 // Fill in the environmental variables from the tree-sitter captures
8948 let mut captured_task_variables = TaskVariables::default();
8949 for (capture_name, value) in tasks.extra_variables.clone() {
8950 captured_task_variables.insert(
8951 task::VariableName::Custom(capture_name.into()),
8952 value.clone(),
8953 );
8954 }
8955 project.update(cx, |project, cx| {
8956 project.task_store().update(cx, |task_store, cx| {
8957 task_store.task_context_for_location(captured_task_variables, location, cx)
8958 })
8959 })
8960 }
8961
8962 pub fn spawn_nearest_task(
8963 &mut self,
8964 action: &SpawnNearestTask,
8965 window: &mut Window,
8966 cx: &mut Context<Self>,
8967 ) {
8968 let Some((workspace, _)) = self.workspace.clone() else {
8969 return;
8970 };
8971 let Some(project) = self.project.clone() else {
8972 return;
8973 };
8974
8975 // Try to find a closest, enclosing node using tree-sitter that has a task
8976 let Some((buffer, buffer_row, tasks)) = self
8977 .find_enclosing_node_task(cx)
8978 // Or find the task that's closest in row-distance.
8979 .or_else(|| self.find_closest_task(cx))
8980 else {
8981 return;
8982 };
8983
8984 let reveal_strategy = action.reveal;
8985 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8986 cx.spawn_in(window, async move |_, cx| {
8987 let context = task_context.await?;
8988 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8989
8990 let resolved = &mut resolved_task.resolved;
8991 resolved.reveal = reveal_strategy;
8992
8993 workspace
8994 .update_in(cx, |workspace, window, cx| {
8995 workspace.schedule_resolved_task(
8996 task_source_kind,
8997 resolved_task,
8998 false,
8999 window,
9000 cx,
9001 );
9002 })
9003 .ok()
9004 })
9005 .detach();
9006 }
9007
9008 fn find_closest_task(
9009 &mut self,
9010 cx: &mut Context<Self>,
9011 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
9012 let cursor_row = self
9013 .selections
9014 .newest_adjusted(&self.display_snapshot(cx))
9015 .head()
9016 .row;
9017
9018 let ((buffer_id, row), tasks) = self
9019 .tasks
9020 .iter()
9021 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
9022
9023 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
9024 let tasks = Arc::new(tasks.to_owned());
9025 Some((buffer, *row, tasks))
9026 }
9027
9028 fn find_enclosing_node_task(
9029 &mut self,
9030 cx: &mut Context<Self>,
9031 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
9032 let snapshot = self.buffer.read(cx).snapshot(cx);
9033 let offset = self
9034 .selections
9035 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
9036 .head();
9037 let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
9038 let offset = excerpt.map_offset_to_buffer(offset);
9039 let buffer_id = excerpt.buffer().remote_id();
9040
9041 let layer = excerpt.buffer().syntax_layer_at(offset)?;
9042 let mut cursor = layer.node().walk();
9043
9044 while cursor.goto_first_child_for_byte(offset.0).is_some() {
9045 if cursor.node().end_byte() == offset.0 {
9046 cursor.goto_next_sibling();
9047 }
9048 }
9049
9050 // Ascend to the smallest ancestor that contains the range and has a task.
9051 loop {
9052 let node = cursor.node();
9053 let node_range = node.byte_range();
9054 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
9055
9056 // Check if this node contains our offset
9057 if node_range.start <= offset.0 && node_range.end >= offset.0 {
9058 // If it contains offset, check for task
9059 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
9060 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
9061 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
9062 }
9063 }
9064
9065 if !cursor.goto_parent() {
9066 break;
9067 }
9068 }
9069 None
9070 }
9071
9072 fn render_run_indicator(
9073 &self,
9074 _style: &EditorStyle,
9075 is_active: bool,
9076 row: DisplayRow,
9077 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
9078 cx: &mut Context<Self>,
9079 ) -> IconButton {
9080 let color = Color::Muted;
9081 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
9082
9083 IconButton::new(
9084 ("run_indicator", row.0 as usize),
9085 ui::IconName::PlayOutlined,
9086 )
9087 .shape(ui::IconButtonShape::Square)
9088 .icon_size(IconSize::XSmall)
9089 .icon_color(color)
9090 .toggle_state(is_active)
9091 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
9092 let quick_launch = match e {
9093 ClickEvent::Keyboard(_) => true,
9094 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
9095 };
9096
9097 window.focus(&editor.focus_handle(cx), cx);
9098 editor.toggle_code_actions(
9099 &ToggleCodeActions {
9100 deployed_from: Some(CodeActionSource::RunMenu(row)),
9101 quick_launch,
9102 },
9103 window,
9104 cx,
9105 );
9106 }))
9107 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
9108 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
9109 }))
9110 }
9111
9112 pub fn context_menu_visible(&self) -> bool {
9113 !self.edit_prediction_preview_is_active()
9114 && self
9115 .context_menu
9116 .borrow()
9117 .as_ref()
9118 .is_some_and(|menu| menu.visible())
9119 }
9120
9121 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
9122 self.context_menu
9123 .borrow()
9124 .as_ref()
9125 .map(|menu| menu.origin())
9126 }
9127
9128 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
9129 self.context_menu_options = Some(options);
9130 }
9131
9132 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
9133 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
9134
9135 fn render_edit_prediction_popover(
9136 &mut self,
9137 text_bounds: &Bounds<Pixels>,
9138 content_origin: gpui::Point<Pixels>,
9139 right_margin: Pixels,
9140 editor_snapshot: &EditorSnapshot,
9141 visible_row_range: Range<DisplayRow>,
9142 scroll_top: ScrollOffset,
9143 scroll_bottom: ScrollOffset,
9144 line_layouts: &[LineWithInvisibles],
9145 line_height: Pixels,
9146 scroll_position: gpui::Point<ScrollOffset>,
9147 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9148 newest_selection_head: Option<DisplayPoint>,
9149 editor_width: Pixels,
9150 style: &EditorStyle,
9151 window: &mut Window,
9152 cx: &mut App,
9153 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9154 if self.mode().is_minimap() {
9155 return None;
9156 }
9157 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
9158
9159 if self.edit_prediction_visible_in_cursor_popover(true) {
9160 return None;
9161 }
9162
9163 match &active_edit_prediction.completion {
9164 EditPrediction::MoveWithin { target, .. } => {
9165 let target_display_point = target.to_display_point(editor_snapshot);
9166
9167 if self.edit_prediction_requires_modifier() {
9168 if !self.edit_prediction_preview_is_active() {
9169 return None;
9170 }
9171
9172 self.render_edit_prediction_modifier_jump_popover(
9173 text_bounds,
9174 content_origin,
9175 visible_row_range,
9176 line_layouts,
9177 line_height,
9178 scroll_pixel_position,
9179 newest_selection_head,
9180 target_display_point,
9181 window,
9182 cx,
9183 )
9184 } else {
9185 self.render_edit_prediction_eager_jump_popover(
9186 text_bounds,
9187 content_origin,
9188 editor_snapshot,
9189 visible_row_range,
9190 scroll_top,
9191 scroll_bottom,
9192 line_height,
9193 scroll_pixel_position,
9194 target_display_point,
9195 editor_width,
9196 window,
9197 cx,
9198 )
9199 }
9200 }
9201 EditPrediction::Edit {
9202 display_mode: EditDisplayMode::Inline,
9203 ..
9204 } => None,
9205 EditPrediction::Edit {
9206 display_mode: EditDisplayMode::TabAccept,
9207 edits,
9208 ..
9209 } => {
9210 let range = &edits.first()?.0;
9211 let target_display_point = range.end.to_display_point(editor_snapshot);
9212
9213 self.render_edit_prediction_end_of_line_popover(
9214 "Accept",
9215 editor_snapshot,
9216 visible_row_range,
9217 target_display_point,
9218 line_height,
9219 scroll_pixel_position,
9220 content_origin,
9221 editor_width,
9222 window,
9223 cx,
9224 )
9225 }
9226 EditPrediction::Edit {
9227 edits,
9228 edit_preview,
9229 display_mode: EditDisplayMode::DiffPopover,
9230 snapshot,
9231 ..
9232 } => self.render_edit_prediction_diff_popover(
9233 text_bounds,
9234 content_origin,
9235 right_margin,
9236 editor_snapshot,
9237 visible_row_range,
9238 line_layouts,
9239 line_height,
9240 scroll_position,
9241 scroll_pixel_position,
9242 newest_selection_head,
9243 editor_width,
9244 style,
9245 edits,
9246 edit_preview,
9247 snapshot,
9248 window,
9249 cx,
9250 ),
9251 EditPrediction::MoveOutside { snapshot, .. } => {
9252 let mut element = self
9253 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
9254 .into_any();
9255
9256 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9257 let origin_x = text_bounds.size.width - size.width - px(30.);
9258 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
9259 element.prepaint_at(origin, window, cx);
9260
9261 Some((element, origin))
9262 }
9263 }
9264 }
9265
9266 fn render_edit_prediction_modifier_jump_popover(
9267 &mut self,
9268 text_bounds: &Bounds<Pixels>,
9269 content_origin: gpui::Point<Pixels>,
9270 visible_row_range: Range<DisplayRow>,
9271 line_layouts: &[LineWithInvisibles],
9272 line_height: Pixels,
9273 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9274 newest_selection_head: Option<DisplayPoint>,
9275 target_display_point: DisplayPoint,
9276 window: &mut Window,
9277 cx: &mut App,
9278 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9279 let scrolled_content_origin =
9280 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
9281
9282 const SCROLL_PADDING_Y: Pixels = px(12.);
9283
9284 if target_display_point.row() < visible_row_range.start {
9285 return self.render_edit_prediction_scroll_popover(
9286 |_| SCROLL_PADDING_Y,
9287 IconName::ArrowUp,
9288 visible_row_range,
9289 line_layouts,
9290 newest_selection_head,
9291 scrolled_content_origin,
9292 window,
9293 cx,
9294 );
9295 } else if target_display_point.row() >= visible_row_range.end {
9296 return self.render_edit_prediction_scroll_popover(
9297 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
9298 IconName::ArrowDown,
9299 visible_row_range,
9300 line_layouts,
9301 newest_selection_head,
9302 scrolled_content_origin,
9303 window,
9304 cx,
9305 );
9306 }
9307
9308 const POLE_WIDTH: Pixels = px(2.);
9309
9310 let line_layout =
9311 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
9312 let target_column = target_display_point.column() as usize;
9313
9314 let target_x = line_layout.x_for_index(target_column);
9315 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
9316 - scroll_pixel_position.y;
9317
9318 let flag_on_right = target_x < text_bounds.size.width / 2.;
9319
9320 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
9321 border_color.l += 0.001;
9322
9323 let mut element = v_flex()
9324 .items_end()
9325 .when(flag_on_right, |el| el.items_start())
9326 .child(if flag_on_right {
9327 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9328 .rounded_bl(px(0.))
9329 .rounded_tl(px(0.))
9330 .border_l_2()
9331 .border_color(border_color)
9332 } else {
9333 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9334 .rounded_br(px(0.))
9335 .rounded_tr(px(0.))
9336 .border_r_2()
9337 .border_color(border_color)
9338 })
9339 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9340 .into_any();
9341
9342 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9343
9344 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9345 - point(
9346 if flag_on_right {
9347 POLE_WIDTH
9348 } else {
9349 size.width - POLE_WIDTH
9350 },
9351 size.height - line_height,
9352 );
9353
9354 origin.x = origin.x.max(content_origin.x);
9355
9356 element.prepaint_at(origin, window, cx);
9357
9358 Some((element, origin))
9359 }
9360
9361 fn render_edit_prediction_scroll_popover(
9362 &mut self,
9363 to_y: impl Fn(Size<Pixels>) -> Pixels,
9364 scroll_icon: IconName,
9365 visible_row_range: Range<DisplayRow>,
9366 line_layouts: &[LineWithInvisibles],
9367 newest_selection_head: Option<DisplayPoint>,
9368 scrolled_content_origin: gpui::Point<Pixels>,
9369 window: &mut Window,
9370 cx: &mut App,
9371 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9372 let mut element = self
9373 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9374 .into_any();
9375
9376 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9377
9378 let cursor = newest_selection_head?;
9379 let cursor_row_layout =
9380 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9381 let cursor_column = cursor.column() as usize;
9382
9383 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9384
9385 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9386
9387 element.prepaint_at(origin, window, cx);
9388 Some((element, origin))
9389 }
9390
9391 fn render_edit_prediction_eager_jump_popover(
9392 &mut self,
9393 text_bounds: &Bounds<Pixels>,
9394 content_origin: gpui::Point<Pixels>,
9395 editor_snapshot: &EditorSnapshot,
9396 visible_row_range: Range<DisplayRow>,
9397 scroll_top: ScrollOffset,
9398 scroll_bottom: ScrollOffset,
9399 line_height: Pixels,
9400 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9401 target_display_point: DisplayPoint,
9402 editor_width: Pixels,
9403 window: &mut Window,
9404 cx: &mut App,
9405 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9406 if target_display_point.row().as_f64() < scroll_top {
9407 let mut element = self
9408 .render_edit_prediction_line_popover(
9409 "Jump to Edit",
9410 Some(IconName::ArrowUp),
9411 window,
9412 cx,
9413 )
9414 .into_any();
9415
9416 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9417 let offset = point(
9418 (text_bounds.size.width - size.width) / 2.,
9419 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9420 );
9421
9422 let origin = text_bounds.origin + offset;
9423 element.prepaint_at(origin, window, cx);
9424 Some((element, origin))
9425 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9426 let mut element = self
9427 .render_edit_prediction_line_popover(
9428 "Jump to Edit",
9429 Some(IconName::ArrowDown),
9430 window,
9431 cx,
9432 )
9433 .into_any();
9434
9435 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9436 let offset = point(
9437 (text_bounds.size.width - size.width) / 2.,
9438 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9439 );
9440
9441 let origin = text_bounds.origin + offset;
9442 element.prepaint_at(origin, window, cx);
9443 Some((element, origin))
9444 } else {
9445 self.render_edit_prediction_end_of_line_popover(
9446 "Jump to Edit",
9447 editor_snapshot,
9448 visible_row_range,
9449 target_display_point,
9450 line_height,
9451 scroll_pixel_position,
9452 content_origin,
9453 editor_width,
9454 window,
9455 cx,
9456 )
9457 }
9458 }
9459
9460 fn render_edit_prediction_end_of_line_popover(
9461 self: &mut Editor,
9462 label: &'static str,
9463 editor_snapshot: &EditorSnapshot,
9464 visible_row_range: Range<DisplayRow>,
9465 target_display_point: DisplayPoint,
9466 line_height: Pixels,
9467 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9468 content_origin: gpui::Point<Pixels>,
9469 editor_width: Pixels,
9470 window: &mut Window,
9471 cx: &mut App,
9472 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9473 let target_line_end = DisplayPoint::new(
9474 target_display_point.row(),
9475 editor_snapshot.line_len(target_display_point.row()),
9476 );
9477
9478 let mut element = self
9479 .render_edit_prediction_line_popover(label, None, window, cx)
9480 .into_any();
9481
9482 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9483
9484 let line_origin =
9485 self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
9486
9487 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9488 let mut origin = start_point
9489 + line_origin
9490 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9491 origin.x = origin.x.max(content_origin.x);
9492
9493 let max_x = content_origin.x + editor_width - size.width;
9494
9495 if origin.x > max_x {
9496 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9497
9498 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9499 origin.y += offset;
9500 IconName::ArrowUp
9501 } else {
9502 origin.y -= offset;
9503 IconName::ArrowDown
9504 };
9505
9506 element = self
9507 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9508 .into_any();
9509
9510 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9511
9512 origin.x = content_origin.x + editor_width - size.width - px(2.);
9513 }
9514
9515 element.prepaint_at(origin, window, cx);
9516 Some((element, origin))
9517 }
9518
9519 fn render_edit_prediction_diff_popover(
9520 self: &Editor,
9521 text_bounds: &Bounds<Pixels>,
9522 content_origin: gpui::Point<Pixels>,
9523 right_margin: Pixels,
9524 editor_snapshot: &EditorSnapshot,
9525 visible_row_range: Range<DisplayRow>,
9526 line_layouts: &[LineWithInvisibles],
9527 line_height: Pixels,
9528 scroll_position: gpui::Point<ScrollOffset>,
9529 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9530 newest_selection_head: Option<DisplayPoint>,
9531 editor_width: Pixels,
9532 style: &EditorStyle,
9533 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9534 edit_preview: &Option<language::EditPreview>,
9535 snapshot: &language::BufferSnapshot,
9536 window: &mut Window,
9537 cx: &mut App,
9538 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9539 let edit_start = edits
9540 .first()
9541 .unwrap()
9542 .0
9543 .start
9544 .to_display_point(editor_snapshot);
9545 let edit_end = edits
9546 .last()
9547 .unwrap()
9548 .0
9549 .end
9550 .to_display_point(editor_snapshot);
9551
9552 let is_visible = visible_row_range.contains(&edit_start.row())
9553 || visible_row_range.contains(&edit_end.row());
9554 if !is_visible {
9555 return None;
9556 }
9557
9558 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9559 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9560 } else {
9561 // Fallback for providers without edit_preview
9562 crate::edit_prediction_fallback_text(edits, cx)
9563 };
9564
9565 let styled_text = highlighted_edits.to_styled_text(&style.text);
9566 let line_count = highlighted_edits.text.lines().count();
9567
9568 const BORDER_WIDTH: Pixels = px(1.);
9569
9570 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9571 let has_keybind = keybind.is_some();
9572
9573 let mut element = h_flex()
9574 .items_start()
9575 .child(
9576 h_flex()
9577 .bg(cx.theme().colors().editor_background)
9578 .border(BORDER_WIDTH)
9579 .shadow_xs()
9580 .border_color(cx.theme().colors().border)
9581 .rounded_l_lg()
9582 .when(line_count > 1, |el| el.rounded_br_lg())
9583 .pr_1()
9584 .child(styled_text),
9585 )
9586 .child(
9587 h_flex()
9588 .h(line_height + BORDER_WIDTH * 2.)
9589 .px_1p5()
9590 .gap_1()
9591 // Workaround: For some reason, there's a gap if we don't do this
9592 .ml(-BORDER_WIDTH)
9593 .shadow(vec![gpui::BoxShadow {
9594 color: gpui::black().opacity(0.05),
9595 offset: point(px(1.), px(1.)),
9596 blur_radius: px(2.),
9597 spread_radius: px(0.),
9598 }])
9599 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9600 .border(BORDER_WIDTH)
9601 .border_color(cx.theme().colors().border)
9602 .rounded_r_lg()
9603 .id("edit_prediction_diff_popover_keybind")
9604 .when(!has_keybind, |el| {
9605 let status_colors = cx.theme().status();
9606
9607 el.bg(status_colors.error_background)
9608 .border_color(status_colors.error.opacity(0.6))
9609 .child(Icon::new(IconName::Info).color(Color::Error))
9610 .cursor_default()
9611 .hoverable_tooltip(move |_window, cx| {
9612 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9613 })
9614 })
9615 .children(keybind),
9616 )
9617 .into_any();
9618
9619 let longest_row =
9620 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9621 let longest_line_width = if visible_row_range.contains(&longest_row) {
9622 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9623 } else {
9624 layout_line(
9625 longest_row,
9626 editor_snapshot,
9627 style,
9628 editor_width,
9629 |_| false,
9630 window,
9631 cx,
9632 )
9633 .width
9634 };
9635
9636 let viewport_bounds =
9637 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9638 right: -right_margin,
9639 ..Default::default()
9640 });
9641
9642 let x_after_longest = Pixels::from(
9643 ScrollPixelOffset::from(
9644 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9645 ) - scroll_pixel_position.x,
9646 );
9647
9648 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9649
9650 // Fully visible if it can be displayed within the window (allow overlapping other
9651 // panes). However, this is only allowed if the popover starts within text_bounds.
9652 let can_position_to_the_right = x_after_longest < text_bounds.right()
9653 && x_after_longest + element_bounds.width < viewport_bounds.right();
9654
9655 let mut origin = if can_position_to_the_right {
9656 point(
9657 x_after_longest,
9658 text_bounds.origin.y
9659 + Pixels::from(
9660 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9661 - scroll_pixel_position.y,
9662 ),
9663 )
9664 } else {
9665 let cursor_row = newest_selection_head.map(|head| head.row());
9666 let above_edit = edit_start
9667 .row()
9668 .0
9669 .checked_sub(line_count as u32)
9670 .map(DisplayRow);
9671 let below_edit = Some(edit_end.row() + 1);
9672 let above_cursor =
9673 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9674 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9675
9676 // Place the edit popover adjacent to the edit if there is a location
9677 // available that is onscreen and does not obscure the cursor. Otherwise,
9678 // place it adjacent to the cursor.
9679 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9680 .into_iter()
9681 .flatten()
9682 .find(|&start_row| {
9683 let end_row = start_row + line_count as u32;
9684 visible_row_range.contains(&start_row)
9685 && visible_row_range.contains(&end_row)
9686 && cursor_row
9687 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9688 })?;
9689
9690 content_origin
9691 + point(
9692 Pixels::from(-scroll_pixel_position.x),
9693 Pixels::from(
9694 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9695 ),
9696 )
9697 };
9698
9699 origin.x -= BORDER_WIDTH;
9700
9701 window.defer_draw(element, origin, 1);
9702
9703 // Do not return an element, since it will already be drawn due to defer_draw.
9704 None
9705 }
9706
9707 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9708 px(30.)
9709 }
9710
9711 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9712 if self.read_only(cx) {
9713 cx.theme().players().read_only()
9714 } else {
9715 self.style.as_ref().unwrap().local_player
9716 }
9717 }
9718
9719 fn render_edit_prediction_accept_keybind(
9720 &self,
9721 window: &mut Window,
9722 cx: &mut App,
9723 ) -> Option<AnyElement> {
9724 let accept_binding =
9725 self.accept_edit_prediction_keybind(EditPredictionGranularity::Full, window, cx);
9726 let accept_keystroke = accept_binding.keystroke()?;
9727
9728 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9729
9730 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9731 Color::Accent
9732 } else {
9733 Color::Muted
9734 };
9735
9736 h_flex()
9737 .px_0p5()
9738 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9739 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9740 .text_size(TextSize::XSmall.rems(cx))
9741 .child(h_flex().children(ui::render_modifiers(
9742 accept_keystroke.modifiers(),
9743 PlatformStyle::platform(),
9744 Some(modifiers_color),
9745 Some(IconSize::XSmall.rems().into()),
9746 true,
9747 )))
9748 .when(is_platform_style_mac, |parent| {
9749 parent.child(accept_keystroke.key().to_string())
9750 })
9751 .when(!is_platform_style_mac, |parent| {
9752 parent.child(
9753 Key::new(
9754 util::capitalize(accept_keystroke.key()),
9755 Some(Color::Default),
9756 )
9757 .size(Some(IconSize::XSmall.rems().into())),
9758 )
9759 })
9760 .into_any()
9761 .into()
9762 }
9763
9764 fn render_edit_prediction_line_popover(
9765 &self,
9766 label: impl Into<SharedString>,
9767 icon: Option<IconName>,
9768 window: &mut Window,
9769 cx: &mut App,
9770 ) -> Stateful<Div> {
9771 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9772
9773 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9774 let has_keybind = keybind.is_some();
9775 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
9776
9777 h_flex()
9778 .id("ep-line-popover")
9779 .py_0p5()
9780 .pl_1()
9781 .pr(padding_right)
9782 .gap_1()
9783 .rounded_md()
9784 .border_1()
9785 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9786 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9787 .shadow_xs()
9788 .when(!has_keybind, |el| {
9789 let status_colors = cx.theme().status();
9790
9791 el.bg(status_colors.error_background)
9792 .border_color(status_colors.error.opacity(0.6))
9793 .pl_2()
9794 .child(Icon::new(icons.error).color(Color::Error))
9795 .cursor_default()
9796 .hoverable_tooltip(move |_window, cx| {
9797 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9798 })
9799 })
9800 .children(keybind)
9801 .child(
9802 Label::new(label)
9803 .size(LabelSize::Small)
9804 .when(!has_keybind, |el| {
9805 el.color(cx.theme().status().error.into()).strikethrough()
9806 }),
9807 )
9808 .when(!has_keybind, |el| {
9809 el.child(
9810 h_flex().ml_1().child(
9811 Icon::new(IconName::Info)
9812 .size(IconSize::Small)
9813 .color(cx.theme().status().error.into()),
9814 ),
9815 )
9816 })
9817 .when_some(icon, |element, icon| {
9818 element.child(
9819 div()
9820 .mt(px(1.5))
9821 .child(Icon::new(icon).size(IconSize::Small)),
9822 )
9823 })
9824 }
9825
9826 fn render_edit_prediction_jump_outside_popover(
9827 &self,
9828 snapshot: &BufferSnapshot,
9829 window: &mut Window,
9830 cx: &mut App,
9831 ) -> Stateful<Div> {
9832 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9833 let has_keybind = keybind.is_some();
9834 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
9835
9836 let file_name = snapshot
9837 .file()
9838 .map(|file| SharedString::new(file.file_name(cx)))
9839 .unwrap_or(SharedString::new_static("untitled"));
9840
9841 h_flex()
9842 .id("ep-jump-outside-popover")
9843 .py_1()
9844 .px_2()
9845 .gap_1()
9846 .rounded_md()
9847 .border_1()
9848 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9849 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9850 .shadow_xs()
9851 .when(!has_keybind, |el| {
9852 let status_colors = cx.theme().status();
9853
9854 el.bg(status_colors.error_background)
9855 .border_color(status_colors.error.opacity(0.6))
9856 .pl_2()
9857 .child(Icon::new(icons.error).color(Color::Error))
9858 .cursor_default()
9859 .hoverable_tooltip(move |_window, cx| {
9860 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9861 })
9862 })
9863 .children(keybind)
9864 .child(
9865 Label::new(file_name)
9866 .size(LabelSize::Small)
9867 .buffer_font(cx)
9868 .when(!has_keybind, |el| {
9869 el.color(cx.theme().status().error.into()).strikethrough()
9870 }),
9871 )
9872 .when(!has_keybind, |el| {
9873 el.child(
9874 h_flex().ml_1().child(
9875 Icon::new(IconName::Info)
9876 .size(IconSize::Small)
9877 .color(cx.theme().status().error.into()),
9878 ),
9879 )
9880 })
9881 .child(
9882 div()
9883 .mt(px(1.5))
9884 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
9885 )
9886 }
9887
9888 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9889 let accent_color = cx.theme().colors().text_accent;
9890 let editor_bg_color = cx.theme().colors().editor_background;
9891 editor_bg_color.blend(accent_color.opacity(0.1))
9892 }
9893
9894 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9895 let accent_color = cx.theme().colors().text_accent;
9896 let editor_bg_color = cx.theme().colors().editor_background;
9897 editor_bg_color.blend(accent_color.opacity(0.6))
9898 }
9899 fn get_prediction_provider_icons(
9900 provider: &Option<RegisteredEditPredictionDelegate>,
9901 cx: &App,
9902 ) -> edit_prediction_types::EditPredictionIconSet {
9903 match provider {
9904 Some(provider) => provider.provider.icons(cx),
9905 None => edit_prediction_types::EditPredictionIconSet::new(IconName::ZedPredict),
9906 }
9907 }
9908
9909 fn render_edit_prediction_cursor_popover(
9910 &self,
9911 min_width: Pixels,
9912 max_width: Pixels,
9913 cursor_point: Point,
9914 style: &EditorStyle,
9915 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9916 _window: &Window,
9917 cx: &mut Context<Editor>,
9918 ) -> Option<AnyElement> {
9919 let provider = self.edit_prediction_provider.as_ref()?;
9920 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
9921
9922 let is_refreshing = provider.provider.is_refreshing(cx);
9923
9924 fn pending_completion_container(icon: IconName) -> Div {
9925 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9926 }
9927
9928 let completion = match &self.active_edit_prediction {
9929 Some(prediction) => {
9930 if !self.has_visible_completions_menu() {
9931 const RADIUS: Pixels = px(6.);
9932 const BORDER_WIDTH: Pixels = px(1.);
9933
9934 return Some(
9935 h_flex()
9936 .elevation_2(cx)
9937 .border(BORDER_WIDTH)
9938 .border_color(cx.theme().colors().border)
9939 .when(accept_keystroke.is_none(), |el| {
9940 el.border_color(cx.theme().status().error)
9941 })
9942 .rounded(RADIUS)
9943 .rounded_tl(px(0.))
9944 .overflow_hidden()
9945 .child(div().px_1p5().child(match &prediction.completion {
9946 EditPrediction::MoveWithin { target, snapshot } => {
9947 use text::ToPoint as _;
9948 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9949 {
9950 Icon::new(icons.down)
9951 } else {
9952 Icon::new(icons.up)
9953 }
9954 }
9955 EditPrediction::MoveOutside { .. } => {
9956 // TODO [zeta2] custom icon for external jump?
9957 Icon::new(icons.base)
9958 }
9959 EditPrediction::Edit { .. } => Icon::new(icons.base),
9960 }))
9961 .child(
9962 h_flex()
9963 .gap_1()
9964 .py_1()
9965 .px_2()
9966 .rounded_r(RADIUS - BORDER_WIDTH)
9967 .border_l_1()
9968 .border_color(cx.theme().colors().border)
9969 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9970 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9971 el.child(
9972 Label::new("Hold")
9973 .size(LabelSize::Small)
9974 .when(accept_keystroke.is_none(), |el| {
9975 el.strikethrough()
9976 })
9977 .line_height_style(LineHeightStyle::UiLabel),
9978 )
9979 })
9980 .id("edit_prediction_cursor_popover_keybind")
9981 .when(accept_keystroke.is_none(), |el| {
9982 let status_colors = cx.theme().status();
9983
9984 el.bg(status_colors.error_background)
9985 .border_color(status_colors.error.opacity(0.6))
9986 .child(Icon::new(IconName::Info).color(Color::Error))
9987 .cursor_default()
9988 .hoverable_tooltip(move |_window, cx| {
9989 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9990 .into()
9991 })
9992 })
9993 .when_some(
9994 accept_keystroke.as_ref(),
9995 |el, accept_keystroke| {
9996 el.child(h_flex().children(ui::render_modifiers(
9997 accept_keystroke.modifiers(),
9998 PlatformStyle::platform(),
9999 Some(Color::Default),
10000 Some(IconSize::XSmall.rems().into()),
10001 false,
10002 )))
10003 },
10004 ),
10005 )
10006 .into_any(),
10007 );
10008 }
10009
10010 self.render_edit_prediction_cursor_popover_preview(
10011 prediction,
10012 cursor_point,
10013 style,
10014 cx,
10015 )?
10016 }
10017
10018 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
10019 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
10020 stale_completion,
10021 cursor_point,
10022 style,
10023 cx,
10024 )?,
10025
10026 None => pending_completion_container(icons.base)
10027 .child(Label::new("...").size(LabelSize::Small)),
10028 },
10029
10030 None => pending_completion_container(icons.base)
10031 .child(Label::new("...").size(LabelSize::Small)),
10032 };
10033
10034 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
10035 completion
10036 .with_animation(
10037 "loading-completion",
10038 Animation::new(Duration::from_secs(2))
10039 .repeat()
10040 .with_easing(pulsating_between(0.4, 0.8)),
10041 |label, delta| label.opacity(delta),
10042 )
10043 .into_any_element()
10044 } else {
10045 completion.into_any_element()
10046 };
10047
10048 let has_completion = self.active_edit_prediction.is_some();
10049
10050 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
10051 Some(
10052 h_flex()
10053 .min_w(min_width)
10054 .max_w(max_width)
10055 .flex_1()
10056 .elevation_2(cx)
10057 .border_color(cx.theme().colors().border)
10058 .child(
10059 div()
10060 .flex_1()
10061 .py_1()
10062 .px_2()
10063 .overflow_hidden()
10064 .child(completion),
10065 )
10066 .when_some(accept_keystroke, |el, accept_keystroke| {
10067 if !accept_keystroke.modifiers().modified() {
10068 return el;
10069 }
10070
10071 el.child(
10072 h_flex()
10073 .h_full()
10074 .border_l_1()
10075 .rounded_r_lg()
10076 .border_color(cx.theme().colors().border)
10077 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10078 .gap_1()
10079 .py_1()
10080 .px_2()
10081 .child(
10082 h_flex()
10083 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
10084 .when(is_platform_style_mac, |parent| parent.gap_1())
10085 .child(h_flex().children(ui::render_modifiers(
10086 accept_keystroke.modifiers(),
10087 PlatformStyle::platform(),
10088 Some(if !has_completion {
10089 Color::Muted
10090 } else {
10091 Color::Default
10092 }),
10093 None,
10094 false,
10095 ))),
10096 )
10097 .child(Label::new("Preview").into_any_element())
10098 .opacity(if has_completion { 1.0 } else { 0.4 }),
10099 )
10100 })
10101 .into_any(),
10102 )
10103 }
10104
10105 fn render_edit_prediction_cursor_popover_preview(
10106 &self,
10107 completion: &EditPredictionState,
10108 cursor_point: Point,
10109 style: &EditorStyle,
10110 cx: &mut Context<Editor>,
10111 ) -> Option<Div> {
10112 use text::ToPoint as _;
10113
10114 fn render_relative_row_jump(
10115 prefix: impl Into<String>,
10116 current_row: u32,
10117 target_row: u32,
10118 ) -> Div {
10119 let (row_diff, arrow) = if target_row < current_row {
10120 (current_row - target_row, IconName::ArrowUp)
10121 } else {
10122 (target_row - current_row, IconName::ArrowDown)
10123 };
10124
10125 h_flex()
10126 .child(
10127 Label::new(format!("{}{}", prefix.into(), row_diff))
10128 .color(Color::Muted)
10129 .size(LabelSize::Small),
10130 )
10131 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
10132 }
10133
10134 let supports_jump = self
10135 .edit_prediction_provider
10136 .as_ref()
10137 .map(|provider| provider.provider.supports_jump_to_edit())
10138 .unwrap_or(true);
10139
10140 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10141
10142 match &completion.completion {
10143 EditPrediction::MoveWithin {
10144 target, snapshot, ..
10145 } => {
10146 if !supports_jump {
10147 return None;
10148 }
10149
10150 Some(
10151 h_flex()
10152 .px_2()
10153 .gap_2()
10154 .flex_1()
10155 .child(
10156 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
10157 Icon::new(icons.down)
10158 } else {
10159 Icon::new(icons.up)
10160 },
10161 )
10162 .child(Label::new("Jump to Edit")),
10163 )
10164 }
10165 EditPrediction::MoveOutside { snapshot, .. } => {
10166 let file_name = snapshot
10167 .file()
10168 .map(|file| file.file_name(cx))
10169 .unwrap_or("untitled");
10170 Some(
10171 h_flex()
10172 .px_2()
10173 .gap_2()
10174 .flex_1()
10175 .child(Icon::new(icons.base))
10176 .child(Label::new(format!("Jump to {file_name}"))),
10177 )
10178 }
10179 EditPrediction::Edit {
10180 edits,
10181 edit_preview,
10182 snapshot,
10183 ..
10184 } => {
10185 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
10186
10187 let (highlighted_edits, has_more_lines) =
10188 if let Some(edit_preview) = edit_preview.as_ref() {
10189 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
10190 .first_line_preview()
10191 } else {
10192 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
10193 };
10194
10195 let styled_text = gpui::StyledText::new(highlighted_edits.text)
10196 .with_default_highlights(&style.text, highlighted_edits.highlights);
10197
10198 let preview = h_flex()
10199 .gap_1()
10200 .min_w_16()
10201 .child(styled_text)
10202 .when(has_more_lines, |parent| parent.child("…"));
10203
10204 let left = if supports_jump && first_edit_row != cursor_point.row {
10205 render_relative_row_jump("", cursor_point.row, first_edit_row)
10206 .into_any_element()
10207 } else {
10208 Icon::new(icons.base).into_any_element()
10209 };
10210
10211 Some(
10212 h_flex()
10213 .h_full()
10214 .flex_1()
10215 .gap_2()
10216 .pr_1()
10217 .overflow_x_hidden()
10218 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
10219 .child(left)
10220 .child(preview),
10221 )
10222 }
10223 }
10224 }
10225
10226 pub fn render_context_menu(
10227 &mut self,
10228 max_height_in_lines: u32,
10229 window: &mut Window,
10230 cx: &mut Context<Editor>,
10231 ) -> Option<AnyElement> {
10232 let menu = self.context_menu.borrow();
10233 let menu = menu.as_ref()?;
10234 if !menu.visible() {
10235 return None;
10236 };
10237 self.style
10238 .as_ref()
10239 .map(|style| menu.render(style, max_height_in_lines, window, cx))
10240 }
10241
10242 fn render_context_menu_aside(
10243 &mut self,
10244 max_size: Size<Pixels>,
10245 window: &mut Window,
10246 cx: &mut Context<Editor>,
10247 ) -> Option<AnyElement> {
10248 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
10249 if menu.visible() {
10250 menu.render_aside(max_size, window, cx)
10251 } else {
10252 None
10253 }
10254 })
10255 }
10256
10257 fn hide_context_menu(
10258 &mut self,
10259 window: &mut Window,
10260 cx: &mut Context<Self>,
10261 ) -> Option<CodeContextMenu> {
10262 cx.notify();
10263 self.completion_tasks.clear();
10264 let context_menu = self.context_menu.borrow_mut().take();
10265 self.stale_edit_prediction_in_menu.take();
10266 self.update_visible_edit_prediction(window, cx);
10267 if let Some(CodeContextMenu::Completions(_)) = &context_menu
10268 && let Some(completion_provider) = &self.completion_provider
10269 {
10270 completion_provider.selection_changed(None, window, cx);
10271 }
10272 context_menu
10273 }
10274
10275 fn show_snippet_choices(
10276 &mut self,
10277 choices: &Vec<String>,
10278 selection: Range<Anchor>,
10279 cx: &mut Context<Self>,
10280 ) {
10281 let Some((_, buffer, _)) = self
10282 .buffer()
10283 .read(cx)
10284 .excerpt_containing(selection.start, cx)
10285 else {
10286 return;
10287 };
10288 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
10289 else {
10290 return;
10291 };
10292 if buffer != end_buffer {
10293 log::error!("expected anchor range to have matching buffer IDs");
10294 return;
10295 }
10296
10297 let id = post_inc(&mut self.next_completion_id);
10298 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
10299 let mut context_menu = self.context_menu.borrow_mut();
10300 let old_menu = context_menu.take();
10301 *context_menu = Some(CodeContextMenu::Completions(
10302 CompletionsMenu::new_snippet_choices(
10303 id,
10304 true,
10305 choices,
10306 selection,
10307 buffer,
10308 old_menu.map(|menu| menu.primary_scroll_handle()),
10309 snippet_sort_order,
10310 ),
10311 ));
10312 }
10313
10314 pub fn insert_snippet(
10315 &mut self,
10316 insertion_ranges: &[Range<MultiBufferOffset>],
10317 snippet: Snippet,
10318 window: &mut Window,
10319 cx: &mut Context<Self>,
10320 ) -> Result<()> {
10321 struct Tabstop<T> {
10322 is_end_tabstop: bool,
10323 ranges: Vec<Range<T>>,
10324 choices: Option<Vec<String>>,
10325 }
10326
10327 let tabstops = self.buffer.update(cx, |buffer, cx| {
10328 let snippet_text: Arc<str> = snippet.text.clone().into();
10329 let edits = insertion_ranges
10330 .iter()
10331 .cloned()
10332 .map(|range| (range, snippet_text.clone()));
10333 let autoindent_mode = AutoindentMode::Block {
10334 original_indent_columns: Vec::new(),
10335 };
10336 buffer.edit(edits, Some(autoindent_mode), cx);
10337
10338 let snapshot = &*buffer.read(cx);
10339 let snippet = &snippet;
10340 snippet
10341 .tabstops
10342 .iter()
10343 .map(|tabstop| {
10344 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10345 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10346 });
10347 let mut tabstop_ranges = tabstop
10348 .ranges
10349 .iter()
10350 .flat_map(|tabstop_range| {
10351 let mut delta = 0_isize;
10352 insertion_ranges.iter().map(move |insertion_range| {
10353 let insertion_start = insertion_range.start + delta;
10354 delta += snippet.text.len() as isize
10355 - (insertion_range.end - insertion_range.start) as isize;
10356
10357 let start =
10358 (insertion_start + tabstop_range.start).min(snapshot.len());
10359 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10360 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10361 })
10362 })
10363 .collect::<Vec<_>>();
10364 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10365
10366 Tabstop {
10367 is_end_tabstop,
10368 ranges: tabstop_ranges,
10369 choices: tabstop.choices.clone(),
10370 }
10371 })
10372 .collect::<Vec<_>>()
10373 });
10374 if let Some(tabstop) = tabstops.first() {
10375 self.change_selections(Default::default(), window, cx, |s| {
10376 // Reverse order so that the first range is the newest created selection.
10377 // Completions will use it and autoscroll will prioritize it.
10378 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10379 });
10380
10381 if let Some(choices) = &tabstop.choices
10382 && let Some(selection) = tabstop.ranges.first()
10383 {
10384 self.show_snippet_choices(choices, selection.clone(), cx)
10385 }
10386
10387 // If we're already at the last tabstop and it's at the end of the snippet,
10388 // we're done, we don't need to keep the state around.
10389 if !tabstop.is_end_tabstop {
10390 let choices = tabstops
10391 .iter()
10392 .map(|tabstop| tabstop.choices.clone())
10393 .collect();
10394
10395 let ranges = tabstops
10396 .into_iter()
10397 .map(|tabstop| tabstop.ranges)
10398 .collect::<Vec<_>>();
10399
10400 self.snippet_stack.push(SnippetState {
10401 active_index: 0,
10402 ranges,
10403 choices,
10404 });
10405 }
10406
10407 // Check whether the just-entered snippet ends with an auto-closable bracket.
10408 if self.autoclose_regions.is_empty() {
10409 let snapshot = self.buffer.read(cx).snapshot(cx);
10410 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10411 let selection_head = selection.head();
10412 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10413 continue;
10414 };
10415
10416 let mut bracket_pair = None;
10417 let max_lookup_length = scope
10418 .brackets()
10419 .map(|(pair, _)| {
10420 pair.start
10421 .as_str()
10422 .chars()
10423 .count()
10424 .max(pair.end.as_str().chars().count())
10425 })
10426 .max();
10427 if let Some(max_lookup_length) = max_lookup_length {
10428 let next_text = snapshot
10429 .chars_at(selection_head)
10430 .take(max_lookup_length)
10431 .collect::<String>();
10432 let prev_text = snapshot
10433 .reversed_chars_at(selection_head)
10434 .take(max_lookup_length)
10435 .collect::<String>();
10436
10437 for (pair, enabled) in scope.brackets() {
10438 if enabled
10439 && pair.close
10440 && prev_text.starts_with(pair.start.as_str())
10441 && next_text.starts_with(pair.end.as_str())
10442 {
10443 bracket_pair = Some(pair.clone());
10444 break;
10445 }
10446 }
10447 }
10448
10449 if let Some(pair) = bracket_pair {
10450 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10451 let autoclose_enabled =
10452 self.use_autoclose && snapshot_settings.use_autoclose;
10453 if autoclose_enabled {
10454 let start = snapshot.anchor_after(selection_head);
10455 let end = snapshot.anchor_after(selection_head);
10456 self.autoclose_regions.push(AutocloseRegion {
10457 selection_id: selection.id,
10458 range: start..end,
10459 pair,
10460 });
10461 }
10462 }
10463 }
10464 }
10465 }
10466 Ok(())
10467 }
10468
10469 pub fn move_to_next_snippet_tabstop(
10470 &mut self,
10471 window: &mut Window,
10472 cx: &mut Context<Self>,
10473 ) -> bool {
10474 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10475 }
10476
10477 pub fn move_to_prev_snippet_tabstop(
10478 &mut self,
10479 window: &mut Window,
10480 cx: &mut Context<Self>,
10481 ) -> bool {
10482 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10483 }
10484
10485 pub fn move_to_snippet_tabstop(
10486 &mut self,
10487 bias: Bias,
10488 window: &mut Window,
10489 cx: &mut Context<Self>,
10490 ) -> bool {
10491 if let Some(mut snippet) = self.snippet_stack.pop() {
10492 match bias {
10493 Bias::Left => {
10494 if snippet.active_index > 0 {
10495 snippet.active_index -= 1;
10496 } else {
10497 self.snippet_stack.push(snippet);
10498 return false;
10499 }
10500 }
10501 Bias::Right => {
10502 if snippet.active_index + 1 < snippet.ranges.len() {
10503 snippet.active_index += 1;
10504 } else {
10505 self.snippet_stack.push(snippet);
10506 return false;
10507 }
10508 }
10509 }
10510 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10511 self.change_selections(Default::default(), window, cx, |s| {
10512 // Reverse order so that the first range is the newest created selection.
10513 // Completions will use it and autoscroll will prioritize it.
10514 s.select_ranges(current_ranges.iter().rev().cloned())
10515 });
10516
10517 if let Some(choices) = &snippet.choices[snippet.active_index]
10518 && let Some(selection) = current_ranges.first()
10519 {
10520 self.show_snippet_choices(choices, selection.clone(), cx);
10521 }
10522
10523 // If snippet state is not at the last tabstop, push it back on the stack
10524 if snippet.active_index + 1 < snippet.ranges.len() {
10525 self.snippet_stack.push(snippet);
10526 }
10527 return true;
10528 }
10529 }
10530
10531 false
10532 }
10533
10534 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10535 self.transact(window, cx, |this, window, cx| {
10536 this.select_all(&SelectAll, window, cx);
10537 this.insert("", window, cx);
10538 });
10539 }
10540
10541 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10542 if self.read_only(cx) {
10543 return;
10544 }
10545 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10546 self.transact(window, cx, |this, window, cx| {
10547 this.select_autoclose_pair(window, cx);
10548
10549 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10550
10551 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10552 if !this.linked_edit_ranges.is_empty() {
10553 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10554 let snapshot = this.buffer.read(cx).snapshot(cx);
10555
10556 for selection in selections.iter() {
10557 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10558 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10559 if selection_start.buffer_id != selection_end.buffer_id {
10560 continue;
10561 }
10562 if let Some(ranges) =
10563 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10564 {
10565 for (buffer, entries) in ranges {
10566 linked_ranges.entry(buffer).or_default().extend(entries);
10567 }
10568 }
10569 }
10570 }
10571
10572 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10573 for selection in &mut selections {
10574 if selection.is_empty() {
10575 let old_head = selection.head();
10576 let mut new_head =
10577 movement::left(&display_map, old_head.to_display_point(&display_map))
10578 .to_point(&display_map);
10579 if let Some((buffer, line_buffer_range)) = display_map
10580 .buffer_snapshot()
10581 .buffer_line_for_row(MultiBufferRow(old_head.row))
10582 {
10583 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10584 let indent_len = match indent_size.kind {
10585 IndentKind::Space => {
10586 buffer.settings_at(line_buffer_range.start, cx).tab_size
10587 }
10588 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10589 };
10590 if old_head.column <= indent_size.len && old_head.column > 0 {
10591 let indent_len = indent_len.get();
10592 new_head = cmp::min(
10593 new_head,
10594 MultiBufferPoint::new(
10595 old_head.row,
10596 ((old_head.column - 1) / indent_len) * indent_len,
10597 ),
10598 );
10599 }
10600 }
10601
10602 selection.set_head(new_head, SelectionGoal::None);
10603 }
10604 }
10605
10606 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10607 this.insert("", window, cx);
10608 let empty_str: Arc<str> = Arc::from("");
10609 for (buffer, edits) in linked_ranges {
10610 let snapshot = buffer.read(cx).snapshot();
10611 use text::ToPoint as TP;
10612
10613 let edits = edits
10614 .into_iter()
10615 .map(|range| {
10616 let end_point = TP::to_point(&range.end, &snapshot);
10617 let mut start_point = TP::to_point(&range.start, &snapshot);
10618
10619 if end_point == start_point {
10620 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10621 .saturating_sub(1);
10622 start_point =
10623 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10624 };
10625
10626 (start_point..end_point, empty_str.clone())
10627 })
10628 .sorted_by_key(|(range, _)| range.start)
10629 .collect::<Vec<_>>();
10630 buffer.update(cx, |this, cx| {
10631 this.edit(edits, None, cx);
10632 })
10633 }
10634 this.refresh_edit_prediction(true, false, window, cx);
10635 refresh_linked_ranges(this, window, cx);
10636 });
10637 }
10638
10639 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10640 if self.read_only(cx) {
10641 return;
10642 }
10643 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10644 self.transact(window, cx, |this, window, cx| {
10645 this.change_selections(Default::default(), window, cx, |s| {
10646 s.move_with(|map, selection| {
10647 if selection.is_empty() {
10648 let cursor = movement::right(map, selection.head());
10649 selection.end = cursor;
10650 selection.reversed = true;
10651 selection.goal = SelectionGoal::None;
10652 }
10653 })
10654 });
10655 this.insert("", window, cx);
10656 this.refresh_edit_prediction(true, false, window, cx);
10657 });
10658 }
10659
10660 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10661 if self.mode.is_single_line() {
10662 cx.propagate();
10663 return;
10664 }
10665
10666 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10667 if self.move_to_prev_snippet_tabstop(window, cx) {
10668 return;
10669 }
10670 self.outdent(&Outdent, window, cx);
10671 }
10672
10673 pub fn next_snippet_tabstop(
10674 &mut self,
10675 _: &NextSnippetTabstop,
10676 window: &mut Window,
10677 cx: &mut Context<Self>,
10678 ) {
10679 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10680 cx.propagate();
10681 return;
10682 }
10683
10684 if self.move_to_next_snippet_tabstop(window, cx) {
10685 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10686 return;
10687 }
10688 cx.propagate();
10689 }
10690
10691 pub fn previous_snippet_tabstop(
10692 &mut self,
10693 _: &PreviousSnippetTabstop,
10694 window: &mut Window,
10695 cx: &mut Context<Self>,
10696 ) {
10697 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10698 cx.propagate();
10699 return;
10700 }
10701
10702 if self.move_to_prev_snippet_tabstop(window, cx) {
10703 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10704 return;
10705 }
10706 cx.propagate();
10707 }
10708
10709 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10710 if self.mode.is_single_line() {
10711 cx.propagate();
10712 return;
10713 }
10714
10715 if self.move_to_next_snippet_tabstop(window, cx) {
10716 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10717 return;
10718 }
10719 if self.read_only(cx) {
10720 return;
10721 }
10722 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10723 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10724 let buffer = self.buffer.read(cx);
10725 let snapshot = buffer.snapshot(cx);
10726 let rows_iter = selections.iter().map(|s| s.head().row);
10727 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10728
10729 let has_some_cursor_in_whitespace = selections
10730 .iter()
10731 .filter(|selection| selection.is_empty())
10732 .any(|selection| {
10733 let cursor = selection.head();
10734 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10735 cursor.column < current_indent.len
10736 });
10737
10738 let mut edits = Vec::new();
10739 let mut prev_edited_row = 0;
10740 let mut row_delta = 0;
10741 for selection in &mut selections {
10742 if selection.start.row != prev_edited_row {
10743 row_delta = 0;
10744 }
10745 prev_edited_row = selection.end.row;
10746
10747 // If cursor is after a list prefix, make selection non-empty to trigger line indent
10748 if selection.is_empty() {
10749 let cursor = selection.head();
10750 let settings = buffer.language_settings_at(cursor, cx);
10751 if settings.indent_list_on_tab {
10752 if let Some(language) = snapshot.language_scope_at(Point::new(cursor.row, 0)) {
10753 if is_list_prefix_row(MultiBufferRow(cursor.row), &snapshot, &language) {
10754 row_delta = Self::indent_selection(
10755 buffer, &snapshot, selection, &mut edits, row_delta, cx,
10756 );
10757 continue;
10758 }
10759 }
10760 }
10761 }
10762
10763 // If the selection is non-empty, then increase the indentation of the selected lines.
10764 if !selection.is_empty() {
10765 row_delta =
10766 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10767 continue;
10768 }
10769
10770 let cursor = selection.head();
10771 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10772 if let Some(suggested_indent) =
10773 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10774 {
10775 // Don't do anything if already at suggested indent
10776 // and there is any other cursor which is not
10777 if has_some_cursor_in_whitespace
10778 && cursor.column == current_indent.len
10779 && current_indent.len == suggested_indent.len
10780 {
10781 continue;
10782 }
10783
10784 // Adjust line and move cursor to suggested indent
10785 // if cursor is not at suggested indent
10786 if cursor.column < suggested_indent.len
10787 && cursor.column <= current_indent.len
10788 && current_indent.len <= suggested_indent.len
10789 {
10790 selection.start = Point::new(cursor.row, suggested_indent.len);
10791 selection.end = selection.start;
10792 if row_delta == 0 {
10793 edits.extend(Buffer::edit_for_indent_size_adjustment(
10794 cursor.row,
10795 current_indent,
10796 suggested_indent,
10797 ));
10798 row_delta = suggested_indent.len - current_indent.len;
10799 }
10800 continue;
10801 }
10802
10803 // If current indent is more than suggested indent
10804 // only move cursor to current indent and skip indent
10805 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10806 selection.start = Point::new(cursor.row, current_indent.len);
10807 selection.end = selection.start;
10808 continue;
10809 }
10810 }
10811
10812 // Otherwise, insert a hard or soft tab.
10813 let settings = buffer.language_settings_at(cursor, cx);
10814 let tab_size = if settings.hard_tabs {
10815 IndentSize::tab()
10816 } else {
10817 let tab_size = settings.tab_size.get();
10818 let indent_remainder = snapshot
10819 .text_for_range(Point::new(cursor.row, 0)..cursor)
10820 .flat_map(str::chars)
10821 .fold(row_delta % tab_size, |counter: u32, c| {
10822 if c == '\t' {
10823 0
10824 } else {
10825 (counter + 1) % tab_size
10826 }
10827 });
10828
10829 let chars_to_next_tab_stop = tab_size - indent_remainder;
10830 IndentSize::spaces(chars_to_next_tab_stop)
10831 };
10832 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10833 selection.end = selection.start;
10834 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10835 row_delta += tab_size.len;
10836 }
10837
10838 self.transact(window, cx, |this, window, cx| {
10839 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10840 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10841 this.refresh_edit_prediction(true, false, window, cx);
10842 });
10843 }
10844
10845 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10846 if self.read_only(cx) {
10847 return;
10848 }
10849 if self.mode.is_single_line() {
10850 cx.propagate();
10851 return;
10852 }
10853
10854 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10855 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10856 let mut prev_edited_row = 0;
10857 let mut row_delta = 0;
10858 let mut edits = Vec::new();
10859 let buffer = self.buffer.read(cx);
10860 let snapshot = buffer.snapshot(cx);
10861 for selection in &mut selections {
10862 if selection.start.row != prev_edited_row {
10863 row_delta = 0;
10864 }
10865 prev_edited_row = selection.end.row;
10866
10867 row_delta =
10868 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10869 }
10870
10871 self.transact(window, cx, |this, window, cx| {
10872 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10873 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10874 });
10875 }
10876
10877 fn indent_selection(
10878 buffer: &MultiBuffer,
10879 snapshot: &MultiBufferSnapshot,
10880 selection: &mut Selection<Point>,
10881 edits: &mut Vec<(Range<Point>, String)>,
10882 delta_for_start_row: u32,
10883 cx: &App,
10884 ) -> u32 {
10885 let settings = buffer.language_settings_at(selection.start, cx);
10886 let tab_size = settings.tab_size.get();
10887 let indent_kind = if settings.hard_tabs {
10888 IndentKind::Tab
10889 } else {
10890 IndentKind::Space
10891 };
10892 let mut start_row = selection.start.row;
10893 let mut end_row = selection.end.row + 1;
10894
10895 // If a selection ends at the beginning of a line, don't indent
10896 // that last line.
10897 if selection.end.column == 0 && selection.end.row > selection.start.row {
10898 end_row -= 1;
10899 }
10900
10901 // Avoid re-indenting a row that has already been indented by a
10902 // previous selection, but still update this selection's column
10903 // to reflect that indentation.
10904 if delta_for_start_row > 0 {
10905 start_row += 1;
10906 selection.start.column += delta_for_start_row;
10907 if selection.end.row == selection.start.row {
10908 selection.end.column += delta_for_start_row;
10909 }
10910 }
10911
10912 let mut delta_for_end_row = 0;
10913 let has_multiple_rows = start_row + 1 != end_row;
10914 for row in start_row..end_row {
10915 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10916 let indent_delta = match (current_indent.kind, indent_kind) {
10917 (IndentKind::Space, IndentKind::Space) => {
10918 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10919 IndentSize::spaces(columns_to_next_tab_stop)
10920 }
10921 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10922 (_, IndentKind::Tab) => IndentSize::tab(),
10923 };
10924
10925 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10926 0
10927 } else {
10928 selection.start.column
10929 };
10930 let row_start = Point::new(row, start);
10931 edits.push((
10932 row_start..row_start,
10933 indent_delta.chars().collect::<String>(),
10934 ));
10935
10936 // Update this selection's endpoints to reflect the indentation.
10937 if row == selection.start.row {
10938 selection.start.column += indent_delta.len;
10939 }
10940 if row == selection.end.row {
10941 selection.end.column += indent_delta.len;
10942 delta_for_end_row = indent_delta.len;
10943 }
10944 }
10945
10946 if selection.start.row == selection.end.row {
10947 delta_for_start_row + delta_for_end_row
10948 } else {
10949 delta_for_end_row
10950 }
10951 }
10952
10953 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10954 if self.read_only(cx) {
10955 return;
10956 }
10957 if self.mode.is_single_line() {
10958 cx.propagate();
10959 return;
10960 }
10961
10962 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10963 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10964 let selections = self.selections.all::<Point>(&display_map);
10965 let mut deletion_ranges = Vec::new();
10966 let mut last_outdent = None;
10967 {
10968 let buffer = self.buffer.read(cx);
10969 let snapshot = buffer.snapshot(cx);
10970 for selection in &selections {
10971 let settings = buffer.language_settings_at(selection.start, cx);
10972 let tab_size = settings.tab_size.get();
10973 let mut rows = selection.spanned_rows(false, &display_map);
10974
10975 // Avoid re-outdenting a row that has already been outdented by a
10976 // previous selection.
10977 if let Some(last_row) = last_outdent
10978 && last_row == rows.start
10979 {
10980 rows.start = rows.start.next_row();
10981 }
10982 let has_multiple_rows = rows.len() > 1;
10983 for row in rows.iter_rows() {
10984 let indent_size = snapshot.indent_size_for_line(row);
10985 if indent_size.len > 0 {
10986 let deletion_len = match indent_size.kind {
10987 IndentKind::Space => {
10988 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10989 if columns_to_prev_tab_stop == 0 {
10990 tab_size
10991 } else {
10992 columns_to_prev_tab_stop
10993 }
10994 }
10995 IndentKind::Tab => 1,
10996 };
10997 let start = if has_multiple_rows
10998 || deletion_len > selection.start.column
10999 || indent_size.len < selection.start.column
11000 {
11001 0
11002 } else {
11003 selection.start.column - deletion_len
11004 };
11005 deletion_ranges.push(
11006 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
11007 );
11008 last_outdent = Some(row);
11009 }
11010 }
11011 }
11012 }
11013
11014 self.transact(window, cx, |this, window, cx| {
11015 this.buffer.update(cx, |buffer, cx| {
11016 let empty_str: Arc<str> = Arc::default();
11017 buffer.edit(
11018 deletion_ranges
11019 .into_iter()
11020 .map(|range| (range, empty_str.clone())),
11021 None,
11022 cx,
11023 );
11024 });
11025 let selections = this
11026 .selections
11027 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11028 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11029 });
11030 }
11031
11032 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
11033 if self.read_only(cx) {
11034 return;
11035 }
11036 if self.mode.is_single_line() {
11037 cx.propagate();
11038 return;
11039 }
11040
11041 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11042 let selections = self
11043 .selections
11044 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11045 .into_iter()
11046 .map(|s| s.range());
11047
11048 self.transact(window, cx, |this, window, cx| {
11049 this.buffer.update(cx, |buffer, cx| {
11050 buffer.autoindent_ranges(selections, cx);
11051 });
11052 let selections = this
11053 .selections
11054 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11055 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11056 });
11057 }
11058
11059 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
11060 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11061 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11062 let selections = self.selections.all::<Point>(&display_map);
11063
11064 let mut new_cursors = Vec::new();
11065 let mut edit_ranges = Vec::new();
11066 let mut selections = selections.iter().peekable();
11067 while let Some(selection) = selections.next() {
11068 let mut rows = selection.spanned_rows(false, &display_map);
11069
11070 // Accumulate contiguous regions of rows that we want to delete.
11071 while let Some(next_selection) = selections.peek() {
11072 let next_rows = next_selection.spanned_rows(false, &display_map);
11073 if next_rows.start <= rows.end {
11074 rows.end = next_rows.end;
11075 selections.next().unwrap();
11076 } else {
11077 break;
11078 }
11079 }
11080
11081 let buffer = display_map.buffer_snapshot();
11082 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
11083 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
11084 // If there's a line after the range, delete the \n from the end of the row range
11085 (
11086 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
11087 rows.end,
11088 )
11089 } else {
11090 // If there isn't a line after the range, delete the \n from the line before the
11091 // start of the row range
11092 edit_start = edit_start.saturating_sub_usize(1);
11093 (buffer.len(), rows.start.previous_row())
11094 };
11095
11096 let text_layout_details = self.text_layout_details(window, cx);
11097 let x = display_map.x_for_display_point(
11098 selection.head().to_display_point(&display_map),
11099 &text_layout_details,
11100 );
11101 let row = Point::new(target_row.0, 0)
11102 .to_display_point(&display_map)
11103 .row();
11104 let column = display_map.display_column_for_x(row, x, &text_layout_details);
11105
11106 new_cursors.push((
11107 selection.id,
11108 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
11109 SelectionGoal::None,
11110 ));
11111 edit_ranges.push(edit_start..edit_end);
11112 }
11113
11114 self.transact(window, cx, |this, window, cx| {
11115 let buffer = this.buffer.update(cx, |buffer, cx| {
11116 let empty_str: Arc<str> = Arc::default();
11117 buffer.edit(
11118 edit_ranges
11119 .into_iter()
11120 .map(|range| (range, empty_str.clone())),
11121 None,
11122 cx,
11123 );
11124 buffer.snapshot(cx)
11125 });
11126 let new_selections = new_cursors
11127 .into_iter()
11128 .map(|(id, cursor, goal)| {
11129 let cursor = cursor.to_point(&buffer);
11130 Selection {
11131 id,
11132 start: cursor,
11133 end: cursor,
11134 reversed: false,
11135 goal,
11136 }
11137 })
11138 .collect();
11139
11140 this.change_selections(Default::default(), window, cx, |s| {
11141 s.select(new_selections);
11142 });
11143 });
11144 }
11145
11146 pub fn join_lines_impl(
11147 &mut self,
11148 insert_whitespace: bool,
11149 window: &mut Window,
11150 cx: &mut Context<Self>,
11151 ) {
11152 if self.read_only(cx) {
11153 return;
11154 }
11155 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
11156 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
11157 let start = MultiBufferRow(selection.start.row);
11158 // Treat single line selections as if they include the next line. Otherwise this action
11159 // would do nothing for single line selections individual cursors.
11160 let end = if selection.start.row == selection.end.row {
11161 MultiBufferRow(selection.start.row + 1)
11162 } else {
11163 MultiBufferRow(selection.end.row)
11164 };
11165
11166 if let Some(last_row_range) = row_ranges.last_mut()
11167 && start <= last_row_range.end
11168 {
11169 last_row_range.end = end;
11170 continue;
11171 }
11172 row_ranges.push(start..end);
11173 }
11174
11175 let snapshot = self.buffer.read(cx).snapshot(cx);
11176 let mut cursor_positions = Vec::new();
11177 for row_range in &row_ranges {
11178 let anchor = snapshot.anchor_before(Point::new(
11179 row_range.end.previous_row().0,
11180 snapshot.line_len(row_range.end.previous_row()),
11181 ));
11182 cursor_positions.push(anchor..anchor);
11183 }
11184
11185 self.transact(window, cx, |this, window, cx| {
11186 for row_range in row_ranges.into_iter().rev() {
11187 for row in row_range.iter_rows().rev() {
11188 let end_of_line = Point::new(row.0, snapshot.line_len(row));
11189 let next_line_row = row.next_row();
11190 let indent = snapshot.indent_size_for_line(next_line_row);
11191 let start_of_next_line = Point::new(next_line_row.0, indent.len);
11192
11193 let replace =
11194 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
11195 " "
11196 } else {
11197 ""
11198 };
11199
11200 this.buffer.update(cx, |buffer, cx| {
11201 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
11202 });
11203 }
11204 }
11205
11206 this.change_selections(Default::default(), window, cx, |s| {
11207 s.select_anchor_ranges(cursor_positions)
11208 });
11209 });
11210 }
11211
11212 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
11213 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11214 self.join_lines_impl(true, window, cx);
11215 }
11216
11217 pub fn sort_lines_case_sensitive(
11218 &mut self,
11219 _: &SortLinesCaseSensitive,
11220 window: &mut Window,
11221 cx: &mut Context<Self>,
11222 ) {
11223 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
11224 }
11225
11226 pub fn sort_lines_by_length(
11227 &mut self,
11228 _: &SortLinesByLength,
11229 window: &mut Window,
11230 cx: &mut Context<Self>,
11231 ) {
11232 self.manipulate_immutable_lines(window, cx, |lines| {
11233 lines.sort_by_key(|&line| line.chars().count())
11234 })
11235 }
11236
11237 pub fn sort_lines_case_insensitive(
11238 &mut self,
11239 _: &SortLinesCaseInsensitive,
11240 window: &mut Window,
11241 cx: &mut Context<Self>,
11242 ) {
11243 self.manipulate_immutable_lines(window, cx, |lines| {
11244 lines.sort_by_key(|line| line.to_lowercase())
11245 })
11246 }
11247
11248 pub fn unique_lines_case_insensitive(
11249 &mut self,
11250 _: &UniqueLinesCaseInsensitive,
11251 window: &mut Window,
11252 cx: &mut Context<Self>,
11253 ) {
11254 self.manipulate_immutable_lines(window, cx, |lines| {
11255 let mut seen = HashSet::default();
11256 lines.retain(|line| seen.insert(line.to_lowercase()));
11257 })
11258 }
11259
11260 pub fn unique_lines_case_sensitive(
11261 &mut self,
11262 _: &UniqueLinesCaseSensitive,
11263 window: &mut Window,
11264 cx: &mut Context<Self>,
11265 ) {
11266 self.manipulate_immutable_lines(window, cx, |lines| {
11267 let mut seen = HashSet::default();
11268 lines.retain(|line| seen.insert(*line));
11269 })
11270 }
11271
11272 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
11273 let snapshot = self.buffer.read(cx).snapshot(cx);
11274 for selection in self.selections.disjoint_anchors_arc().iter() {
11275 if snapshot
11276 .language_at(selection.start)
11277 .and_then(|lang| lang.config().wrap_characters.as_ref())
11278 .is_some()
11279 {
11280 return true;
11281 }
11282 }
11283 false
11284 }
11285
11286 fn wrap_selections_in_tag(
11287 &mut self,
11288 _: &WrapSelectionsInTag,
11289 window: &mut Window,
11290 cx: &mut Context<Self>,
11291 ) {
11292 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11293
11294 let snapshot = self.buffer.read(cx).snapshot(cx);
11295
11296 let mut edits = Vec::new();
11297 let mut boundaries = Vec::new();
11298
11299 for selection in self
11300 .selections
11301 .all_adjusted(&self.display_snapshot(cx))
11302 .iter()
11303 {
11304 let Some(wrap_config) = snapshot
11305 .language_at(selection.start)
11306 .and_then(|lang| lang.config().wrap_characters.clone())
11307 else {
11308 continue;
11309 };
11310
11311 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
11312 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
11313
11314 let start_before = snapshot.anchor_before(selection.start);
11315 let end_after = snapshot.anchor_after(selection.end);
11316
11317 edits.push((start_before..start_before, open_tag));
11318 edits.push((end_after..end_after, close_tag));
11319
11320 boundaries.push((
11321 start_before,
11322 end_after,
11323 wrap_config.start_prefix.len(),
11324 wrap_config.end_suffix.len(),
11325 ));
11326 }
11327
11328 if edits.is_empty() {
11329 return;
11330 }
11331
11332 self.transact(window, cx, |this, window, cx| {
11333 let buffer = this.buffer.update(cx, |buffer, cx| {
11334 buffer.edit(edits, None, cx);
11335 buffer.snapshot(cx)
11336 });
11337
11338 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
11339 for (start_before, end_after, start_prefix_len, end_suffix_len) in
11340 boundaries.into_iter()
11341 {
11342 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
11343 let close_offset = end_after
11344 .to_offset(&buffer)
11345 .saturating_sub_usize(end_suffix_len);
11346 new_selections.push(open_offset..open_offset);
11347 new_selections.push(close_offset..close_offset);
11348 }
11349
11350 this.change_selections(Default::default(), window, cx, |s| {
11351 s.select_ranges(new_selections);
11352 });
11353
11354 this.request_autoscroll(Autoscroll::fit(), cx);
11355 });
11356 }
11357
11358 pub fn toggle_read_only(
11359 &mut self,
11360 _: &workspace::ToggleReadOnlyFile,
11361 _: &mut Window,
11362 cx: &mut Context<Self>,
11363 ) {
11364 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
11365 buffer.update(cx, |buffer, cx| {
11366 buffer.set_capability(
11367 match buffer.capability() {
11368 Capability::ReadWrite => Capability::Read,
11369 Capability::Read => Capability::ReadWrite,
11370 Capability::ReadOnly => Capability::ReadOnly,
11371 },
11372 cx,
11373 );
11374 })
11375 }
11376 }
11377
11378 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11379 let Some(project) = self.project.clone() else {
11380 return;
11381 };
11382 self.reload(project, window, cx)
11383 .detach_and_notify_err(window, cx);
11384 }
11385
11386 pub fn restore_file(
11387 &mut self,
11388 _: &::git::RestoreFile,
11389 window: &mut Window,
11390 cx: &mut Context<Self>,
11391 ) {
11392 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11393 let mut buffer_ids = HashSet::default();
11394 let snapshot = self.buffer().read(cx).snapshot(cx);
11395 for selection in self
11396 .selections
11397 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11398 {
11399 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11400 }
11401
11402 let buffer = self.buffer().read(cx);
11403 let ranges = buffer_ids
11404 .into_iter()
11405 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11406 .collect::<Vec<_>>();
11407
11408 self.restore_hunks_in_ranges(ranges, window, cx);
11409 }
11410
11411 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11412 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11413 let selections = self
11414 .selections
11415 .all(&self.display_snapshot(cx))
11416 .into_iter()
11417 .map(|s| s.range())
11418 .collect();
11419 self.restore_hunks_in_ranges(selections, window, cx);
11420 }
11421
11422 pub fn restore_hunks_in_ranges(
11423 &mut self,
11424 ranges: Vec<Range<Point>>,
11425 window: &mut Window,
11426 cx: &mut Context<Editor>,
11427 ) {
11428 if self.delegate_stage_and_restore {
11429 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11430 if !hunks.is_empty() {
11431 cx.emit(EditorEvent::RestoreRequested { hunks });
11432 }
11433 return;
11434 }
11435 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11436 self.transact(window, cx, |editor, window, cx| {
11437 editor.restore_diff_hunks(hunks, cx);
11438 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
11439 selections.refresh()
11440 });
11441 });
11442 }
11443
11444 pub(crate) fn restore_diff_hunks(&self, hunks: Vec<MultiBufferDiffHunk>, cx: &mut App) {
11445 let mut revert_changes = HashMap::default();
11446 let chunk_by = hunks.into_iter().chunk_by(|hunk| hunk.buffer_id);
11447 for (buffer_id, hunks) in &chunk_by {
11448 let hunks = hunks.collect::<Vec<_>>();
11449 for hunk in &hunks {
11450 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11451 }
11452 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11453 }
11454 if !revert_changes.is_empty() {
11455 self.buffer().update(cx, |multi_buffer, cx| {
11456 for (buffer_id, changes) in revert_changes {
11457 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
11458 buffer.update(cx, |buffer, cx| {
11459 buffer.edit(
11460 changes
11461 .into_iter()
11462 .map(|(range, text)| (range, text.to_string())),
11463 None,
11464 cx,
11465 );
11466 });
11467 }
11468 }
11469 });
11470 }
11471 }
11472
11473 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11474 if let Some(status) = self
11475 .addons
11476 .iter()
11477 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11478 {
11479 return Some(status);
11480 }
11481 self.project
11482 .as_ref()?
11483 .read(cx)
11484 .status_for_buffer_id(buffer_id, cx)
11485 }
11486
11487 pub fn open_active_item_in_terminal(
11488 &mut self,
11489 _: &OpenInTerminal,
11490 window: &mut Window,
11491 cx: &mut Context<Self>,
11492 ) {
11493 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11494 let project_path = buffer.read(cx).project_path(cx)?;
11495 let project = self.project()?.read(cx);
11496 let entry = project.entry_for_path(&project_path, cx)?;
11497 let parent = match &entry.canonical_path {
11498 Some(canonical_path) => canonical_path.to_path_buf(),
11499 None => project.absolute_path(&project_path, cx)?,
11500 }
11501 .parent()?
11502 .to_path_buf();
11503 Some(parent)
11504 }) {
11505 window.dispatch_action(
11506 OpenTerminal {
11507 working_directory,
11508 local: false,
11509 }
11510 .boxed_clone(),
11511 cx,
11512 );
11513 }
11514 }
11515
11516 fn set_breakpoint_context_menu(
11517 &mut self,
11518 display_row: DisplayRow,
11519 position: Option<Anchor>,
11520 clicked_point: gpui::Point<Pixels>,
11521 window: &mut Window,
11522 cx: &mut Context<Self>,
11523 ) {
11524 let source = self
11525 .buffer
11526 .read(cx)
11527 .snapshot(cx)
11528 .anchor_before(Point::new(display_row.0, 0u32));
11529
11530 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11531
11532 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11533 self,
11534 source,
11535 clicked_point,
11536 context_menu,
11537 window,
11538 cx,
11539 );
11540 }
11541
11542 fn add_edit_breakpoint_block(
11543 &mut self,
11544 anchor: Anchor,
11545 breakpoint: &Breakpoint,
11546 edit_action: BreakpointPromptEditAction,
11547 window: &mut Window,
11548 cx: &mut Context<Self>,
11549 ) {
11550 let weak_editor = cx.weak_entity();
11551 let bp_prompt = cx.new(|cx| {
11552 BreakpointPromptEditor::new(
11553 weak_editor,
11554 anchor,
11555 breakpoint.clone(),
11556 edit_action,
11557 window,
11558 cx,
11559 )
11560 });
11561
11562 let height = bp_prompt.update(cx, |this, cx| {
11563 this.prompt
11564 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11565 });
11566 let cloned_prompt = bp_prompt.clone();
11567 let blocks = vec![BlockProperties {
11568 style: BlockStyle::Sticky,
11569 placement: BlockPlacement::Above(anchor),
11570 height: Some(height),
11571 render: Arc::new(move |cx| {
11572 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11573 cloned_prompt.clone().into_any_element()
11574 }),
11575 priority: 0,
11576 }];
11577
11578 let focus_handle = bp_prompt.focus_handle(cx);
11579 window.focus(&focus_handle, cx);
11580
11581 let block_ids = self.insert_blocks(blocks, None, cx);
11582 bp_prompt.update(cx, |prompt, _| {
11583 prompt.add_block_ids(block_ids);
11584 });
11585 }
11586
11587 pub(crate) fn breakpoint_at_row(
11588 &self,
11589 row: u32,
11590 window: &mut Window,
11591 cx: &mut Context<Self>,
11592 ) -> Option<(Anchor, Breakpoint)> {
11593 let snapshot = self.snapshot(window, cx);
11594 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11595
11596 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11597 }
11598
11599 pub(crate) fn breakpoint_at_anchor(
11600 &self,
11601 breakpoint_position: Anchor,
11602 snapshot: &EditorSnapshot,
11603 cx: &mut Context<Self>,
11604 ) -> Option<(Anchor, Breakpoint)> {
11605 let buffer = self
11606 .buffer
11607 .read(cx)
11608 .buffer_for_anchor(breakpoint_position, cx)?;
11609
11610 let enclosing_excerpt = breakpoint_position.excerpt_id;
11611 let buffer_snapshot = buffer.read(cx).snapshot();
11612
11613 let row = buffer_snapshot
11614 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11615 .row;
11616
11617 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11618 let anchor_end = snapshot
11619 .buffer_snapshot()
11620 .anchor_after(Point::new(row, line_len));
11621
11622 self.breakpoint_store
11623 .as_ref()?
11624 .read_with(cx, |breakpoint_store, cx| {
11625 breakpoint_store
11626 .breakpoints(
11627 &buffer,
11628 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11629 &buffer_snapshot,
11630 cx,
11631 )
11632 .next()
11633 .and_then(|(bp, _)| {
11634 let breakpoint_row = buffer_snapshot
11635 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11636 .row;
11637
11638 if breakpoint_row == row {
11639 snapshot
11640 .buffer_snapshot()
11641 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11642 .map(|position| (position, bp.bp.clone()))
11643 } else {
11644 None
11645 }
11646 })
11647 })
11648 }
11649
11650 pub fn edit_log_breakpoint(
11651 &mut self,
11652 _: &EditLogBreakpoint,
11653 window: &mut Window,
11654 cx: &mut Context<Self>,
11655 ) {
11656 if self.breakpoint_store.is_none() {
11657 return;
11658 }
11659
11660 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11661 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11662 message: None,
11663 state: BreakpointState::Enabled,
11664 condition: None,
11665 hit_condition: None,
11666 });
11667
11668 self.add_edit_breakpoint_block(
11669 anchor,
11670 &breakpoint,
11671 BreakpointPromptEditAction::Log,
11672 window,
11673 cx,
11674 );
11675 }
11676 }
11677
11678 fn breakpoints_at_cursors(
11679 &self,
11680 window: &mut Window,
11681 cx: &mut Context<Self>,
11682 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11683 let snapshot = self.snapshot(window, cx);
11684 let cursors = self
11685 .selections
11686 .disjoint_anchors_arc()
11687 .iter()
11688 .map(|selection| {
11689 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11690
11691 let breakpoint_position = self
11692 .breakpoint_at_row(cursor_position.row, window, cx)
11693 .map(|bp| bp.0)
11694 .unwrap_or_else(|| {
11695 snapshot
11696 .display_snapshot
11697 .buffer_snapshot()
11698 .anchor_after(Point::new(cursor_position.row, 0))
11699 });
11700
11701 let breakpoint = self
11702 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11703 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11704
11705 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11706 })
11707 // 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.
11708 .collect::<HashMap<Anchor, _>>();
11709
11710 cursors.into_iter().collect()
11711 }
11712
11713 pub fn enable_breakpoint(
11714 &mut self,
11715 _: &crate::actions::EnableBreakpoint,
11716 window: &mut Window,
11717 cx: &mut Context<Self>,
11718 ) {
11719 if self.breakpoint_store.is_none() {
11720 return;
11721 }
11722
11723 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11724 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11725 continue;
11726 };
11727 self.edit_breakpoint_at_anchor(
11728 anchor,
11729 breakpoint,
11730 BreakpointEditAction::InvertState,
11731 cx,
11732 );
11733 }
11734 }
11735
11736 pub fn disable_breakpoint(
11737 &mut self,
11738 _: &crate::actions::DisableBreakpoint,
11739 window: &mut Window,
11740 cx: &mut Context<Self>,
11741 ) {
11742 if self.breakpoint_store.is_none() {
11743 return;
11744 }
11745
11746 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11747 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11748 continue;
11749 };
11750 self.edit_breakpoint_at_anchor(
11751 anchor,
11752 breakpoint,
11753 BreakpointEditAction::InvertState,
11754 cx,
11755 );
11756 }
11757 }
11758
11759 pub fn toggle_breakpoint(
11760 &mut self,
11761 _: &crate::actions::ToggleBreakpoint,
11762 window: &mut Window,
11763 cx: &mut Context<Self>,
11764 ) {
11765 if self.breakpoint_store.is_none() {
11766 return;
11767 }
11768
11769 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11770 if let Some(breakpoint) = breakpoint {
11771 self.edit_breakpoint_at_anchor(
11772 anchor,
11773 breakpoint,
11774 BreakpointEditAction::Toggle,
11775 cx,
11776 );
11777 } else {
11778 self.edit_breakpoint_at_anchor(
11779 anchor,
11780 Breakpoint::new_standard(),
11781 BreakpointEditAction::Toggle,
11782 cx,
11783 );
11784 }
11785 }
11786 }
11787
11788 pub fn edit_breakpoint_at_anchor(
11789 &mut self,
11790 breakpoint_position: Anchor,
11791 breakpoint: Breakpoint,
11792 edit_action: BreakpointEditAction,
11793 cx: &mut Context<Self>,
11794 ) {
11795 let Some(breakpoint_store) = &self.breakpoint_store else {
11796 return;
11797 };
11798
11799 let Some(buffer) = self
11800 .buffer
11801 .read(cx)
11802 .buffer_for_anchor(breakpoint_position, cx)
11803 else {
11804 return;
11805 };
11806
11807 breakpoint_store.update(cx, |breakpoint_store, cx| {
11808 breakpoint_store.toggle_breakpoint(
11809 buffer,
11810 BreakpointWithPosition {
11811 position: breakpoint_position.text_anchor,
11812 bp: breakpoint,
11813 },
11814 edit_action,
11815 cx,
11816 );
11817 });
11818
11819 cx.notify();
11820 }
11821
11822 #[cfg(any(test, feature = "test-support"))]
11823 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11824 self.breakpoint_store.clone()
11825 }
11826
11827 pub fn prepare_restore_change(
11828 &self,
11829 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11830 hunk: &MultiBufferDiffHunk,
11831 cx: &mut App,
11832 ) -> Option<()> {
11833 if hunk.is_created_file() {
11834 return None;
11835 }
11836 let buffer = self.buffer.read(cx);
11837 let diff = buffer.diff_for(hunk.buffer_id)?;
11838 let buffer = buffer.buffer(hunk.buffer_id)?;
11839 let buffer = buffer.read(cx);
11840 let original_text = diff
11841 .read(cx)
11842 .base_text(cx)
11843 .as_rope()
11844 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
11845 let buffer_snapshot = buffer.snapshot();
11846 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11847 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11848 probe
11849 .0
11850 .start
11851 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11852 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11853 }) {
11854 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11855 Some(())
11856 } else {
11857 None
11858 }
11859 }
11860
11861 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11862 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11863 }
11864
11865 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11866 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11867 }
11868
11869 pub fn rotate_selections_forward(
11870 &mut self,
11871 _: &RotateSelectionsForward,
11872 window: &mut Window,
11873 cx: &mut Context<Self>,
11874 ) {
11875 self.rotate_selections(window, cx, false)
11876 }
11877
11878 pub fn rotate_selections_backward(
11879 &mut self,
11880 _: &RotateSelectionsBackward,
11881 window: &mut Window,
11882 cx: &mut Context<Self>,
11883 ) {
11884 self.rotate_selections(window, cx, true)
11885 }
11886
11887 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
11888 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11889 let display_snapshot = self.display_snapshot(cx);
11890 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
11891
11892 if selections.len() < 2 {
11893 return;
11894 }
11895
11896 let (edits, new_selections) = {
11897 let buffer = self.buffer.read(cx).read(cx);
11898 let has_selections = selections.iter().any(|s| !s.is_empty());
11899 if has_selections {
11900 let mut selected_texts: Vec<String> = selections
11901 .iter()
11902 .map(|selection| {
11903 buffer
11904 .text_for_range(selection.start..selection.end)
11905 .collect()
11906 })
11907 .collect();
11908
11909 if reverse {
11910 selected_texts.rotate_left(1);
11911 } else {
11912 selected_texts.rotate_right(1);
11913 }
11914
11915 let mut offset_delta: i64 = 0;
11916 let mut new_selections = Vec::new();
11917 let edits: Vec<_> = selections
11918 .iter()
11919 .zip(selected_texts.iter())
11920 .map(|(selection, new_text)| {
11921 let old_len = (selection.end.0 - selection.start.0) as i64;
11922 let new_len = new_text.len() as i64;
11923 let adjusted_start =
11924 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
11925 let adjusted_end =
11926 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
11927
11928 new_selections.push(Selection {
11929 id: selection.id,
11930 start: adjusted_start,
11931 end: adjusted_end,
11932 reversed: selection.reversed,
11933 goal: selection.goal,
11934 });
11935
11936 offset_delta += new_len - old_len;
11937 (selection.start..selection.end, new_text.clone())
11938 })
11939 .collect();
11940 (edits, new_selections)
11941 } else {
11942 let mut all_rows: Vec<u32> = selections
11943 .iter()
11944 .map(|selection| buffer.offset_to_point(selection.start).row)
11945 .collect();
11946 all_rows.sort_unstable();
11947 all_rows.dedup();
11948
11949 if all_rows.len() < 2 {
11950 return;
11951 }
11952
11953 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
11954 .iter()
11955 .map(|&row| {
11956 let start = Point::new(row, 0);
11957 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11958 buffer.point_to_offset(start)..buffer.point_to_offset(end)
11959 })
11960 .collect();
11961
11962 let mut line_texts: Vec<String> = line_ranges
11963 .iter()
11964 .map(|range| buffer.text_for_range(range.clone()).collect())
11965 .collect();
11966
11967 if reverse {
11968 line_texts.rotate_left(1);
11969 } else {
11970 line_texts.rotate_right(1);
11971 }
11972
11973 let edits = line_ranges
11974 .iter()
11975 .zip(line_texts.iter())
11976 .map(|(range, new_text)| (range.clone(), new_text.clone()))
11977 .collect();
11978
11979 let num_rows = all_rows.len();
11980 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
11981 .iter()
11982 .enumerate()
11983 .map(|(i, &row)| (row, i))
11984 .collect();
11985
11986 // Compute new line start offsets after rotation (handles CRLF)
11987 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
11988 let first_line_start = line_ranges[0].start.0;
11989 let mut new_line_starts: Vec<usize> = vec![first_line_start];
11990 for text in line_texts.iter().take(num_rows - 1) {
11991 let prev_start = *new_line_starts.last().unwrap();
11992 new_line_starts.push(prev_start + text.len() + newline_len);
11993 }
11994
11995 let new_selections = selections
11996 .iter()
11997 .map(|selection| {
11998 let point = buffer.offset_to_point(selection.start);
11999 let old_index = row_to_index[&point.row];
12000 let new_index = if reverse {
12001 (old_index + num_rows - 1) % num_rows
12002 } else {
12003 (old_index + 1) % num_rows
12004 };
12005 let new_offset =
12006 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
12007 Selection {
12008 id: selection.id,
12009 start: new_offset,
12010 end: new_offset,
12011 reversed: selection.reversed,
12012 goal: selection.goal,
12013 }
12014 })
12015 .collect();
12016
12017 (edits, new_selections)
12018 }
12019 };
12020
12021 self.transact(window, cx, |this, window, cx| {
12022 this.buffer.update(cx, |buffer, cx| {
12023 buffer.edit(edits, None, cx);
12024 });
12025 this.change_selections(Default::default(), window, cx, |s| {
12026 s.select(new_selections);
12027 });
12028 });
12029 }
12030
12031 fn manipulate_lines<M>(
12032 &mut self,
12033 window: &mut Window,
12034 cx: &mut Context<Self>,
12035 mut manipulate: M,
12036 ) where
12037 M: FnMut(&str) -> LineManipulationResult,
12038 {
12039 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12040
12041 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12042 let buffer = self.buffer.read(cx).snapshot(cx);
12043
12044 let mut edits = Vec::new();
12045
12046 let selections = self.selections.all::<Point>(&display_map);
12047 let mut selections = selections.iter().peekable();
12048 let mut contiguous_row_selections = Vec::new();
12049 let mut new_selections = Vec::new();
12050 let mut added_lines = 0;
12051 let mut removed_lines = 0;
12052
12053 while let Some(selection) = selections.next() {
12054 let (start_row, end_row) = consume_contiguous_rows(
12055 &mut contiguous_row_selections,
12056 selection,
12057 &display_map,
12058 &mut selections,
12059 );
12060
12061 let start_point = Point::new(start_row.0, 0);
12062 let end_point = Point::new(
12063 end_row.previous_row().0,
12064 buffer.line_len(end_row.previous_row()),
12065 );
12066 let text = buffer
12067 .text_for_range(start_point..end_point)
12068 .collect::<String>();
12069
12070 let LineManipulationResult {
12071 new_text,
12072 line_count_before,
12073 line_count_after,
12074 } = manipulate(&text);
12075
12076 edits.push((start_point..end_point, new_text));
12077
12078 // Selections must change based on added and removed line count
12079 let start_row =
12080 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
12081 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
12082 new_selections.push(Selection {
12083 id: selection.id,
12084 start: start_row,
12085 end: end_row,
12086 goal: SelectionGoal::None,
12087 reversed: selection.reversed,
12088 });
12089
12090 if line_count_after > line_count_before {
12091 added_lines += line_count_after - line_count_before;
12092 } else if line_count_before > line_count_after {
12093 removed_lines += line_count_before - line_count_after;
12094 }
12095 }
12096
12097 self.transact(window, cx, |this, window, cx| {
12098 let buffer = this.buffer.update(cx, |buffer, cx| {
12099 buffer.edit(edits, None, cx);
12100 buffer.snapshot(cx)
12101 });
12102
12103 // Recalculate offsets on newly edited buffer
12104 let new_selections = new_selections
12105 .iter()
12106 .map(|s| {
12107 let start_point = Point::new(s.start.0, 0);
12108 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
12109 Selection {
12110 id: s.id,
12111 start: buffer.point_to_offset(start_point),
12112 end: buffer.point_to_offset(end_point),
12113 goal: s.goal,
12114 reversed: s.reversed,
12115 }
12116 })
12117 .collect();
12118
12119 this.change_selections(Default::default(), window, cx, |s| {
12120 s.select(new_selections);
12121 });
12122
12123 this.request_autoscroll(Autoscroll::fit(), cx);
12124 });
12125 }
12126
12127 fn manipulate_immutable_lines<Fn>(
12128 &mut self,
12129 window: &mut Window,
12130 cx: &mut Context<Self>,
12131 mut callback: Fn,
12132 ) where
12133 Fn: FnMut(&mut Vec<&str>),
12134 {
12135 self.manipulate_lines(window, cx, |text| {
12136 let mut lines: Vec<&str> = text.split('\n').collect();
12137 let line_count_before = lines.len();
12138
12139 callback(&mut lines);
12140
12141 LineManipulationResult {
12142 new_text: lines.join("\n"),
12143 line_count_before,
12144 line_count_after: lines.len(),
12145 }
12146 });
12147 }
12148
12149 fn manipulate_mutable_lines<Fn>(
12150 &mut self,
12151 window: &mut Window,
12152 cx: &mut Context<Self>,
12153 mut callback: Fn,
12154 ) where
12155 Fn: FnMut(&mut Vec<Cow<'_, str>>),
12156 {
12157 self.manipulate_lines(window, cx, |text| {
12158 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
12159 let line_count_before = lines.len();
12160
12161 callback(&mut lines);
12162
12163 LineManipulationResult {
12164 new_text: lines.join("\n"),
12165 line_count_before,
12166 line_count_after: lines.len(),
12167 }
12168 });
12169 }
12170
12171 pub fn convert_indentation_to_spaces(
12172 &mut self,
12173 _: &ConvertIndentationToSpaces,
12174 window: &mut Window,
12175 cx: &mut Context<Self>,
12176 ) {
12177 let settings = self.buffer.read(cx).language_settings(cx);
12178 let tab_size = settings.tab_size.get() as usize;
12179
12180 self.manipulate_mutable_lines(window, cx, |lines| {
12181 // Allocates a reasonably sized scratch buffer once for the whole loop
12182 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12183 // Avoids recomputing spaces that could be inserted many times
12184 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12185 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12186 .collect();
12187
12188 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12189 let mut chars = line.as_ref().chars();
12190 let mut col = 0;
12191 let mut changed = false;
12192
12193 for ch in chars.by_ref() {
12194 match ch {
12195 ' ' => {
12196 reindented_line.push(' ');
12197 col += 1;
12198 }
12199 '\t' => {
12200 // \t are converted to spaces depending on the current column
12201 let spaces_len = tab_size - (col % tab_size);
12202 reindented_line.extend(&space_cache[spaces_len - 1]);
12203 col += spaces_len;
12204 changed = true;
12205 }
12206 _ => {
12207 // If we dont append before break, the character is consumed
12208 reindented_line.push(ch);
12209 break;
12210 }
12211 }
12212 }
12213
12214 if !changed {
12215 reindented_line.clear();
12216 continue;
12217 }
12218 // Append the rest of the line and replace old reference with new one
12219 reindented_line.extend(chars);
12220 *line = Cow::Owned(reindented_line.clone());
12221 reindented_line.clear();
12222 }
12223 });
12224 }
12225
12226 pub fn convert_indentation_to_tabs(
12227 &mut self,
12228 _: &ConvertIndentationToTabs,
12229 window: &mut Window,
12230 cx: &mut Context<Self>,
12231 ) {
12232 let settings = self.buffer.read(cx).language_settings(cx);
12233 let tab_size = settings.tab_size.get() as usize;
12234
12235 self.manipulate_mutable_lines(window, cx, |lines| {
12236 // Allocates a reasonably sized buffer once for the whole loop
12237 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12238 // Avoids recomputing spaces that could be inserted many times
12239 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12240 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12241 .collect();
12242
12243 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12244 let mut chars = line.chars();
12245 let mut spaces_count = 0;
12246 let mut first_non_indent_char = None;
12247 let mut changed = false;
12248
12249 for ch in chars.by_ref() {
12250 match ch {
12251 ' ' => {
12252 // Keep track of spaces. Append \t when we reach tab_size
12253 spaces_count += 1;
12254 changed = true;
12255 if spaces_count == tab_size {
12256 reindented_line.push('\t');
12257 spaces_count = 0;
12258 }
12259 }
12260 '\t' => {
12261 reindented_line.push('\t');
12262 spaces_count = 0;
12263 }
12264 _ => {
12265 // Dont append it yet, we might have remaining spaces
12266 first_non_indent_char = Some(ch);
12267 break;
12268 }
12269 }
12270 }
12271
12272 if !changed {
12273 reindented_line.clear();
12274 continue;
12275 }
12276 // Remaining spaces that didn't make a full tab stop
12277 if spaces_count > 0 {
12278 reindented_line.extend(&space_cache[spaces_count - 1]);
12279 }
12280 // If we consume an extra character that was not indentation, add it back
12281 if let Some(extra_char) = first_non_indent_char {
12282 reindented_line.push(extra_char);
12283 }
12284 // Append the rest of the line and replace old reference with new one
12285 reindented_line.extend(chars);
12286 *line = Cow::Owned(reindented_line.clone());
12287 reindented_line.clear();
12288 }
12289 });
12290 }
12291
12292 pub fn convert_to_upper_case(
12293 &mut self,
12294 _: &ConvertToUpperCase,
12295 window: &mut Window,
12296 cx: &mut Context<Self>,
12297 ) {
12298 self.manipulate_text(window, cx, |text| text.to_uppercase())
12299 }
12300
12301 pub fn convert_to_lower_case(
12302 &mut self,
12303 _: &ConvertToLowerCase,
12304 window: &mut Window,
12305 cx: &mut Context<Self>,
12306 ) {
12307 self.manipulate_text(window, cx, |text| text.to_lowercase())
12308 }
12309
12310 pub fn convert_to_title_case(
12311 &mut self,
12312 _: &ConvertToTitleCase,
12313 window: &mut Window,
12314 cx: &mut Context<Self>,
12315 ) {
12316 self.manipulate_text(window, cx, |text| {
12317 text.split('\n')
12318 .map(|line| line.to_case(Case::Title))
12319 .join("\n")
12320 })
12321 }
12322
12323 pub fn convert_to_snake_case(
12324 &mut self,
12325 _: &ConvertToSnakeCase,
12326 window: &mut Window,
12327 cx: &mut Context<Self>,
12328 ) {
12329 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
12330 }
12331
12332 pub fn convert_to_kebab_case(
12333 &mut self,
12334 _: &ConvertToKebabCase,
12335 window: &mut Window,
12336 cx: &mut Context<Self>,
12337 ) {
12338 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
12339 }
12340
12341 pub fn convert_to_upper_camel_case(
12342 &mut self,
12343 _: &ConvertToUpperCamelCase,
12344 window: &mut Window,
12345 cx: &mut Context<Self>,
12346 ) {
12347 self.manipulate_text(window, cx, |text| {
12348 text.split('\n')
12349 .map(|line| line.to_case(Case::UpperCamel))
12350 .join("\n")
12351 })
12352 }
12353
12354 pub fn convert_to_lower_camel_case(
12355 &mut self,
12356 _: &ConvertToLowerCamelCase,
12357 window: &mut Window,
12358 cx: &mut Context<Self>,
12359 ) {
12360 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
12361 }
12362
12363 pub fn convert_to_opposite_case(
12364 &mut self,
12365 _: &ConvertToOppositeCase,
12366 window: &mut Window,
12367 cx: &mut Context<Self>,
12368 ) {
12369 self.manipulate_text(window, cx, |text| {
12370 text.chars()
12371 .fold(String::with_capacity(text.len()), |mut t, c| {
12372 if c.is_uppercase() {
12373 t.extend(c.to_lowercase());
12374 } else {
12375 t.extend(c.to_uppercase());
12376 }
12377 t
12378 })
12379 })
12380 }
12381
12382 pub fn convert_to_sentence_case(
12383 &mut self,
12384 _: &ConvertToSentenceCase,
12385 window: &mut Window,
12386 cx: &mut Context<Self>,
12387 ) {
12388 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
12389 }
12390
12391 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
12392 self.manipulate_text(window, cx, |text| {
12393 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
12394 if has_upper_case_characters {
12395 text.to_lowercase()
12396 } else {
12397 text.to_uppercase()
12398 }
12399 })
12400 }
12401
12402 pub fn convert_to_rot13(
12403 &mut self,
12404 _: &ConvertToRot13,
12405 window: &mut Window,
12406 cx: &mut Context<Self>,
12407 ) {
12408 self.manipulate_text(window, cx, |text| {
12409 text.chars()
12410 .map(|c| match c {
12411 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
12412 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
12413 _ => c,
12414 })
12415 .collect()
12416 })
12417 }
12418
12419 pub fn convert_to_rot47(
12420 &mut self,
12421 _: &ConvertToRot47,
12422 window: &mut Window,
12423 cx: &mut Context<Self>,
12424 ) {
12425 self.manipulate_text(window, cx, |text| {
12426 text.chars()
12427 .map(|c| {
12428 let code_point = c as u32;
12429 if code_point >= 33 && code_point <= 126 {
12430 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
12431 }
12432 c
12433 })
12434 .collect()
12435 })
12436 }
12437
12438 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
12439 where
12440 Fn: FnMut(&str) -> String,
12441 {
12442 let buffer = self.buffer.read(cx).snapshot(cx);
12443
12444 let mut new_selections = Vec::new();
12445 let mut edits = Vec::new();
12446 let mut selection_adjustment = 0isize;
12447
12448 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
12449 let selection_is_empty = selection.is_empty();
12450
12451 let (start, end) = if selection_is_empty {
12452 let (word_range, _) = buffer.surrounding_word(selection.start, None);
12453 (word_range.start, word_range.end)
12454 } else {
12455 (
12456 buffer.point_to_offset(selection.start),
12457 buffer.point_to_offset(selection.end),
12458 )
12459 };
12460
12461 let text = buffer.text_for_range(start..end).collect::<String>();
12462 let old_length = text.len() as isize;
12463 let text = callback(&text);
12464
12465 new_selections.push(Selection {
12466 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
12467 end: MultiBufferOffset(
12468 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
12469 ),
12470 goal: SelectionGoal::None,
12471 id: selection.id,
12472 reversed: selection.reversed,
12473 });
12474
12475 selection_adjustment += old_length - text.len() as isize;
12476
12477 edits.push((start..end, text));
12478 }
12479
12480 self.transact(window, cx, |this, window, cx| {
12481 this.buffer.update(cx, |buffer, cx| {
12482 buffer.edit(edits, None, cx);
12483 });
12484
12485 this.change_selections(Default::default(), window, cx, |s| {
12486 s.select(new_selections);
12487 });
12488
12489 this.request_autoscroll(Autoscroll::fit(), cx);
12490 });
12491 }
12492
12493 pub fn move_selection_on_drop(
12494 &mut self,
12495 selection: &Selection<Anchor>,
12496 target: DisplayPoint,
12497 is_cut: bool,
12498 window: &mut Window,
12499 cx: &mut Context<Self>,
12500 ) {
12501 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12502 let buffer = display_map.buffer_snapshot();
12503 let mut edits = Vec::new();
12504 let insert_point = display_map
12505 .clip_point(target, Bias::Left)
12506 .to_point(&display_map);
12507 let text = buffer
12508 .text_for_range(selection.start..selection.end)
12509 .collect::<String>();
12510 if is_cut {
12511 edits.push(((selection.start..selection.end), String::new()));
12512 }
12513 let insert_anchor = buffer.anchor_before(insert_point);
12514 edits.push(((insert_anchor..insert_anchor), text));
12515 let last_edit_start = insert_anchor.bias_left(buffer);
12516 let last_edit_end = insert_anchor.bias_right(buffer);
12517 self.transact(window, cx, |this, window, cx| {
12518 this.buffer.update(cx, |buffer, cx| {
12519 buffer.edit(edits, None, cx);
12520 });
12521 this.change_selections(Default::default(), window, cx, |s| {
12522 s.select_anchor_ranges([last_edit_start..last_edit_end]);
12523 });
12524 });
12525 }
12526
12527 pub fn clear_selection_drag_state(&mut self) {
12528 self.selection_drag_state = SelectionDragState::None;
12529 }
12530
12531 pub fn duplicate(
12532 &mut self,
12533 upwards: bool,
12534 whole_lines: bool,
12535 window: &mut Window,
12536 cx: &mut Context<Self>,
12537 ) {
12538 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12539
12540 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12541 let buffer = display_map.buffer_snapshot();
12542 let selections = self.selections.all::<Point>(&display_map);
12543
12544 let mut edits = Vec::new();
12545 let mut selections_iter = selections.iter().peekable();
12546 while let Some(selection) = selections_iter.next() {
12547 let mut rows = selection.spanned_rows(false, &display_map);
12548 // duplicate line-wise
12549 if whole_lines || selection.start == selection.end {
12550 // Avoid duplicating the same lines twice.
12551 while let Some(next_selection) = selections_iter.peek() {
12552 let next_rows = next_selection.spanned_rows(false, &display_map);
12553 if next_rows.start < rows.end {
12554 rows.end = next_rows.end;
12555 selections_iter.next().unwrap();
12556 } else {
12557 break;
12558 }
12559 }
12560
12561 // Copy the text from the selected row region and splice it either at the start
12562 // or end of the region.
12563 let start = Point::new(rows.start.0, 0);
12564 let end = Point::new(
12565 rows.end.previous_row().0,
12566 buffer.line_len(rows.end.previous_row()),
12567 );
12568
12569 let mut text = buffer.text_for_range(start..end).collect::<String>();
12570
12571 let insert_location = if upwards {
12572 // When duplicating upward, we need to insert before the current line.
12573 // If we're on the last line and it doesn't end with a newline,
12574 // we need to add a newline before the duplicated content.
12575 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
12576 && buffer.max_point().column > 0
12577 && !text.ends_with('\n');
12578
12579 if needs_leading_newline {
12580 text.insert(0, '\n');
12581 end
12582 } else {
12583 text.push('\n');
12584 Point::new(rows.start.0, 0)
12585 }
12586 } else {
12587 text.push('\n');
12588 start
12589 };
12590 edits.push((insert_location..insert_location, text));
12591 } else {
12592 // duplicate character-wise
12593 let start = selection.start;
12594 let end = selection.end;
12595 let text = buffer.text_for_range(start..end).collect::<String>();
12596 edits.push((selection.end..selection.end, text));
12597 }
12598 }
12599
12600 self.transact(window, cx, |this, window, cx| {
12601 this.buffer.update(cx, |buffer, cx| {
12602 buffer.edit(edits, None, cx);
12603 });
12604
12605 // When duplicating upward with whole lines, move the cursor to the duplicated line
12606 if upwards && whole_lines {
12607 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
12608
12609 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12610 let mut new_ranges = Vec::new();
12611 let selections = s.all::<Point>(&display_map);
12612 let mut selections_iter = selections.iter().peekable();
12613
12614 while let Some(first_selection) = selections_iter.next() {
12615 // Group contiguous selections together to find the total row span
12616 let mut group_selections = vec![first_selection];
12617 let mut rows = first_selection.spanned_rows(false, &display_map);
12618
12619 while let Some(next_selection) = selections_iter.peek() {
12620 let next_rows = next_selection.spanned_rows(false, &display_map);
12621 if next_rows.start < rows.end {
12622 rows.end = next_rows.end;
12623 group_selections.push(selections_iter.next().unwrap());
12624 } else {
12625 break;
12626 }
12627 }
12628
12629 let row_count = rows.end.0 - rows.start.0;
12630
12631 // Move all selections in this group up by the total number of duplicated rows
12632 for selection in group_selections {
12633 let new_start = Point::new(
12634 selection.start.row.saturating_sub(row_count),
12635 selection.start.column,
12636 );
12637
12638 let new_end = Point::new(
12639 selection.end.row.saturating_sub(row_count),
12640 selection.end.column,
12641 );
12642
12643 new_ranges.push(new_start..new_end);
12644 }
12645 }
12646
12647 s.select_ranges(new_ranges);
12648 });
12649 }
12650
12651 this.request_autoscroll(Autoscroll::fit(), cx);
12652 });
12653 }
12654
12655 pub fn duplicate_line_up(
12656 &mut self,
12657 _: &DuplicateLineUp,
12658 window: &mut Window,
12659 cx: &mut Context<Self>,
12660 ) {
12661 self.duplicate(true, true, window, cx);
12662 }
12663
12664 pub fn duplicate_line_down(
12665 &mut self,
12666 _: &DuplicateLineDown,
12667 window: &mut Window,
12668 cx: &mut Context<Self>,
12669 ) {
12670 self.duplicate(false, true, window, cx);
12671 }
12672
12673 pub fn duplicate_selection(
12674 &mut self,
12675 _: &DuplicateSelection,
12676 window: &mut Window,
12677 cx: &mut Context<Self>,
12678 ) {
12679 self.duplicate(false, false, window, cx);
12680 }
12681
12682 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
12683 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12684 if self.mode.is_single_line() {
12685 cx.propagate();
12686 return;
12687 }
12688
12689 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12690 let buffer = self.buffer.read(cx).snapshot(cx);
12691
12692 let mut edits = Vec::new();
12693 let mut unfold_ranges = Vec::new();
12694 let mut refold_creases = Vec::new();
12695
12696 let selections = self.selections.all::<Point>(&display_map);
12697 let mut selections = selections.iter().peekable();
12698 let mut contiguous_row_selections = Vec::new();
12699 let mut new_selections = Vec::new();
12700
12701 while let Some(selection) = selections.next() {
12702 // Find all the selections that span a contiguous row range
12703 let (start_row, end_row) = consume_contiguous_rows(
12704 &mut contiguous_row_selections,
12705 selection,
12706 &display_map,
12707 &mut selections,
12708 );
12709
12710 // Move the text spanned by the row range to be before the line preceding the row range
12711 if start_row.0 > 0 {
12712 let range_to_move = Point::new(
12713 start_row.previous_row().0,
12714 buffer.line_len(start_row.previous_row()),
12715 )
12716 ..Point::new(
12717 end_row.previous_row().0,
12718 buffer.line_len(end_row.previous_row()),
12719 );
12720 let insertion_point = display_map
12721 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
12722 .0;
12723
12724 // Don't move lines across excerpts
12725 if buffer
12726 .excerpt_containing(insertion_point..range_to_move.end)
12727 .is_some()
12728 {
12729 let text = buffer
12730 .text_for_range(range_to_move.clone())
12731 .flat_map(|s| s.chars())
12732 .skip(1)
12733 .chain(['\n'])
12734 .collect::<String>();
12735
12736 edits.push((
12737 buffer.anchor_after(range_to_move.start)
12738 ..buffer.anchor_before(range_to_move.end),
12739 String::new(),
12740 ));
12741 let insertion_anchor = buffer.anchor_after(insertion_point);
12742 edits.push((insertion_anchor..insertion_anchor, text));
12743
12744 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12745
12746 // Move selections up
12747 new_selections.extend(contiguous_row_selections.drain(..).map(
12748 |mut selection| {
12749 selection.start.row -= row_delta;
12750 selection.end.row -= row_delta;
12751 selection
12752 },
12753 ));
12754
12755 // Move folds up
12756 unfold_ranges.push(range_to_move.clone());
12757 for fold in display_map.folds_in_range(
12758 buffer.anchor_before(range_to_move.start)
12759 ..buffer.anchor_after(range_to_move.end),
12760 ) {
12761 let mut start = fold.range.start.to_point(&buffer);
12762 let mut end = fold.range.end.to_point(&buffer);
12763 start.row -= row_delta;
12764 end.row -= row_delta;
12765 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12766 }
12767 }
12768 }
12769
12770 // If we didn't move line(s), preserve the existing selections
12771 new_selections.append(&mut contiguous_row_selections);
12772 }
12773
12774 self.transact(window, cx, |this, window, cx| {
12775 this.unfold_ranges(&unfold_ranges, true, true, cx);
12776 this.buffer.update(cx, |buffer, cx| {
12777 for (range, text) in edits {
12778 buffer.edit([(range, text)], None, cx);
12779 }
12780 });
12781 this.fold_creases(refold_creases, true, window, cx);
12782 this.change_selections(Default::default(), window, cx, |s| {
12783 s.select(new_selections);
12784 })
12785 });
12786 }
12787
12788 pub fn move_line_down(
12789 &mut self,
12790 _: &MoveLineDown,
12791 window: &mut Window,
12792 cx: &mut Context<Self>,
12793 ) {
12794 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12795 if self.mode.is_single_line() {
12796 cx.propagate();
12797 return;
12798 }
12799
12800 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12801 let buffer = self.buffer.read(cx).snapshot(cx);
12802
12803 let mut edits = Vec::new();
12804 let mut unfold_ranges = Vec::new();
12805 let mut refold_creases = Vec::new();
12806
12807 let selections = self.selections.all::<Point>(&display_map);
12808 let mut selections = selections.iter().peekable();
12809 let mut contiguous_row_selections = Vec::new();
12810 let mut new_selections = Vec::new();
12811
12812 while let Some(selection) = selections.next() {
12813 // Find all the selections that span a contiguous row range
12814 let (start_row, end_row) = consume_contiguous_rows(
12815 &mut contiguous_row_selections,
12816 selection,
12817 &display_map,
12818 &mut selections,
12819 );
12820
12821 // Move the text spanned by the row range to be after the last line of the row range
12822 if end_row.0 <= buffer.max_point().row {
12823 let range_to_move =
12824 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12825 let insertion_point = display_map
12826 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12827 .0;
12828
12829 // Don't move lines across excerpt boundaries
12830 if buffer
12831 .excerpt_containing(range_to_move.start..insertion_point)
12832 .is_some()
12833 {
12834 let mut text = String::from("\n");
12835 text.extend(buffer.text_for_range(range_to_move.clone()));
12836 text.pop(); // Drop trailing newline
12837 edits.push((
12838 buffer.anchor_after(range_to_move.start)
12839 ..buffer.anchor_before(range_to_move.end),
12840 String::new(),
12841 ));
12842 let insertion_anchor = buffer.anchor_after(insertion_point);
12843 edits.push((insertion_anchor..insertion_anchor, text));
12844
12845 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12846
12847 // Move selections down
12848 new_selections.extend(contiguous_row_selections.drain(..).map(
12849 |mut selection| {
12850 selection.start.row += row_delta;
12851 selection.end.row += row_delta;
12852 selection
12853 },
12854 ));
12855
12856 // Move folds down
12857 unfold_ranges.push(range_to_move.clone());
12858 for fold in display_map.folds_in_range(
12859 buffer.anchor_before(range_to_move.start)
12860 ..buffer.anchor_after(range_to_move.end),
12861 ) {
12862 let mut start = fold.range.start.to_point(&buffer);
12863 let mut end = fold.range.end.to_point(&buffer);
12864 start.row += row_delta;
12865 end.row += row_delta;
12866 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12867 }
12868 }
12869 }
12870
12871 // If we didn't move line(s), preserve the existing selections
12872 new_selections.append(&mut contiguous_row_selections);
12873 }
12874
12875 self.transact(window, cx, |this, window, cx| {
12876 this.unfold_ranges(&unfold_ranges, true, true, cx);
12877 this.buffer.update(cx, |buffer, cx| {
12878 for (range, text) in edits {
12879 buffer.edit([(range, text)], None, cx);
12880 }
12881 });
12882 this.fold_creases(refold_creases, true, window, cx);
12883 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12884 });
12885 }
12886
12887 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12888 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12889 let text_layout_details = &self.text_layout_details(window, cx);
12890 self.transact(window, cx, |this, window, cx| {
12891 let edits = this.change_selections(Default::default(), window, cx, |s| {
12892 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
12893 s.move_with(|display_map, selection| {
12894 if !selection.is_empty() {
12895 return;
12896 }
12897
12898 let mut head = selection.head();
12899 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12900 if head.column() == display_map.line_len(head.row()) {
12901 transpose_offset = display_map
12902 .buffer_snapshot()
12903 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12904 }
12905
12906 if transpose_offset == MultiBufferOffset(0) {
12907 return;
12908 }
12909
12910 *head.column_mut() += 1;
12911 head = display_map.clip_point(head, Bias::Right);
12912 let goal = SelectionGoal::HorizontalPosition(
12913 display_map
12914 .x_for_display_point(head, text_layout_details)
12915 .into(),
12916 );
12917 selection.collapse_to(head, goal);
12918
12919 let transpose_start = display_map
12920 .buffer_snapshot()
12921 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12922 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12923 let transpose_end = display_map
12924 .buffer_snapshot()
12925 .clip_offset(transpose_offset + 1usize, Bias::Right);
12926 if let Some(ch) = display_map
12927 .buffer_snapshot()
12928 .chars_at(transpose_start)
12929 .next()
12930 {
12931 edits.push((transpose_start..transpose_offset, String::new()));
12932 edits.push((transpose_end..transpose_end, ch.to_string()));
12933 }
12934 }
12935 });
12936 edits
12937 });
12938 this.buffer
12939 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12940 let selections = this
12941 .selections
12942 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
12943 this.change_selections(Default::default(), window, cx, |s| {
12944 s.select(selections);
12945 });
12946 });
12947 }
12948
12949 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12950 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12951 if self.mode.is_single_line() {
12952 cx.propagate();
12953 return;
12954 }
12955
12956 self.rewrap_impl(RewrapOptions::default(), cx)
12957 }
12958
12959 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12960 let buffer = self.buffer.read(cx).snapshot(cx);
12961 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12962
12963 #[derive(Clone, Debug, PartialEq)]
12964 enum CommentFormat {
12965 /// single line comment, with prefix for line
12966 Line(String),
12967 /// single line within a block comment, with prefix for line
12968 BlockLine(String),
12969 /// a single line of a block comment that includes the initial delimiter
12970 BlockCommentWithStart(BlockCommentConfig),
12971 /// a single line of a block comment that includes the ending delimiter
12972 BlockCommentWithEnd(BlockCommentConfig),
12973 }
12974
12975 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12976 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12977 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12978 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12979 .peekable();
12980
12981 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12982 row
12983 } else {
12984 return Vec::new();
12985 };
12986
12987 let language_settings = buffer.language_settings_at(selection.head(), cx);
12988 let language_scope = buffer.language_scope_at(selection.head());
12989
12990 let indent_and_prefix_for_row =
12991 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12992 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12993 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12994 &language_scope
12995 {
12996 let indent_end = Point::new(row, indent.len);
12997 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12998 let line_text_after_indent = buffer
12999 .text_for_range(indent_end..line_end)
13000 .collect::<String>();
13001
13002 let is_within_comment_override = buffer
13003 .language_scope_at(indent_end)
13004 .is_some_and(|scope| scope.override_name() == Some("comment"));
13005 let comment_delimiters = if is_within_comment_override {
13006 // we are within a comment syntax node, but we don't
13007 // yet know what kind of comment: block, doc or line
13008 match (
13009 language_scope.documentation_comment(),
13010 language_scope.block_comment(),
13011 ) {
13012 (Some(config), _) | (_, Some(config))
13013 if buffer.contains_str_at(indent_end, &config.start) =>
13014 {
13015 Some(CommentFormat::BlockCommentWithStart(config.clone()))
13016 }
13017 (Some(config), _) | (_, Some(config))
13018 if line_text_after_indent.ends_with(config.end.as_ref()) =>
13019 {
13020 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
13021 }
13022 (Some(config), _) | (_, Some(config))
13023 if buffer.contains_str_at(indent_end, &config.prefix) =>
13024 {
13025 Some(CommentFormat::BlockLine(config.prefix.to_string()))
13026 }
13027 (_, _) => language_scope
13028 .line_comment_prefixes()
13029 .iter()
13030 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13031 .map(|prefix| CommentFormat::Line(prefix.to_string())),
13032 }
13033 } else {
13034 // we not in an overridden comment node, but we may
13035 // be within a non-overridden line comment node
13036 language_scope
13037 .line_comment_prefixes()
13038 .iter()
13039 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13040 .map(|prefix| CommentFormat::Line(prefix.to_string()))
13041 };
13042
13043 let rewrap_prefix = language_scope
13044 .rewrap_prefixes()
13045 .iter()
13046 .find_map(|prefix_regex| {
13047 prefix_regex.find(&line_text_after_indent).map(|mat| {
13048 if mat.start() == 0 {
13049 Some(mat.as_str().to_string())
13050 } else {
13051 None
13052 }
13053 })
13054 })
13055 .flatten();
13056 (comment_delimiters, rewrap_prefix)
13057 } else {
13058 (None, None)
13059 };
13060 (indent, comment_prefix, rewrap_prefix)
13061 };
13062
13063 let mut ranges = Vec::new();
13064 let from_empty_selection = selection.is_empty();
13065
13066 let mut current_range_start = first_row;
13067 let mut prev_row = first_row;
13068 let (
13069 mut current_range_indent,
13070 mut current_range_comment_delimiters,
13071 mut current_range_rewrap_prefix,
13072 ) = indent_and_prefix_for_row(first_row);
13073
13074 for row in non_blank_rows_iter.skip(1) {
13075 let has_paragraph_break = row > prev_row + 1;
13076
13077 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
13078 indent_and_prefix_for_row(row);
13079
13080 let has_indent_change = row_indent != current_range_indent;
13081 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
13082
13083 let has_boundary_change = has_comment_change
13084 || row_rewrap_prefix.is_some()
13085 || (has_indent_change && current_range_comment_delimiters.is_some());
13086
13087 if has_paragraph_break || has_boundary_change {
13088 ranges.push((
13089 language_settings.clone(),
13090 Point::new(current_range_start, 0)
13091 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13092 current_range_indent,
13093 current_range_comment_delimiters.clone(),
13094 current_range_rewrap_prefix.clone(),
13095 from_empty_selection,
13096 ));
13097 current_range_start = row;
13098 current_range_indent = row_indent;
13099 current_range_comment_delimiters = row_comment_delimiters;
13100 current_range_rewrap_prefix = row_rewrap_prefix;
13101 }
13102 prev_row = row;
13103 }
13104
13105 ranges.push((
13106 language_settings.clone(),
13107 Point::new(current_range_start, 0)
13108 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13109 current_range_indent,
13110 current_range_comment_delimiters,
13111 current_range_rewrap_prefix,
13112 from_empty_selection,
13113 ));
13114
13115 ranges
13116 });
13117
13118 let mut edits = Vec::new();
13119 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
13120
13121 for (
13122 language_settings,
13123 wrap_range,
13124 mut indent_size,
13125 comment_prefix,
13126 rewrap_prefix,
13127 from_empty_selection,
13128 ) in wrap_ranges
13129 {
13130 let mut start_row = wrap_range.start.row;
13131 let mut end_row = wrap_range.end.row;
13132
13133 // Skip selections that overlap with a range that has already been rewrapped.
13134 let selection_range = start_row..end_row;
13135 if rewrapped_row_ranges
13136 .iter()
13137 .any(|range| range.overlaps(&selection_range))
13138 {
13139 continue;
13140 }
13141
13142 let tab_size = language_settings.tab_size;
13143
13144 let (line_prefix, inside_comment) = match &comment_prefix {
13145 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
13146 (Some(prefix.as_str()), true)
13147 }
13148 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
13149 (Some(prefix.as_ref()), true)
13150 }
13151 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13152 start: _,
13153 end: _,
13154 prefix,
13155 tab_size,
13156 })) => {
13157 indent_size.len += tab_size;
13158 (Some(prefix.as_ref()), true)
13159 }
13160 None => (None, false),
13161 };
13162 let indent_prefix = indent_size.chars().collect::<String>();
13163 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
13164
13165 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
13166 RewrapBehavior::InComments => inside_comment,
13167 RewrapBehavior::InSelections => !wrap_range.is_empty(),
13168 RewrapBehavior::Anywhere => true,
13169 };
13170
13171 let should_rewrap = options.override_language_settings
13172 || allow_rewrap_based_on_language
13173 || self.hard_wrap.is_some();
13174 if !should_rewrap {
13175 continue;
13176 }
13177
13178 if from_empty_selection {
13179 'expand_upwards: while start_row > 0 {
13180 let prev_row = start_row - 1;
13181 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
13182 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
13183 && !buffer.is_line_blank(MultiBufferRow(prev_row))
13184 {
13185 start_row = prev_row;
13186 } else {
13187 break 'expand_upwards;
13188 }
13189 }
13190
13191 'expand_downwards: while end_row < buffer.max_point().row {
13192 let next_row = end_row + 1;
13193 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
13194 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
13195 && !buffer.is_line_blank(MultiBufferRow(next_row))
13196 {
13197 end_row = next_row;
13198 } else {
13199 break 'expand_downwards;
13200 }
13201 }
13202 }
13203
13204 let start = Point::new(start_row, 0);
13205 let start_offset = ToOffset::to_offset(&start, &buffer);
13206 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
13207 let selection_text = buffer.text_for_range(start..end).collect::<String>();
13208 let mut first_line_delimiter = None;
13209 let mut last_line_delimiter = None;
13210 let Some(lines_without_prefixes) = selection_text
13211 .lines()
13212 .enumerate()
13213 .map(|(ix, line)| {
13214 let line_trimmed = line.trim_start();
13215 if rewrap_prefix.is_some() && ix > 0 {
13216 Ok(line_trimmed)
13217 } else if let Some(
13218 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13219 start,
13220 prefix,
13221 end,
13222 tab_size,
13223 })
13224 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
13225 start,
13226 prefix,
13227 end,
13228 tab_size,
13229 }),
13230 ) = &comment_prefix
13231 {
13232 let line_trimmed = line_trimmed
13233 .strip_prefix(start.as_ref())
13234 .map(|s| {
13235 let mut indent_size = indent_size;
13236 indent_size.len -= tab_size;
13237 let indent_prefix: String = indent_size.chars().collect();
13238 first_line_delimiter = Some((indent_prefix, start));
13239 s.trim_start()
13240 })
13241 .unwrap_or(line_trimmed);
13242 let line_trimmed = line_trimmed
13243 .strip_suffix(end.as_ref())
13244 .map(|s| {
13245 last_line_delimiter = Some(end);
13246 s.trim_end()
13247 })
13248 .unwrap_or(line_trimmed);
13249 let line_trimmed = line_trimmed
13250 .strip_prefix(prefix.as_ref())
13251 .unwrap_or(line_trimmed);
13252 Ok(line_trimmed)
13253 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
13254 line_trimmed.strip_prefix(prefix).with_context(|| {
13255 format!("line did not start with prefix {prefix:?}: {line:?}")
13256 })
13257 } else {
13258 line_trimmed
13259 .strip_prefix(&line_prefix.trim_start())
13260 .with_context(|| {
13261 format!("line did not start with prefix {line_prefix:?}: {line:?}")
13262 })
13263 }
13264 })
13265 .collect::<Result<Vec<_>, _>>()
13266 .log_err()
13267 else {
13268 continue;
13269 };
13270
13271 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
13272 buffer
13273 .language_settings_at(Point::new(start_row, 0), cx)
13274 .preferred_line_length as usize
13275 });
13276
13277 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
13278 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
13279 } else {
13280 line_prefix.clone()
13281 };
13282
13283 let wrapped_text = {
13284 let mut wrapped_text = wrap_with_prefix(
13285 line_prefix,
13286 subsequent_lines_prefix,
13287 lines_without_prefixes.join("\n"),
13288 wrap_column,
13289 tab_size,
13290 options.preserve_existing_whitespace,
13291 );
13292
13293 if let Some((indent, delimiter)) = first_line_delimiter {
13294 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
13295 }
13296 if let Some(last_line) = last_line_delimiter {
13297 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
13298 }
13299
13300 wrapped_text
13301 };
13302
13303 // TODO: should always use char-based diff while still supporting cursor behavior that
13304 // matches vim.
13305 let mut diff_options = DiffOptions::default();
13306 if options.override_language_settings {
13307 diff_options.max_word_diff_len = 0;
13308 diff_options.max_word_diff_line_count = 0;
13309 } else {
13310 diff_options.max_word_diff_len = usize::MAX;
13311 diff_options.max_word_diff_line_count = usize::MAX;
13312 }
13313
13314 for (old_range, new_text) in
13315 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
13316 {
13317 let edit_start = buffer.anchor_after(start_offset + old_range.start);
13318 let edit_end = buffer.anchor_after(start_offset + old_range.end);
13319 edits.push((edit_start..edit_end, new_text));
13320 }
13321
13322 rewrapped_row_ranges.push(start_row..=end_row);
13323 }
13324
13325 self.buffer
13326 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13327 }
13328
13329 pub fn cut_common(
13330 &mut self,
13331 cut_no_selection_line: bool,
13332 window: &mut Window,
13333 cx: &mut Context<Self>,
13334 ) -> ClipboardItem {
13335 let mut text = String::new();
13336 let buffer = self.buffer.read(cx).snapshot(cx);
13337 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13338 let mut clipboard_selections = Vec::with_capacity(selections.len());
13339 {
13340 let max_point = buffer.max_point();
13341 let mut is_first = true;
13342 let mut prev_selection_was_entire_line = false;
13343 for selection in &mut selections {
13344 let is_entire_line =
13345 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
13346 if is_entire_line {
13347 selection.start = Point::new(selection.start.row, 0);
13348 if !selection.is_empty() && selection.end.column == 0 {
13349 selection.end = cmp::min(max_point, selection.end);
13350 } else {
13351 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
13352 }
13353 selection.goal = SelectionGoal::None;
13354 }
13355 if is_first {
13356 is_first = false;
13357 } else if !prev_selection_was_entire_line {
13358 text += "\n";
13359 }
13360 prev_selection_was_entire_line = is_entire_line;
13361 let mut len = 0;
13362 for chunk in buffer.text_for_range(selection.start..selection.end) {
13363 text.push_str(chunk);
13364 len += chunk.len();
13365 }
13366
13367 clipboard_selections.push(ClipboardSelection::for_buffer(
13368 len,
13369 is_entire_line,
13370 selection.range(),
13371 &buffer,
13372 self.project.as_ref(),
13373 cx,
13374 ));
13375 }
13376 }
13377
13378 self.transact(window, cx, |this, window, cx| {
13379 this.change_selections(Default::default(), window, cx, |s| {
13380 s.select(selections);
13381 });
13382 this.insert("", window, cx);
13383 });
13384 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
13385 }
13386
13387 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
13388 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13389 let item = self.cut_common(true, window, cx);
13390 cx.write_to_clipboard(item);
13391 }
13392
13393 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
13394 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13395 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13396 s.move_with(|snapshot, sel| {
13397 if sel.is_empty() {
13398 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
13399 }
13400 if sel.is_empty() {
13401 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13402 }
13403 });
13404 });
13405 let item = self.cut_common(false, window, cx);
13406 cx.set_global(KillRing(item))
13407 }
13408
13409 pub fn kill_ring_yank(
13410 &mut self,
13411 _: &KillRingYank,
13412 window: &mut Window,
13413 cx: &mut Context<Self>,
13414 ) {
13415 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13416 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
13417 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
13418 (kill_ring.text().to_string(), kill_ring.metadata_json())
13419 } else {
13420 return;
13421 }
13422 } else {
13423 return;
13424 };
13425 self.do_paste(&text, metadata, false, window, cx);
13426 }
13427
13428 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
13429 self.do_copy(true, cx);
13430 }
13431
13432 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
13433 self.do_copy(false, cx);
13434 }
13435
13436 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
13437 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13438 let buffer = self.buffer.read(cx).read(cx);
13439 let mut text = String::new();
13440
13441 let mut clipboard_selections = Vec::with_capacity(selections.len());
13442 {
13443 let max_point = buffer.max_point();
13444 let mut is_first = true;
13445 let mut prev_selection_was_entire_line = false;
13446 for selection in &selections {
13447 let mut start = selection.start;
13448 let mut end = selection.end;
13449 let is_entire_line = selection.is_empty() || self.selections.line_mode();
13450 let mut add_trailing_newline = false;
13451 if is_entire_line {
13452 start = Point::new(start.row, 0);
13453 let next_line_start = Point::new(end.row + 1, 0);
13454 if next_line_start <= max_point {
13455 end = next_line_start;
13456 } else {
13457 // We're on the last line without a trailing newline.
13458 // Copy to the end of the line and add a newline afterwards.
13459 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
13460 add_trailing_newline = true;
13461 }
13462 }
13463
13464 let mut trimmed_selections = Vec::new();
13465 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
13466 let row = MultiBufferRow(start.row);
13467 let first_indent = buffer.indent_size_for_line(row);
13468 if first_indent.len == 0 || start.column > first_indent.len {
13469 trimmed_selections.push(start..end);
13470 } else {
13471 trimmed_selections.push(
13472 Point::new(row.0, first_indent.len)
13473 ..Point::new(row.0, buffer.line_len(row)),
13474 );
13475 for row in start.row + 1..=end.row {
13476 let mut line_len = buffer.line_len(MultiBufferRow(row));
13477 if row == end.row {
13478 line_len = end.column;
13479 }
13480 if line_len == 0 {
13481 trimmed_selections
13482 .push(Point::new(row, 0)..Point::new(row, line_len));
13483 continue;
13484 }
13485 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
13486 if row_indent_size.len >= first_indent.len {
13487 trimmed_selections.push(
13488 Point::new(row, first_indent.len)..Point::new(row, line_len),
13489 );
13490 } else {
13491 trimmed_selections.clear();
13492 trimmed_selections.push(start..end);
13493 break;
13494 }
13495 }
13496 }
13497 } else {
13498 trimmed_selections.push(start..end);
13499 }
13500
13501 let is_multiline_trim = trimmed_selections.len() > 1;
13502 for trimmed_range in trimmed_selections {
13503 if is_first {
13504 is_first = false;
13505 } else if is_multiline_trim || !prev_selection_was_entire_line {
13506 text += "\n";
13507 }
13508 prev_selection_was_entire_line = is_entire_line && !is_multiline_trim;
13509 let mut len = 0;
13510 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
13511 text.push_str(chunk);
13512 len += chunk.len();
13513 }
13514 if add_trailing_newline {
13515 text.push('\n');
13516 len += 1;
13517 }
13518 clipboard_selections.push(ClipboardSelection::for_buffer(
13519 len,
13520 is_entire_line,
13521 trimmed_range,
13522 &buffer,
13523 self.project.as_ref(),
13524 cx,
13525 ));
13526 }
13527 }
13528 }
13529
13530 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
13531 text,
13532 clipboard_selections,
13533 ));
13534 }
13535
13536 pub fn do_paste(
13537 &mut self,
13538 text: &String,
13539 clipboard_selections: Option<Vec<ClipboardSelection>>,
13540 handle_entire_lines: bool,
13541 window: &mut Window,
13542 cx: &mut Context<Self>,
13543 ) {
13544 if self.read_only(cx) {
13545 return;
13546 }
13547
13548 let clipboard_text = Cow::Borrowed(text.as_str());
13549
13550 self.transact(window, cx, |this, window, cx| {
13551 let had_active_edit_prediction = this.has_active_edit_prediction();
13552 let display_map = this.display_snapshot(cx);
13553 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
13554 let cursor_offset = this
13555 .selections
13556 .last::<MultiBufferOffset>(&display_map)
13557 .head();
13558
13559 if let Some(mut clipboard_selections) = clipboard_selections {
13560 let all_selections_were_entire_line =
13561 clipboard_selections.iter().all(|s| s.is_entire_line);
13562 let first_selection_indent_column =
13563 clipboard_selections.first().map(|s| s.first_line_indent);
13564 if clipboard_selections.len() != old_selections.len() {
13565 clipboard_selections.drain(..);
13566 }
13567 let mut auto_indent_on_paste = true;
13568
13569 this.buffer.update(cx, |buffer, cx| {
13570 let snapshot = buffer.read(cx);
13571 auto_indent_on_paste = snapshot
13572 .language_settings_at(cursor_offset, cx)
13573 .auto_indent_on_paste;
13574
13575 let mut start_offset = 0;
13576 let mut edits = Vec::new();
13577 let mut original_indent_columns = Vec::new();
13578 for (ix, selection) in old_selections.iter().enumerate() {
13579 let to_insert;
13580 let entire_line;
13581 let original_indent_column;
13582 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
13583 let end_offset = start_offset + clipboard_selection.len;
13584 to_insert = &clipboard_text[start_offset..end_offset];
13585 entire_line = clipboard_selection.is_entire_line;
13586 start_offset = if entire_line {
13587 end_offset
13588 } else {
13589 end_offset + 1
13590 };
13591 original_indent_column = Some(clipboard_selection.first_line_indent);
13592 } else {
13593 to_insert = &*clipboard_text;
13594 entire_line = all_selections_were_entire_line;
13595 original_indent_column = first_selection_indent_column
13596 }
13597
13598 let (range, to_insert) =
13599 if selection.is_empty() && handle_entire_lines && entire_line {
13600 // If the corresponding selection was empty when this slice of the
13601 // clipboard text was written, then the entire line containing the
13602 // selection was copied. If this selection is also currently empty,
13603 // then paste the line before the current line of the buffer.
13604 let column = selection.start.to_point(&snapshot).column as usize;
13605 let line_start = selection.start - column;
13606 (line_start..line_start, Cow::Borrowed(to_insert))
13607 } else {
13608 let language = snapshot.language_at(selection.head());
13609 let range = selection.range();
13610 if let Some(language) = language
13611 && language.name() == "Markdown".into()
13612 {
13613 edit_for_markdown_paste(
13614 &snapshot,
13615 range,
13616 to_insert,
13617 url::Url::parse(to_insert).ok(),
13618 )
13619 } else {
13620 (range, Cow::Borrowed(to_insert))
13621 }
13622 };
13623
13624 edits.push((range, to_insert));
13625 original_indent_columns.push(original_indent_column);
13626 }
13627 drop(snapshot);
13628
13629 buffer.edit(
13630 edits,
13631 if auto_indent_on_paste {
13632 Some(AutoindentMode::Block {
13633 original_indent_columns,
13634 })
13635 } else {
13636 None
13637 },
13638 cx,
13639 );
13640 });
13641
13642 let selections = this
13643 .selections
13644 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13645 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
13646 } else {
13647 let url = url::Url::parse(&clipboard_text).ok();
13648
13649 let auto_indent_mode = if !clipboard_text.is_empty() {
13650 Some(AutoindentMode::Block {
13651 original_indent_columns: Vec::new(),
13652 })
13653 } else {
13654 None
13655 };
13656
13657 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
13658 let snapshot = buffer.snapshot(cx);
13659
13660 let anchors = old_selections
13661 .iter()
13662 .map(|s| {
13663 let anchor = snapshot.anchor_after(s.head());
13664 s.map(|_| anchor)
13665 })
13666 .collect::<Vec<_>>();
13667
13668 let mut edits = Vec::new();
13669
13670 for selection in old_selections.iter() {
13671 let language = snapshot.language_at(selection.head());
13672 let range = selection.range();
13673
13674 let (edit_range, edit_text) = if let Some(language) = language
13675 && language.name() == "Markdown".into()
13676 {
13677 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
13678 } else {
13679 (range, clipboard_text.clone())
13680 };
13681
13682 edits.push((edit_range, edit_text));
13683 }
13684
13685 drop(snapshot);
13686 buffer.edit(edits, auto_indent_mode, cx);
13687
13688 anchors
13689 });
13690
13691 this.change_selections(Default::default(), window, cx, |s| {
13692 s.select_anchors(selection_anchors);
13693 });
13694 }
13695
13696 // 🤔 | .. | show_in_menu |
13697 // | .. | true true
13698 // | had_edit_prediction | false true
13699
13700 let trigger_in_words =
13701 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
13702
13703 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
13704 });
13705 }
13706
13707 pub fn diff_clipboard_with_selection(
13708 &mut self,
13709 _: &DiffClipboardWithSelection,
13710 window: &mut Window,
13711 cx: &mut Context<Self>,
13712 ) {
13713 let selections = self
13714 .selections
13715 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
13716
13717 if selections.is_empty() {
13718 log::warn!("There should always be at least one selection in Zed. This is a bug.");
13719 return;
13720 };
13721
13722 let clipboard_text = match cx.read_from_clipboard() {
13723 Some(item) => match item.entries().first() {
13724 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
13725 _ => None,
13726 },
13727 None => None,
13728 };
13729
13730 let Some(clipboard_text) = clipboard_text else {
13731 log::warn!("Clipboard doesn't contain text.");
13732 return;
13733 };
13734
13735 window.dispatch_action(
13736 Box::new(DiffClipboardWithSelectionData {
13737 clipboard_text,
13738 editor: cx.entity(),
13739 }),
13740 cx,
13741 );
13742 }
13743
13744 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
13745 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13746 if let Some(item) = cx.read_from_clipboard() {
13747 let entries = item.entries();
13748
13749 match entries.first() {
13750 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
13751 // of all the pasted entries.
13752 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
13753 .do_paste(
13754 clipboard_string.text(),
13755 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
13756 true,
13757 window,
13758 cx,
13759 ),
13760 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
13761 }
13762 }
13763 }
13764
13765 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
13766 if self.read_only(cx) {
13767 return;
13768 }
13769
13770 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13771
13772 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
13773 if let Some((selections, _)) =
13774 self.selection_history.transaction(transaction_id).cloned()
13775 {
13776 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13777 s.select_anchors(selections.to_vec());
13778 });
13779 } else {
13780 log::error!(
13781 "No entry in selection_history found for undo. \
13782 This may correspond to a bug where undo does not update the selection. \
13783 If this is occurring, please add details to \
13784 https://github.com/zed-industries/zed/issues/22692"
13785 );
13786 }
13787 self.request_autoscroll(Autoscroll::fit(), cx);
13788 self.unmark_text(window, cx);
13789 self.refresh_edit_prediction(true, false, window, cx);
13790 cx.emit(EditorEvent::Edited { transaction_id });
13791 cx.emit(EditorEvent::TransactionUndone { transaction_id });
13792 }
13793 }
13794
13795 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
13796 if self.read_only(cx) {
13797 return;
13798 }
13799
13800 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13801
13802 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
13803 if let Some((_, Some(selections))) =
13804 self.selection_history.transaction(transaction_id).cloned()
13805 {
13806 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13807 s.select_anchors(selections.to_vec());
13808 });
13809 } else {
13810 log::error!(
13811 "No entry in selection_history found for redo. \
13812 This may correspond to a bug where undo does not update the selection. \
13813 If this is occurring, please add details to \
13814 https://github.com/zed-industries/zed/issues/22692"
13815 );
13816 }
13817 self.request_autoscroll(Autoscroll::fit(), cx);
13818 self.unmark_text(window, cx);
13819 self.refresh_edit_prediction(true, false, window, cx);
13820 cx.emit(EditorEvent::Edited { transaction_id });
13821 }
13822 }
13823
13824 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13825 self.buffer
13826 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13827 }
13828
13829 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13830 self.buffer
13831 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13832 }
13833
13834 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13835 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13836 self.change_selections(Default::default(), window, cx, |s| {
13837 s.move_with(|map, selection| {
13838 let cursor = if selection.is_empty() {
13839 movement::left(map, selection.start)
13840 } else {
13841 selection.start
13842 };
13843 selection.collapse_to(cursor, SelectionGoal::None);
13844 });
13845 })
13846 }
13847
13848 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13849 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13850 self.change_selections(Default::default(), window, cx, |s| {
13851 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
13852 })
13853 }
13854
13855 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13856 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13857 self.change_selections(Default::default(), window, cx, |s| {
13858 s.move_with(|map, selection| {
13859 let cursor = if selection.is_empty() {
13860 movement::right(map, selection.end)
13861 } else {
13862 selection.end
13863 };
13864 selection.collapse_to(cursor, SelectionGoal::None)
13865 });
13866 })
13867 }
13868
13869 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13870 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13871 self.change_selections(Default::default(), window, cx, |s| {
13872 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13873 });
13874 }
13875
13876 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13877 if self.take_rename(true, window, cx).is_some() {
13878 return;
13879 }
13880
13881 if self.mode.is_single_line() {
13882 cx.propagate();
13883 return;
13884 }
13885
13886 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13887
13888 let text_layout_details = &self.text_layout_details(window, cx);
13889 let selection_count = self.selections.count();
13890 let first_selection = self.selections.first_anchor();
13891
13892 self.change_selections(Default::default(), window, cx, |s| {
13893 s.move_with(|map, selection| {
13894 if !selection.is_empty() {
13895 selection.goal = SelectionGoal::None;
13896 }
13897 let (cursor, goal) = movement::up(
13898 map,
13899 selection.start,
13900 selection.goal,
13901 false,
13902 text_layout_details,
13903 );
13904 selection.collapse_to(cursor, goal);
13905 });
13906 });
13907
13908 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13909 {
13910 cx.propagate();
13911 }
13912 }
13913
13914 pub fn move_up_by_lines(
13915 &mut self,
13916 action: &MoveUpByLines,
13917 window: &mut Window,
13918 cx: &mut Context<Self>,
13919 ) {
13920 if self.take_rename(true, window, cx).is_some() {
13921 return;
13922 }
13923
13924 if self.mode.is_single_line() {
13925 cx.propagate();
13926 return;
13927 }
13928
13929 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13930
13931 let text_layout_details = &self.text_layout_details(window, cx);
13932
13933 self.change_selections(Default::default(), window, cx, |s| {
13934 s.move_with(|map, selection| {
13935 if !selection.is_empty() {
13936 selection.goal = SelectionGoal::None;
13937 }
13938 let (cursor, goal) = movement::up_by_rows(
13939 map,
13940 selection.start,
13941 action.lines,
13942 selection.goal,
13943 false,
13944 text_layout_details,
13945 );
13946 selection.collapse_to(cursor, goal);
13947 });
13948 })
13949 }
13950
13951 pub fn move_down_by_lines(
13952 &mut self,
13953 action: &MoveDownByLines,
13954 window: &mut Window,
13955 cx: &mut Context<Self>,
13956 ) {
13957 if self.take_rename(true, window, cx).is_some() {
13958 return;
13959 }
13960
13961 if self.mode.is_single_line() {
13962 cx.propagate();
13963 return;
13964 }
13965
13966 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13967
13968 let text_layout_details = &self.text_layout_details(window, cx);
13969
13970 self.change_selections(Default::default(), window, cx, |s| {
13971 s.move_with(|map, selection| {
13972 if !selection.is_empty() {
13973 selection.goal = SelectionGoal::None;
13974 }
13975 let (cursor, goal) = movement::down_by_rows(
13976 map,
13977 selection.start,
13978 action.lines,
13979 selection.goal,
13980 false,
13981 text_layout_details,
13982 );
13983 selection.collapse_to(cursor, goal);
13984 });
13985 })
13986 }
13987
13988 pub fn select_down_by_lines(
13989 &mut self,
13990 action: &SelectDownByLines,
13991 window: &mut Window,
13992 cx: &mut Context<Self>,
13993 ) {
13994 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13995 let text_layout_details = &self.text_layout_details(window, cx);
13996 self.change_selections(Default::default(), window, cx, |s| {
13997 s.move_heads_with(|map, head, goal| {
13998 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13999 })
14000 })
14001 }
14002
14003 pub fn select_up_by_lines(
14004 &mut self,
14005 action: &SelectUpByLines,
14006 window: &mut Window,
14007 cx: &mut Context<Self>,
14008 ) {
14009 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14010 let text_layout_details = &self.text_layout_details(window, cx);
14011 self.change_selections(Default::default(), window, cx, |s| {
14012 s.move_heads_with(|map, head, goal| {
14013 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
14014 })
14015 })
14016 }
14017
14018 pub fn select_page_up(
14019 &mut self,
14020 _: &SelectPageUp,
14021 window: &mut Window,
14022 cx: &mut Context<Self>,
14023 ) {
14024 let Some(row_count) = self.visible_row_count() else {
14025 return;
14026 };
14027
14028 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14029
14030 let text_layout_details = &self.text_layout_details(window, cx);
14031
14032 self.change_selections(Default::default(), window, cx, |s| {
14033 s.move_heads_with(|map, head, goal| {
14034 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
14035 })
14036 })
14037 }
14038
14039 pub fn move_page_up(
14040 &mut self,
14041 action: &MovePageUp,
14042 window: &mut Window,
14043 cx: &mut Context<Self>,
14044 ) {
14045 if self.take_rename(true, window, cx).is_some() {
14046 return;
14047 }
14048
14049 if self
14050 .context_menu
14051 .borrow_mut()
14052 .as_mut()
14053 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
14054 .unwrap_or(false)
14055 {
14056 return;
14057 }
14058
14059 if matches!(self.mode, EditorMode::SingleLine) {
14060 cx.propagate();
14061 return;
14062 }
14063
14064 let Some(row_count) = self.visible_row_count() else {
14065 return;
14066 };
14067
14068 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14069
14070 let effects = if action.center_cursor {
14071 SelectionEffects::scroll(Autoscroll::center())
14072 } else {
14073 SelectionEffects::default()
14074 };
14075
14076 let text_layout_details = &self.text_layout_details(window, cx);
14077
14078 self.change_selections(effects, window, cx, |s| {
14079 s.move_with(|map, selection| {
14080 if !selection.is_empty() {
14081 selection.goal = SelectionGoal::None;
14082 }
14083 let (cursor, goal) = movement::up_by_rows(
14084 map,
14085 selection.end,
14086 row_count,
14087 selection.goal,
14088 false,
14089 text_layout_details,
14090 );
14091 selection.collapse_to(cursor, goal);
14092 });
14093 });
14094 }
14095
14096 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
14097 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14098 let text_layout_details = &self.text_layout_details(window, cx);
14099 self.change_selections(Default::default(), window, cx, |s| {
14100 s.move_heads_with(|map, head, goal| {
14101 movement::up(map, head, goal, false, text_layout_details)
14102 })
14103 })
14104 }
14105
14106 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
14107 self.take_rename(true, window, cx);
14108
14109 if self.mode.is_single_line() {
14110 cx.propagate();
14111 return;
14112 }
14113
14114 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14115
14116 let text_layout_details = &self.text_layout_details(window, cx);
14117 let selection_count = self.selections.count();
14118 let first_selection = self.selections.first_anchor();
14119
14120 self.change_selections(Default::default(), window, cx, |s| {
14121 s.move_with(|map, selection| {
14122 if !selection.is_empty() {
14123 selection.goal = SelectionGoal::None;
14124 }
14125 let (cursor, goal) = movement::down(
14126 map,
14127 selection.end,
14128 selection.goal,
14129 false,
14130 text_layout_details,
14131 );
14132 selection.collapse_to(cursor, goal);
14133 });
14134 });
14135
14136 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14137 {
14138 cx.propagate();
14139 }
14140 }
14141
14142 pub fn select_page_down(
14143 &mut self,
14144 _: &SelectPageDown,
14145 window: &mut Window,
14146 cx: &mut Context<Self>,
14147 ) {
14148 let Some(row_count) = self.visible_row_count() else {
14149 return;
14150 };
14151
14152 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14153
14154 let text_layout_details = &self.text_layout_details(window, cx);
14155
14156 self.change_selections(Default::default(), window, cx, |s| {
14157 s.move_heads_with(|map, head, goal| {
14158 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
14159 })
14160 })
14161 }
14162
14163 pub fn move_page_down(
14164 &mut self,
14165 action: &MovePageDown,
14166 window: &mut Window,
14167 cx: &mut Context<Self>,
14168 ) {
14169 if self.take_rename(true, window, cx).is_some() {
14170 return;
14171 }
14172
14173 if self
14174 .context_menu
14175 .borrow_mut()
14176 .as_mut()
14177 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
14178 .unwrap_or(false)
14179 {
14180 return;
14181 }
14182
14183 if matches!(self.mode, EditorMode::SingleLine) {
14184 cx.propagate();
14185 return;
14186 }
14187
14188 let Some(row_count) = self.visible_row_count() else {
14189 return;
14190 };
14191
14192 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14193
14194 let effects = if action.center_cursor {
14195 SelectionEffects::scroll(Autoscroll::center())
14196 } else {
14197 SelectionEffects::default()
14198 };
14199
14200 let text_layout_details = &self.text_layout_details(window, cx);
14201 self.change_selections(effects, window, cx, |s| {
14202 s.move_with(|map, selection| {
14203 if !selection.is_empty() {
14204 selection.goal = SelectionGoal::None;
14205 }
14206 let (cursor, goal) = movement::down_by_rows(
14207 map,
14208 selection.end,
14209 row_count,
14210 selection.goal,
14211 false,
14212 text_layout_details,
14213 );
14214 selection.collapse_to(cursor, goal);
14215 });
14216 });
14217 }
14218
14219 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
14220 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14221 let text_layout_details = &self.text_layout_details(window, cx);
14222 self.change_selections(Default::default(), window, cx, |s| {
14223 s.move_heads_with(|map, head, goal| {
14224 movement::down(map, head, goal, false, text_layout_details)
14225 })
14226 });
14227 }
14228
14229 pub fn context_menu_first(
14230 &mut self,
14231 _: &ContextMenuFirst,
14232 window: &mut Window,
14233 cx: &mut Context<Self>,
14234 ) {
14235 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14236 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
14237 }
14238 }
14239
14240 pub fn context_menu_prev(
14241 &mut self,
14242 _: &ContextMenuPrevious,
14243 window: &mut Window,
14244 cx: &mut Context<Self>,
14245 ) {
14246 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14247 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
14248 }
14249 }
14250
14251 pub fn context_menu_next(
14252 &mut self,
14253 _: &ContextMenuNext,
14254 window: &mut Window,
14255 cx: &mut Context<Self>,
14256 ) {
14257 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14258 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
14259 }
14260 }
14261
14262 pub fn context_menu_last(
14263 &mut self,
14264 _: &ContextMenuLast,
14265 window: &mut Window,
14266 cx: &mut Context<Self>,
14267 ) {
14268 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14269 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
14270 }
14271 }
14272
14273 pub fn signature_help_prev(
14274 &mut self,
14275 _: &SignatureHelpPrevious,
14276 _: &mut Window,
14277 cx: &mut Context<Self>,
14278 ) {
14279 if let Some(popover) = self.signature_help_state.popover_mut() {
14280 if popover.current_signature == 0 {
14281 popover.current_signature = popover.signatures.len() - 1;
14282 } else {
14283 popover.current_signature -= 1;
14284 }
14285 cx.notify();
14286 }
14287 }
14288
14289 pub fn signature_help_next(
14290 &mut self,
14291 _: &SignatureHelpNext,
14292 _: &mut Window,
14293 cx: &mut Context<Self>,
14294 ) {
14295 if let Some(popover) = self.signature_help_state.popover_mut() {
14296 if popover.current_signature + 1 == popover.signatures.len() {
14297 popover.current_signature = 0;
14298 } else {
14299 popover.current_signature += 1;
14300 }
14301 cx.notify();
14302 }
14303 }
14304
14305 pub fn move_to_previous_word_start(
14306 &mut self,
14307 _: &MoveToPreviousWordStart,
14308 window: &mut Window,
14309 cx: &mut Context<Self>,
14310 ) {
14311 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14312 self.change_selections(Default::default(), window, cx, |s| {
14313 s.move_cursors_with(|map, head, _| {
14314 (
14315 movement::previous_word_start(map, head),
14316 SelectionGoal::None,
14317 )
14318 });
14319 })
14320 }
14321
14322 pub fn move_to_previous_subword_start(
14323 &mut self,
14324 _: &MoveToPreviousSubwordStart,
14325 window: &mut Window,
14326 cx: &mut Context<Self>,
14327 ) {
14328 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14329 self.change_selections(Default::default(), window, cx, |s| {
14330 s.move_cursors_with(|map, head, _| {
14331 (
14332 movement::previous_subword_start(map, head),
14333 SelectionGoal::None,
14334 )
14335 });
14336 })
14337 }
14338
14339 pub fn select_to_previous_word_start(
14340 &mut self,
14341 _: &SelectToPreviousWordStart,
14342 window: &mut Window,
14343 cx: &mut Context<Self>,
14344 ) {
14345 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14346 self.change_selections(Default::default(), window, cx, |s| {
14347 s.move_heads_with(|map, head, _| {
14348 (
14349 movement::previous_word_start(map, head),
14350 SelectionGoal::None,
14351 )
14352 });
14353 })
14354 }
14355
14356 pub fn select_to_previous_subword_start(
14357 &mut self,
14358 _: &SelectToPreviousSubwordStart,
14359 window: &mut Window,
14360 cx: &mut Context<Self>,
14361 ) {
14362 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14363 self.change_selections(Default::default(), window, cx, |s| {
14364 s.move_heads_with(|map, head, _| {
14365 (
14366 movement::previous_subword_start(map, head),
14367 SelectionGoal::None,
14368 )
14369 });
14370 })
14371 }
14372
14373 pub fn delete_to_previous_word_start(
14374 &mut self,
14375 action: &DeleteToPreviousWordStart,
14376 window: &mut Window,
14377 cx: &mut Context<Self>,
14378 ) {
14379 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14380 self.transact(window, cx, |this, window, cx| {
14381 this.select_autoclose_pair(window, cx);
14382 this.change_selections(Default::default(), window, cx, |s| {
14383 s.move_with(|map, selection| {
14384 if selection.is_empty() {
14385 let mut cursor = if action.ignore_newlines {
14386 movement::previous_word_start(map, selection.head())
14387 } else {
14388 movement::previous_word_start_or_newline(map, selection.head())
14389 };
14390 cursor = movement::adjust_greedy_deletion(
14391 map,
14392 selection.head(),
14393 cursor,
14394 action.ignore_brackets,
14395 );
14396 selection.set_head(cursor, SelectionGoal::None);
14397 }
14398 });
14399 });
14400 this.insert("", window, cx);
14401 });
14402 }
14403
14404 pub fn delete_to_previous_subword_start(
14405 &mut self,
14406 action: &DeleteToPreviousSubwordStart,
14407 window: &mut Window,
14408 cx: &mut Context<Self>,
14409 ) {
14410 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14411 self.transact(window, cx, |this, window, cx| {
14412 this.select_autoclose_pair(window, cx);
14413 this.change_selections(Default::default(), window, cx, |s| {
14414 s.move_with(|map, selection| {
14415 if selection.is_empty() {
14416 let mut cursor = if action.ignore_newlines {
14417 movement::previous_subword_start(map, selection.head())
14418 } else {
14419 movement::previous_subword_start_or_newline(map, selection.head())
14420 };
14421 cursor = movement::adjust_greedy_deletion(
14422 map,
14423 selection.head(),
14424 cursor,
14425 action.ignore_brackets,
14426 );
14427 selection.set_head(cursor, SelectionGoal::None);
14428 }
14429 });
14430 });
14431 this.insert("", window, cx);
14432 });
14433 }
14434
14435 pub fn move_to_next_word_end(
14436 &mut self,
14437 _: &MoveToNextWordEnd,
14438 window: &mut Window,
14439 cx: &mut Context<Self>,
14440 ) {
14441 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14442 self.change_selections(Default::default(), window, cx, |s| {
14443 s.move_cursors_with(|map, head, _| {
14444 (movement::next_word_end(map, head), SelectionGoal::None)
14445 });
14446 })
14447 }
14448
14449 pub fn move_to_next_subword_end(
14450 &mut self,
14451 _: &MoveToNextSubwordEnd,
14452 window: &mut Window,
14453 cx: &mut Context<Self>,
14454 ) {
14455 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14456 self.change_selections(Default::default(), window, cx, |s| {
14457 s.move_cursors_with(|map, head, _| {
14458 (movement::next_subword_end(map, head), SelectionGoal::None)
14459 });
14460 })
14461 }
14462
14463 pub fn select_to_next_word_end(
14464 &mut self,
14465 _: &SelectToNextWordEnd,
14466 window: &mut Window,
14467 cx: &mut Context<Self>,
14468 ) {
14469 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14470 self.change_selections(Default::default(), window, cx, |s| {
14471 s.move_heads_with(|map, head, _| {
14472 (movement::next_word_end(map, head), SelectionGoal::None)
14473 });
14474 })
14475 }
14476
14477 pub fn select_to_next_subword_end(
14478 &mut self,
14479 _: &SelectToNextSubwordEnd,
14480 window: &mut Window,
14481 cx: &mut Context<Self>,
14482 ) {
14483 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14484 self.change_selections(Default::default(), window, cx, |s| {
14485 s.move_heads_with(|map, head, _| {
14486 (movement::next_subword_end(map, head), SelectionGoal::None)
14487 });
14488 })
14489 }
14490
14491 pub fn delete_to_next_word_end(
14492 &mut self,
14493 action: &DeleteToNextWordEnd,
14494 window: &mut Window,
14495 cx: &mut Context<Self>,
14496 ) {
14497 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14498 self.transact(window, cx, |this, window, cx| {
14499 this.change_selections(Default::default(), window, cx, |s| {
14500 s.move_with(|map, selection| {
14501 if selection.is_empty() {
14502 let mut cursor = if action.ignore_newlines {
14503 movement::next_word_end(map, selection.head())
14504 } else {
14505 movement::next_word_end_or_newline(map, selection.head())
14506 };
14507 cursor = movement::adjust_greedy_deletion(
14508 map,
14509 selection.head(),
14510 cursor,
14511 action.ignore_brackets,
14512 );
14513 selection.set_head(cursor, SelectionGoal::None);
14514 }
14515 });
14516 });
14517 this.insert("", window, cx);
14518 });
14519 }
14520
14521 pub fn delete_to_next_subword_end(
14522 &mut self,
14523 action: &DeleteToNextSubwordEnd,
14524 window: &mut Window,
14525 cx: &mut Context<Self>,
14526 ) {
14527 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14528 self.transact(window, cx, |this, window, cx| {
14529 this.change_selections(Default::default(), window, cx, |s| {
14530 s.move_with(|map, selection| {
14531 if selection.is_empty() {
14532 let mut cursor = if action.ignore_newlines {
14533 movement::next_subword_end(map, selection.head())
14534 } else {
14535 movement::next_subword_end_or_newline(map, selection.head())
14536 };
14537 cursor = movement::adjust_greedy_deletion(
14538 map,
14539 selection.head(),
14540 cursor,
14541 action.ignore_brackets,
14542 );
14543 selection.set_head(cursor, SelectionGoal::None);
14544 }
14545 });
14546 });
14547 this.insert("", window, cx);
14548 });
14549 }
14550
14551 pub fn move_to_beginning_of_line(
14552 &mut self,
14553 action: &MoveToBeginningOfLine,
14554 window: &mut Window,
14555 cx: &mut Context<Self>,
14556 ) {
14557 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14558 self.change_selections(Default::default(), window, cx, |s| {
14559 s.move_cursors_with(|map, head, _| {
14560 (
14561 movement::indented_line_beginning(
14562 map,
14563 head,
14564 action.stop_at_soft_wraps,
14565 action.stop_at_indent,
14566 ),
14567 SelectionGoal::None,
14568 )
14569 });
14570 })
14571 }
14572
14573 pub fn select_to_beginning_of_line(
14574 &mut self,
14575 action: &SelectToBeginningOfLine,
14576 window: &mut Window,
14577 cx: &mut Context<Self>,
14578 ) {
14579 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14580 self.change_selections(Default::default(), window, cx, |s| {
14581 s.move_heads_with(|map, head, _| {
14582 (
14583 movement::indented_line_beginning(
14584 map,
14585 head,
14586 action.stop_at_soft_wraps,
14587 action.stop_at_indent,
14588 ),
14589 SelectionGoal::None,
14590 )
14591 });
14592 });
14593 }
14594
14595 pub fn delete_to_beginning_of_line(
14596 &mut self,
14597 action: &DeleteToBeginningOfLine,
14598 window: &mut Window,
14599 cx: &mut Context<Self>,
14600 ) {
14601 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14602 self.transact(window, cx, |this, window, cx| {
14603 this.change_selections(Default::default(), window, cx, |s| {
14604 s.move_with(|_, selection| {
14605 selection.reversed = true;
14606 });
14607 });
14608
14609 this.select_to_beginning_of_line(
14610 &SelectToBeginningOfLine {
14611 stop_at_soft_wraps: false,
14612 stop_at_indent: action.stop_at_indent,
14613 },
14614 window,
14615 cx,
14616 );
14617 this.backspace(&Backspace, window, cx);
14618 });
14619 }
14620
14621 pub fn move_to_end_of_line(
14622 &mut self,
14623 action: &MoveToEndOfLine,
14624 window: &mut Window,
14625 cx: &mut Context<Self>,
14626 ) {
14627 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14628 self.change_selections(Default::default(), window, cx, |s| {
14629 s.move_cursors_with(|map, head, _| {
14630 (
14631 movement::line_end(map, head, action.stop_at_soft_wraps),
14632 SelectionGoal::None,
14633 )
14634 });
14635 })
14636 }
14637
14638 pub fn select_to_end_of_line(
14639 &mut self,
14640 action: &SelectToEndOfLine,
14641 window: &mut Window,
14642 cx: &mut Context<Self>,
14643 ) {
14644 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14645 self.change_selections(Default::default(), window, cx, |s| {
14646 s.move_heads_with(|map, head, _| {
14647 (
14648 movement::line_end(map, head, action.stop_at_soft_wraps),
14649 SelectionGoal::None,
14650 )
14651 });
14652 })
14653 }
14654
14655 pub fn delete_to_end_of_line(
14656 &mut self,
14657 _: &DeleteToEndOfLine,
14658 window: &mut Window,
14659 cx: &mut Context<Self>,
14660 ) {
14661 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14662 self.transact(window, cx, |this, window, cx| {
14663 this.select_to_end_of_line(
14664 &SelectToEndOfLine {
14665 stop_at_soft_wraps: false,
14666 },
14667 window,
14668 cx,
14669 );
14670 this.delete(&Delete, window, cx);
14671 });
14672 }
14673
14674 pub fn cut_to_end_of_line(
14675 &mut self,
14676 action: &CutToEndOfLine,
14677 window: &mut Window,
14678 cx: &mut Context<Self>,
14679 ) {
14680 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14681 self.transact(window, cx, |this, window, cx| {
14682 this.select_to_end_of_line(
14683 &SelectToEndOfLine {
14684 stop_at_soft_wraps: false,
14685 },
14686 window,
14687 cx,
14688 );
14689 if !action.stop_at_newlines {
14690 this.change_selections(Default::default(), window, cx, |s| {
14691 s.move_with(|_, sel| {
14692 if sel.is_empty() {
14693 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
14694 }
14695 });
14696 });
14697 }
14698 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14699 let item = this.cut_common(false, window, cx);
14700 cx.write_to_clipboard(item);
14701 });
14702 }
14703
14704 pub fn move_to_start_of_paragraph(
14705 &mut self,
14706 _: &MoveToStartOfParagraph,
14707 window: &mut Window,
14708 cx: &mut Context<Self>,
14709 ) {
14710 if matches!(self.mode, EditorMode::SingleLine) {
14711 cx.propagate();
14712 return;
14713 }
14714 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14715 self.change_selections(Default::default(), window, cx, |s| {
14716 s.move_with(|map, selection| {
14717 selection.collapse_to(
14718 movement::start_of_paragraph(map, selection.head(), 1),
14719 SelectionGoal::None,
14720 )
14721 });
14722 })
14723 }
14724
14725 pub fn move_to_end_of_paragraph(
14726 &mut self,
14727 _: &MoveToEndOfParagraph,
14728 window: &mut Window,
14729 cx: &mut Context<Self>,
14730 ) {
14731 if matches!(self.mode, EditorMode::SingleLine) {
14732 cx.propagate();
14733 return;
14734 }
14735 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14736 self.change_selections(Default::default(), window, cx, |s| {
14737 s.move_with(|map, selection| {
14738 selection.collapse_to(
14739 movement::end_of_paragraph(map, selection.head(), 1),
14740 SelectionGoal::None,
14741 )
14742 });
14743 })
14744 }
14745
14746 pub fn select_to_start_of_paragraph(
14747 &mut self,
14748 _: &SelectToStartOfParagraph,
14749 window: &mut Window,
14750 cx: &mut Context<Self>,
14751 ) {
14752 if matches!(self.mode, EditorMode::SingleLine) {
14753 cx.propagate();
14754 return;
14755 }
14756 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14757 self.change_selections(Default::default(), window, cx, |s| {
14758 s.move_heads_with(|map, head, _| {
14759 (
14760 movement::start_of_paragraph(map, head, 1),
14761 SelectionGoal::None,
14762 )
14763 });
14764 })
14765 }
14766
14767 pub fn select_to_end_of_paragraph(
14768 &mut self,
14769 _: &SelectToEndOfParagraph,
14770 window: &mut Window,
14771 cx: &mut Context<Self>,
14772 ) {
14773 if matches!(self.mode, EditorMode::SingleLine) {
14774 cx.propagate();
14775 return;
14776 }
14777 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14778 self.change_selections(Default::default(), window, cx, |s| {
14779 s.move_heads_with(|map, head, _| {
14780 (
14781 movement::end_of_paragraph(map, head, 1),
14782 SelectionGoal::None,
14783 )
14784 });
14785 })
14786 }
14787
14788 pub fn move_to_start_of_excerpt(
14789 &mut self,
14790 _: &MoveToStartOfExcerpt,
14791 window: &mut Window,
14792 cx: &mut Context<Self>,
14793 ) {
14794 if matches!(self.mode, EditorMode::SingleLine) {
14795 cx.propagate();
14796 return;
14797 }
14798 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14799 self.change_selections(Default::default(), window, cx, |s| {
14800 s.move_with(|map, selection| {
14801 selection.collapse_to(
14802 movement::start_of_excerpt(
14803 map,
14804 selection.head(),
14805 workspace::searchable::Direction::Prev,
14806 ),
14807 SelectionGoal::None,
14808 )
14809 });
14810 })
14811 }
14812
14813 pub fn move_to_start_of_next_excerpt(
14814 &mut self,
14815 _: &MoveToStartOfNextExcerpt,
14816 window: &mut Window,
14817 cx: &mut Context<Self>,
14818 ) {
14819 if matches!(self.mode, EditorMode::SingleLine) {
14820 cx.propagate();
14821 return;
14822 }
14823
14824 self.change_selections(Default::default(), window, cx, |s| {
14825 s.move_with(|map, selection| {
14826 selection.collapse_to(
14827 movement::start_of_excerpt(
14828 map,
14829 selection.head(),
14830 workspace::searchable::Direction::Next,
14831 ),
14832 SelectionGoal::None,
14833 )
14834 });
14835 })
14836 }
14837
14838 pub fn move_to_end_of_excerpt(
14839 &mut self,
14840 _: &MoveToEndOfExcerpt,
14841 window: &mut Window,
14842 cx: &mut Context<Self>,
14843 ) {
14844 if matches!(self.mode, EditorMode::SingleLine) {
14845 cx.propagate();
14846 return;
14847 }
14848 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14849 self.change_selections(Default::default(), window, cx, |s| {
14850 s.move_with(|map, selection| {
14851 selection.collapse_to(
14852 movement::end_of_excerpt(
14853 map,
14854 selection.head(),
14855 workspace::searchable::Direction::Next,
14856 ),
14857 SelectionGoal::None,
14858 )
14859 });
14860 })
14861 }
14862
14863 pub fn move_to_end_of_previous_excerpt(
14864 &mut self,
14865 _: &MoveToEndOfPreviousExcerpt,
14866 window: &mut Window,
14867 cx: &mut Context<Self>,
14868 ) {
14869 if matches!(self.mode, EditorMode::SingleLine) {
14870 cx.propagate();
14871 return;
14872 }
14873 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14874 self.change_selections(Default::default(), window, cx, |s| {
14875 s.move_with(|map, selection| {
14876 selection.collapse_to(
14877 movement::end_of_excerpt(
14878 map,
14879 selection.head(),
14880 workspace::searchable::Direction::Prev,
14881 ),
14882 SelectionGoal::None,
14883 )
14884 });
14885 })
14886 }
14887
14888 pub fn select_to_start_of_excerpt(
14889 &mut self,
14890 _: &SelectToStartOfExcerpt,
14891 window: &mut Window,
14892 cx: &mut Context<Self>,
14893 ) {
14894 if matches!(self.mode, EditorMode::SingleLine) {
14895 cx.propagate();
14896 return;
14897 }
14898 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14899 self.change_selections(Default::default(), window, cx, |s| {
14900 s.move_heads_with(|map, head, _| {
14901 (
14902 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14903 SelectionGoal::None,
14904 )
14905 });
14906 })
14907 }
14908
14909 pub fn select_to_start_of_next_excerpt(
14910 &mut self,
14911 _: &SelectToStartOfNextExcerpt,
14912 window: &mut Window,
14913 cx: &mut Context<Self>,
14914 ) {
14915 if matches!(self.mode, EditorMode::SingleLine) {
14916 cx.propagate();
14917 return;
14918 }
14919 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14920 self.change_selections(Default::default(), window, cx, |s| {
14921 s.move_heads_with(|map, head, _| {
14922 (
14923 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14924 SelectionGoal::None,
14925 )
14926 });
14927 })
14928 }
14929
14930 pub fn select_to_end_of_excerpt(
14931 &mut self,
14932 _: &SelectToEndOfExcerpt,
14933 window: &mut Window,
14934 cx: &mut Context<Self>,
14935 ) {
14936 if matches!(self.mode, EditorMode::SingleLine) {
14937 cx.propagate();
14938 return;
14939 }
14940 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14941 self.change_selections(Default::default(), window, cx, |s| {
14942 s.move_heads_with(|map, head, _| {
14943 (
14944 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14945 SelectionGoal::None,
14946 )
14947 });
14948 })
14949 }
14950
14951 pub fn select_to_end_of_previous_excerpt(
14952 &mut self,
14953 _: &SelectToEndOfPreviousExcerpt,
14954 window: &mut Window,
14955 cx: &mut Context<Self>,
14956 ) {
14957 if matches!(self.mode, EditorMode::SingleLine) {
14958 cx.propagate();
14959 return;
14960 }
14961 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14962 self.change_selections(Default::default(), window, cx, |s| {
14963 s.move_heads_with(|map, head, _| {
14964 (
14965 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14966 SelectionGoal::None,
14967 )
14968 });
14969 })
14970 }
14971
14972 pub fn move_to_beginning(
14973 &mut self,
14974 _: &MoveToBeginning,
14975 window: &mut Window,
14976 cx: &mut Context<Self>,
14977 ) {
14978 if matches!(self.mode, EditorMode::SingleLine) {
14979 cx.propagate();
14980 return;
14981 }
14982 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14983 self.change_selections(Default::default(), window, cx, |s| {
14984 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
14985 });
14986 }
14987
14988 pub fn select_to_beginning(
14989 &mut self,
14990 _: &SelectToBeginning,
14991 window: &mut Window,
14992 cx: &mut Context<Self>,
14993 ) {
14994 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14995 selection.set_head(Point::zero(), SelectionGoal::None);
14996 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14997 self.change_selections(Default::default(), window, cx, |s| {
14998 s.select(vec![selection]);
14999 });
15000 }
15001
15002 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
15003 if matches!(self.mode, EditorMode::SingleLine) {
15004 cx.propagate();
15005 return;
15006 }
15007 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15008 let cursor = self.buffer.read(cx).read(cx).len();
15009 self.change_selections(Default::default(), window, cx, |s| {
15010 s.select_ranges(vec![cursor..cursor])
15011 });
15012 }
15013
15014 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
15015 self.nav_history = nav_history;
15016 }
15017
15018 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
15019 self.nav_history.as_ref()
15020 }
15021
15022 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
15023 self.push_to_nav_history(
15024 self.selections.newest_anchor().head(),
15025 None,
15026 false,
15027 true,
15028 cx,
15029 );
15030 }
15031
15032 fn navigation_data(&self, cursor_anchor: Anchor, cx: &mut Context<Self>) -> NavigationData {
15033 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15034 let buffer = self.buffer.read(cx).read(cx);
15035 let cursor_position = cursor_anchor.to_point(&buffer);
15036 let scroll_anchor = self.scroll_manager.native_anchor(&display_snapshot, cx);
15037 let scroll_top_row = scroll_anchor.top_row(&buffer);
15038 drop(buffer);
15039
15040 NavigationData {
15041 cursor_anchor,
15042 cursor_position,
15043 scroll_anchor,
15044 scroll_top_row,
15045 }
15046 }
15047
15048 fn navigation_entry(
15049 &self,
15050 cursor_anchor: Anchor,
15051 cx: &mut Context<Self>,
15052 ) -> Option<NavigationEntry> {
15053 let Some(history) = self.nav_history.clone() else {
15054 return None;
15055 };
15056 let data = self.navigation_data(cursor_anchor, cx);
15057 Some(history.navigation_entry(Some(Arc::new(data) as Arc<dyn Any + Send + Sync>)))
15058 }
15059
15060 fn push_to_nav_history(
15061 &mut self,
15062 cursor_anchor: Anchor,
15063 new_position: Option<Point>,
15064 is_deactivate: bool,
15065 always: bool,
15066 cx: &mut Context<Self>,
15067 ) {
15068 let data = self.navigation_data(cursor_anchor, cx);
15069 if let Some(nav_history) = self.nav_history.as_mut() {
15070 if let Some(new_position) = new_position {
15071 let row_delta = (new_position.row as i64 - data.cursor_position.row as i64).abs();
15072 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
15073 return;
15074 }
15075 }
15076
15077 nav_history.push(Some(data), cx);
15078 cx.emit(EditorEvent::PushedToNavHistory {
15079 anchor: cursor_anchor,
15080 is_deactivate,
15081 })
15082 }
15083 }
15084
15085 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
15086 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15087 let buffer = self.buffer.read(cx).snapshot(cx);
15088 let mut selection = self
15089 .selections
15090 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
15091 selection.set_head(buffer.len(), SelectionGoal::None);
15092 self.change_selections(Default::default(), window, cx, |s| {
15093 s.select(vec![selection]);
15094 });
15095 }
15096
15097 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
15098 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15099 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15100 s.select_ranges(vec![Anchor::min()..Anchor::max()]);
15101 });
15102 }
15103
15104 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
15105 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15106 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15107 let mut selections = self.selections.all::<Point>(&display_map);
15108 let max_point = display_map.buffer_snapshot().max_point();
15109 for selection in &mut selections {
15110 let rows = selection.spanned_rows(true, &display_map);
15111 selection.start = Point::new(rows.start.0, 0);
15112 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
15113 selection.reversed = false;
15114 }
15115 self.change_selections(Default::default(), window, cx, |s| {
15116 s.select(selections);
15117 });
15118 }
15119
15120 pub fn split_selection_into_lines(
15121 &mut self,
15122 action: &SplitSelectionIntoLines,
15123 window: &mut Window,
15124 cx: &mut Context<Self>,
15125 ) {
15126 let selections = self
15127 .selections
15128 .all::<Point>(&self.display_snapshot(cx))
15129 .into_iter()
15130 .map(|selection| selection.start..selection.end)
15131 .collect::<Vec<_>>();
15132 self.unfold_ranges(&selections, true, true, cx);
15133
15134 let mut new_selection_ranges = Vec::new();
15135 {
15136 let buffer = self.buffer.read(cx).read(cx);
15137 for selection in selections {
15138 for row in selection.start.row..selection.end.row {
15139 let line_start = Point::new(row, 0);
15140 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
15141
15142 if action.keep_selections {
15143 // Keep the selection range for each line
15144 let selection_start = if row == selection.start.row {
15145 selection.start
15146 } else {
15147 line_start
15148 };
15149 new_selection_ranges.push(selection_start..line_end);
15150 } else {
15151 // Collapse to cursor at end of line
15152 new_selection_ranges.push(line_end..line_end);
15153 }
15154 }
15155
15156 let is_multiline_selection = selection.start.row != selection.end.row;
15157 // Don't insert last one if it's a multi-line selection ending at the start of a line,
15158 // so this action feels more ergonomic when paired with other selection operations
15159 let should_skip_last = is_multiline_selection && selection.end.column == 0;
15160 if !should_skip_last {
15161 if action.keep_selections {
15162 if is_multiline_selection {
15163 let line_start = Point::new(selection.end.row, 0);
15164 new_selection_ranges.push(line_start..selection.end);
15165 } else {
15166 new_selection_ranges.push(selection.start..selection.end);
15167 }
15168 } else {
15169 new_selection_ranges.push(selection.end..selection.end);
15170 }
15171 }
15172 }
15173 }
15174 self.change_selections(Default::default(), window, cx, |s| {
15175 s.select_ranges(new_selection_ranges);
15176 });
15177 }
15178
15179 pub fn add_selection_above(
15180 &mut self,
15181 action: &AddSelectionAbove,
15182 window: &mut Window,
15183 cx: &mut Context<Self>,
15184 ) {
15185 self.add_selection(true, action.skip_soft_wrap, window, cx);
15186 }
15187
15188 pub fn add_selection_below(
15189 &mut self,
15190 action: &AddSelectionBelow,
15191 window: &mut Window,
15192 cx: &mut Context<Self>,
15193 ) {
15194 self.add_selection(false, action.skip_soft_wrap, window, cx);
15195 }
15196
15197 fn add_selection(
15198 &mut self,
15199 above: bool,
15200 skip_soft_wrap: bool,
15201 window: &mut Window,
15202 cx: &mut Context<Self>,
15203 ) {
15204 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15205
15206 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15207 let all_selections = self.selections.all::<Point>(&display_map);
15208 let text_layout_details = self.text_layout_details(window, cx);
15209
15210 let (mut columnar_selections, new_selections_to_columnarize) = {
15211 if let Some(state) = self.add_selections_state.as_ref() {
15212 let columnar_selection_ids: HashSet<_> = state
15213 .groups
15214 .iter()
15215 .flat_map(|group| group.stack.iter())
15216 .copied()
15217 .collect();
15218
15219 all_selections
15220 .into_iter()
15221 .partition(|s| columnar_selection_ids.contains(&s.id))
15222 } else {
15223 (Vec::new(), all_selections)
15224 }
15225 };
15226
15227 let mut state = self
15228 .add_selections_state
15229 .take()
15230 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
15231
15232 for selection in new_selections_to_columnarize {
15233 let range = selection.display_range(&display_map).sorted();
15234 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
15235 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
15236 let positions = start_x.min(end_x)..start_x.max(end_x);
15237 let mut stack = Vec::new();
15238 for row in range.start.row().0..=range.end.row().0 {
15239 if let Some(selection) = self.selections.build_columnar_selection(
15240 &display_map,
15241 DisplayRow(row),
15242 &positions,
15243 selection.reversed,
15244 &text_layout_details,
15245 ) {
15246 stack.push(selection.id);
15247 columnar_selections.push(selection);
15248 }
15249 }
15250 if !stack.is_empty() {
15251 if above {
15252 stack.reverse();
15253 }
15254 state.groups.push(AddSelectionsGroup { above, stack });
15255 }
15256 }
15257
15258 let mut final_selections = Vec::new();
15259 let end_row = if above {
15260 DisplayRow(0)
15261 } else {
15262 display_map.max_point().row()
15263 };
15264
15265 // When `skip_soft_wrap` is true, we use buffer columns instead of pixel
15266 // positions to place new selections, so we need to keep track of the
15267 // column range of the oldest selection in each group, because
15268 // intermediate selections may have been clamped to shorter lines.
15269 // selections may have been clamped to shorter lines.
15270 let mut goal_columns_by_selection_id = if skip_soft_wrap {
15271 let mut map = HashMap::default();
15272 for group in state.groups.iter() {
15273 if let Some(oldest_id) = group.stack.first() {
15274 if let Some(oldest_selection) =
15275 columnar_selections.iter().find(|s| s.id == *oldest_id)
15276 {
15277 let start_col = oldest_selection.start.column;
15278 let end_col = oldest_selection.end.column;
15279 let goal_columns = start_col.min(end_col)..start_col.max(end_col);
15280 for id in &group.stack {
15281 map.insert(*id, goal_columns.clone());
15282 }
15283 }
15284 }
15285 }
15286 map
15287 } else {
15288 HashMap::default()
15289 };
15290
15291 let mut last_added_item_per_group = HashMap::default();
15292 for group in state.groups.iter_mut() {
15293 if let Some(last_id) = group.stack.last() {
15294 last_added_item_per_group.insert(*last_id, group);
15295 }
15296 }
15297
15298 for selection in columnar_selections {
15299 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
15300 if above == group.above {
15301 let range = selection.display_range(&display_map).sorted();
15302 debug_assert_eq!(range.start.row(), range.end.row());
15303 let row = range.start.row();
15304 let positions =
15305 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
15306 Pixels::from(start)..Pixels::from(end)
15307 } else {
15308 let start_x =
15309 display_map.x_for_display_point(range.start, &text_layout_details);
15310 let end_x =
15311 display_map.x_for_display_point(range.end, &text_layout_details);
15312 start_x.min(end_x)..start_x.max(end_x)
15313 };
15314
15315 let maybe_new_selection = if skip_soft_wrap {
15316 let goal_columns = goal_columns_by_selection_id
15317 .remove(&selection.id)
15318 .unwrap_or_else(|| {
15319 let start_col = selection.start.column;
15320 let end_col = selection.end.column;
15321 start_col.min(end_col)..start_col.max(end_col)
15322 });
15323 self.selections.find_next_columnar_selection_by_buffer_row(
15324 &display_map,
15325 row,
15326 end_row,
15327 above,
15328 &goal_columns,
15329 selection.reversed,
15330 &text_layout_details,
15331 )
15332 } else {
15333 self.selections.find_next_columnar_selection_by_display_row(
15334 &display_map,
15335 row,
15336 end_row,
15337 above,
15338 &positions,
15339 selection.reversed,
15340 &text_layout_details,
15341 )
15342 };
15343
15344 if let Some(new_selection) = maybe_new_selection {
15345 group.stack.push(new_selection.id);
15346 if above {
15347 final_selections.push(new_selection);
15348 final_selections.push(selection);
15349 } else {
15350 final_selections.push(selection);
15351 final_selections.push(new_selection);
15352 }
15353 } else {
15354 final_selections.push(selection);
15355 }
15356 } else {
15357 group.stack.pop();
15358 }
15359 } else {
15360 final_selections.push(selection);
15361 }
15362 }
15363
15364 self.change_selections(Default::default(), window, cx, |s| {
15365 s.select(final_selections);
15366 });
15367
15368 let final_selection_ids: HashSet<_> = self
15369 .selections
15370 .all::<Point>(&display_map)
15371 .iter()
15372 .map(|s| s.id)
15373 .collect();
15374 state.groups.retain_mut(|group| {
15375 // selections might get merged above so we remove invalid items from stacks
15376 group.stack.retain(|id| final_selection_ids.contains(id));
15377
15378 // single selection in stack can be treated as initial state
15379 group.stack.len() > 1
15380 });
15381
15382 if !state.groups.is_empty() {
15383 self.add_selections_state = Some(state);
15384 }
15385 }
15386
15387 pub fn insert_snippet_at_selections(
15388 &mut self,
15389 action: &InsertSnippet,
15390 window: &mut Window,
15391 cx: &mut Context<Self>,
15392 ) {
15393 self.try_insert_snippet_at_selections(action, window, cx)
15394 .log_err();
15395 }
15396
15397 fn try_insert_snippet_at_selections(
15398 &mut self,
15399 action: &InsertSnippet,
15400 window: &mut Window,
15401 cx: &mut Context<Self>,
15402 ) -> Result<()> {
15403 let insertion_ranges = self
15404 .selections
15405 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15406 .into_iter()
15407 .map(|selection| selection.range())
15408 .collect_vec();
15409
15410 let snippet = if let Some(snippet_body) = &action.snippet {
15411 if action.language.is_none() && action.name.is_none() {
15412 Snippet::parse(snippet_body)?
15413 } else {
15414 bail!("`snippet` is mutually exclusive with `language` and `name`")
15415 }
15416 } else if let Some(name) = &action.name {
15417 let project = self.project().context("no project")?;
15418 let snippet_store = project.read(cx).snippets().read(cx);
15419 let snippet = snippet_store
15420 .snippets_for(action.language.clone(), cx)
15421 .into_iter()
15422 .find(|snippet| snippet.name == *name)
15423 .context("snippet not found")?;
15424 Snippet::parse(&snippet.body)?
15425 } else {
15426 // todo(andrew): open modal to select snippet
15427 bail!("`name` or `snippet` is required")
15428 };
15429
15430 self.insert_snippet(&insertion_ranges, snippet, window, cx)
15431 }
15432
15433 fn select_match_ranges(
15434 &mut self,
15435 range: Range<MultiBufferOffset>,
15436 reversed: bool,
15437 replace_newest: bool,
15438 auto_scroll: Option<Autoscroll>,
15439 window: &mut Window,
15440 cx: &mut Context<Editor>,
15441 ) {
15442 self.unfold_ranges(
15443 std::slice::from_ref(&range),
15444 false,
15445 auto_scroll.is_some(),
15446 cx,
15447 );
15448 let effects = if let Some(scroll) = auto_scroll {
15449 SelectionEffects::scroll(scroll)
15450 } else {
15451 SelectionEffects::no_scroll()
15452 };
15453 self.change_selections(effects, window, cx, |s| {
15454 if replace_newest {
15455 s.delete(s.newest_anchor().id);
15456 }
15457 if reversed {
15458 s.insert_range(range.end..range.start);
15459 } else {
15460 s.insert_range(range);
15461 }
15462 });
15463 }
15464
15465 pub fn select_next_match_internal(
15466 &mut self,
15467 display_map: &DisplaySnapshot,
15468 replace_newest: bool,
15469 autoscroll: Option<Autoscroll>,
15470 window: &mut Window,
15471 cx: &mut Context<Self>,
15472 ) -> Result<()> {
15473 let buffer = display_map.buffer_snapshot();
15474 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15475 if let Some(mut select_next_state) = self.select_next_state.take() {
15476 let query = &select_next_state.query;
15477 if !select_next_state.done {
15478 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15479 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15480 let mut next_selected_range = None;
15481
15482 let bytes_after_last_selection =
15483 buffer.bytes_in_range(last_selection.end..buffer.len());
15484 let bytes_before_first_selection =
15485 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
15486 let query_matches = query
15487 .stream_find_iter(bytes_after_last_selection)
15488 .map(|result| (last_selection.end, result))
15489 .chain(
15490 query
15491 .stream_find_iter(bytes_before_first_selection)
15492 .map(|result| (MultiBufferOffset(0), result)),
15493 );
15494
15495 for (start_offset, query_match) in query_matches {
15496 let query_match = query_match.unwrap(); // can only fail due to I/O
15497 let offset_range =
15498 start_offset + query_match.start()..start_offset + query_match.end();
15499
15500 if !select_next_state.wordwise
15501 || (!buffer.is_inside_word(offset_range.start, None)
15502 && !buffer.is_inside_word(offset_range.end, None))
15503 {
15504 let idx = selections
15505 .partition_point(|selection| selection.end <= offset_range.start);
15506 let overlaps = selections
15507 .get(idx)
15508 .map_or(false, |selection| selection.start < offset_range.end);
15509
15510 if !overlaps {
15511 next_selected_range = Some(offset_range);
15512 break;
15513 }
15514 }
15515 }
15516
15517 if let Some(next_selected_range) = next_selected_range {
15518 self.select_match_ranges(
15519 next_selected_range,
15520 last_selection.reversed,
15521 replace_newest,
15522 autoscroll,
15523 window,
15524 cx,
15525 );
15526 } else {
15527 select_next_state.done = true;
15528 }
15529 }
15530
15531 self.select_next_state = Some(select_next_state);
15532 } else {
15533 let mut only_carets = true;
15534 let mut same_text_selected = true;
15535 let mut selected_text = None;
15536
15537 let mut selections_iter = selections.iter().peekable();
15538 while let Some(selection) = selections_iter.next() {
15539 if selection.start != selection.end {
15540 only_carets = false;
15541 }
15542
15543 if same_text_selected {
15544 if selected_text.is_none() {
15545 selected_text =
15546 Some(buffer.text_for_range(selection.range()).collect::<String>());
15547 }
15548
15549 if let Some(next_selection) = selections_iter.peek() {
15550 if next_selection.len() == selection.len() {
15551 let next_selected_text = buffer
15552 .text_for_range(next_selection.range())
15553 .collect::<String>();
15554 if Some(next_selected_text) != selected_text {
15555 same_text_selected = false;
15556 selected_text = None;
15557 }
15558 } else {
15559 same_text_selected = false;
15560 selected_text = None;
15561 }
15562 }
15563 }
15564 }
15565
15566 if only_carets {
15567 for selection in &mut selections {
15568 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15569 selection.start = word_range.start;
15570 selection.end = word_range.end;
15571 selection.goal = SelectionGoal::None;
15572 selection.reversed = false;
15573 self.select_match_ranges(
15574 selection.start..selection.end,
15575 selection.reversed,
15576 replace_newest,
15577 autoscroll,
15578 window,
15579 cx,
15580 );
15581 }
15582
15583 if selections.len() == 1 {
15584 let selection = selections
15585 .last()
15586 .expect("ensured that there's only one selection");
15587 let query = buffer
15588 .text_for_range(selection.start..selection.end)
15589 .collect::<String>();
15590 let is_empty = query.is_empty();
15591 let select_state = SelectNextState {
15592 query: self.build_query(&[query], cx)?,
15593 wordwise: true,
15594 done: is_empty,
15595 };
15596 self.select_next_state = Some(select_state);
15597 } else {
15598 self.select_next_state = None;
15599 }
15600 } else if let Some(selected_text) = selected_text {
15601 self.select_next_state = Some(SelectNextState {
15602 query: self.build_query(&[selected_text], cx)?,
15603 wordwise: false,
15604 done: false,
15605 });
15606 self.select_next_match_internal(
15607 display_map,
15608 replace_newest,
15609 autoscroll,
15610 window,
15611 cx,
15612 )?;
15613 }
15614 }
15615 Ok(())
15616 }
15617
15618 pub fn select_all_matches(
15619 &mut self,
15620 _action: &SelectAllMatches,
15621 window: &mut Window,
15622 cx: &mut Context<Self>,
15623 ) -> Result<()> {
15624 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15625
15626 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15627
15628 self.select_next_match_internal(&display_map, false, None, window, cx)?;
15629 let Some(select_next_state) = self.select_next_state.as_mut().filter(|state| !state.done)
15630 else {
15631 return Ok(());
15632 };
15633
15634 let mut new_selections = Vec::new();
15635
15636 let reversed = self
15637 .selections
15638 .oldest::<MultiBufferOffset>(&display_map)
15639 .reversed;
15640 let buffer = display_map.buffer_snapshot();
15641 let query_matches = select_next_state
15642 .query
15643 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
15644
15645 for query_match in query_matches.into_iter() {
15646 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
15647 let offset_range = if reversed {
15648 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
15649 } else {
15650 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
15651 };
15652
15653 if !select_next_state.wordwise
15654 || (!buffer.is_inside_word(offset_range.start, None)
15655 && !buffer.is_inside_word(offset_range.end, None))
15656 {
15657 new_selections.push(offset_range.start..offset_range.end);
15658 }
15659 }
15660
15661 select_next_state.done = true;
15662
15663 if new_selections.is_empty() {
15664 log::error!("bug: new_selections is empty in select_all_matches");
15665 return Ok(());
15666 }
15667
15668 self.unfold_ranges(&new_selections, false, false, cx);
15669 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
15670 selections.select_ranges(new_selections)
15671 });
15672
15673 Ok(())
15674 }
15675
15676 pub fn select_next(
15677 &mut self,
15678 action: &SelectNext,
15679 window: &mut Window,
15680 cx: &mut Context<Self>,
15681 ) -> Result<()> {
15682 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15683 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15684 self.select_next_match_internal(
15685 &display_map,
15686 action.replace_newest,
15687 Some(Autoscroll::newest()),
15688 window,
15689 cx,
15690 )
15691 }
15692
15693 pub fn select_previous(
15694 &mut self,
15695 action: &SelectPrevious,
15696 window: &mut Window,
15697 cx: &mut Context<Self>,
15698 ) -> Result<()> {
15699 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15700 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15701 let buffer = display_map.buffer_snapshot();
15702 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15703 if let Some(mut select_prev_state) = self.select_prev_state.take() {
15704 let query = &select_prev_state.query;
15705 if !select_prev_state.done {
15706 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15707 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15708 let mut next_selected_range = None;
15709 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
15710 let bytes_before_last_selection =
15711 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
15712 let bytes_after_first_selection =
15713 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
15714 let query_matches = query
15715 .stream_find_iter(bytes_before_last_selection)
15716 .map(|result| (last_selection.start, result))
15717 .chain(
15718 query
15719 .stream_find_iter(bytes_after_first_selection)
15720 .map(|result| (buffer.len(), result)),
15721 );
15722 for (end_offset, query_match) in query_matches {
15723 let query_match = query_match.unwrap(); // can only fail due to I/O
15724 let offset_range =
15725 end_offset - query_match.end()..end_offset - query_match.start();
15726
15727 if !select_prev_state.wordwise
15728 || (!buffer.is_inside_word(offset_range.start, None)
15729 && !buffer.is_inside_word(offset_range.end, None))
15730 {
15731 next_selected_range = Some(offset_range);
15732 break;
15733 }
15734 }
15735
15736 if let Some(next_selected_range) = next_selected_range {
15737 self.select_match_ranges(
15738 next_selected_range,
15739 last_selection.reversed,
15740 action.replace_newest,
15741 Some(Autoscroll::newest()),
15742 window,
15743 cx,
15744 );
15745 } else {
15746 select_prev_state.done = true;
15747 }
15748 }
15749
15750 self.select_prev_state = Some(select_prev_state);
15751 } else {
15752 let mut only_carets = true;
15753 let mut same_text_selected = true;
15754 let mut selected_text = None;
15755
15756 let mut selections_iter = selections.iter().peekable();
15757 while let Some(selection) = selections_iter.next() {
15758 if selection.start != selection.end {
15759 only_carets = false;
15760 }
15761
15762 if same_text_selected {
15763 if selected_text.is_none() {
15764 selected_text =
15765 Some(buffer.text_for_range(selection.range()).collect::<String>());
15766 }
15767
15768 if let Some(next_selection) = selections_iter.peek() {
15769 if next_selection.len() == selection.len() {
15770 let next_selected_text = buffer
15771 .text_for_range(next_selection.range())
15772 .collect::<String>();
15773 if Some(next_selected_text) != selected_text {
15774 same_text_selected = false;
15775 selected_text = None;
15776 }
15777 } else {
15778 same_text_selected = false;
15779 selected_text = None;
15780 }
15781 }
15782 }
15783 }
15784
15785 if only_carets {
15786 for selection in &mut selections {
15787 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15788 selection.start = word_range.start;
15789 selection.end = word_range.end;
15790 selection.goal = SelectionGoal::None;
15791 selection.reversed = false;
15792 self.select_match_ranges(
15793 selection.start..selection.end,
15794 selection.reversed,
15795 action.replace_newest,
15796 Some(Autoscroll::newest()),
15797 window,
15798 cx,
15799 );
15800 }
15801 if selections.len() == 1 {
15802 let selection = selections
15803 .last()
15804 .expect("ensured that there's only one selection");
15805 let query = buffer
15806 .text_for_range(selection.start..selection.end)
15807 .collect::<String>();
15808 let is_empty = query.is_empty();
15809 let select_state = SelectNextState {
15810 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
15811 wordwise: true,
15812 done: is_empty,
15813 };
15814 self.select_prev_state = Some(select_state);
15815 } else {
15816 self.select_prev_state = None;
15817 }
15818 } else if let Some(selected_text) = selected_text {
15819 self.select_prev_state = Some(SelectNextState {
15820 query: self
15821 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
15822 wordwise: false,
15823 done: false,
15824 });
15825 self.select_previous(action, window, cx)?;
15826 }
15827 }
15828 Ok(())
15829 }
15830
15831 /// Builds an `AhoCorasick` automaton from the provided patterns, while
15832 /// setting the case sensitivity based on the global
15833 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
15834 /// editor's settings.
15835 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
15836 where
15837 I: IntoIterator<Item = P>,
15838 P: AsRef<[u8]>,
15839 {
15840 let case_sensitive = self
15841 .select_next_is_case_sensitive
15842 .unwrap_or_else(|| EditorSettings::get_global(cx).search.case_sensitive);
15843
15844 let mut builder = AhoCorasickBuilder::new();
15845 builder.ascii_case_insensitive(!case_sensitive);
15846 builder.build(patterns)
15847 }
15848
15849 pub fn find_next_match(
15850 &mut self,
15851 _: &FindNextMatch,
15852 window: &mut Window,
15853 cx: &mut Context<Self>,
15854 ) -> Result<()> {
15855 let selections = self.selections.disjoint_anchors_arc();
15856 match selections.first() {
15857 Some(first) if selections.len() >= 2 => {
15858 self.change_selections(Default::default(), window, cx, |s| {
15859 s.select_ranges([first.range()]);
15860 });
15861 }
15862 _ => self.select_next(
15863 &SelectNext {
15864 replace_newest: true,
15865 },
15866 window,
15867 cx,
15868 )?,
15869 }
15870 Ok(())
15871 }
15872
15873 pub fn find_previous_match(
15874 &mut self,
15875 _: &FindPreviousMatch,
15876 window: &mut Window,
15877 cx: &mut Context<Self>,
15878 ) -> Result<()> {
15879 let selections = self.selections.disjoint_anchors_arc();
15880 match selections.last() {
15881 Some(last) if selections.len() >= 2 => {
15882 self.change_selections(Default::default(), window, cx, |s| {
15883 s.select_ranges([last.range()]);
15884 });
15885 }
15886 _ => self.select_previous(
15887 &SelectPrevious {
15888 replace_newest: true,
15889 },
15890 window,
15891 cx,
15892 )?,
15893 }
15894 Ok(())
15895 }
15896
15897 pub fn toggle_comments(
15898 &mut self,
15899 action: &ToggleComments,
15900 window: &mut Window,
15901 cx: &mut Context<Self>,
15902 ) {
15903 if self.read_only(cx) {
15904 return;
15905 }
15906 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15907 let text_layout_details = &self.text_layout_details(window, cx);
15908 self.transact(window, cx, |this, window, cx| {
15909 let mut selections = this
15910 .selections
15911 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
15912 let mut edits = Vec::new();
15913 let mut selection_edit_ranges = Vec::new();
15914 let mut last_toggled_row = None;
15915 let snapshot = this.buffer.read(cx).read(cx);
15916 let empty_str: Arc<str> = Arc::default();
15917 let mut suffixes_inserted = Vec::new();
15918 let ignore_indent = action.ignore_indent;
15919
15920 fn comment_prefix_range(
15921 snapshot: &MultiBufferSnapshot,
15922 row: MultiBufferRow,
15923 comment_prefix: &str,
15924 comment_prefix_whitespace: &str,
15925 ignore_indent: bool,
15926 ) -> Range<Point> {
15927 let indent_size = if ignore_indent {
15928 0
15929 } else {
15930 snapshot.indent_size_for_line(row).len
15931 };
15932
15933 let start = Point::new(row.0, indent_size);
15934
15935 let mut line_bytes = snapshot
15936 .bytes_in_range(start..snapshot.max_point())
15937 .flatten()
15938 .copied();
15939
15940 // If this line currently begins with the line comment prefix, then record
15941 // the range containing the prefix.
15942 if line_bytes
15943 .by_ref()
15944 .take(comment_prefix.len())
15945 .eq(comment_prefix.bytes())
15946 {
15947 // Include any whitespace that matches the comment prefix.
15948 let matching_whitespace_len = line_bytes
15949 .zip(comment_prefix_whitespace.bytes())
15950 .take_while(|(a, b)| a == b)
15951 .count() as u32;
15952 let end = Point::new(
15953 start.row,
15954 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
15955 );
15956 start..end
15957 } else {
15958 start..start
15959 }
15960 }
15961
15962 fn comment_suffix_range(
15963 snapshot: &MultiBufferSnapshot,
15964 row: MultiBufferRow,
15965 comment_suffix: &str,
15966 comment_suffix_has_leading_space: bool,
15967 ) -> Range<Point> {
15968 let end = Point::new(row.0, snapshot.line_len(row));
15969 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
15970
15971 let mut line_end_bytes = snapshot
15972 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15973 .flatten()
15974 .copied();
15975
15976 let leading_space_len = if suffix_start_column > 0
15977 && line_end_bytes.next() == Some(b' ')
15978 && comment_suffix_has_leading_space
15979 {
15980 1
15981 } else {
15982 0
15983 };
15984
15985 // If this line currently begins with the line comment prefix, then record
15986 // the range containing the prefix.
15987 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15988 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15989 start..end
15990 } else {
15991 end..end
15992 }
15993 }
15994
15995 // TODO: Handle selections that cross excerpts
15996 for selection in &mut selections {
15997 let start_column = snapshot
15998 .indent_size_for_line(MultiBufferRow(selection.start.row))
15999 .len;
16000 let language = if let Some(language) =
16001 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
16002 {
16003 language
16004 } else {
16005 continue;
16006 };
16007
16008 selection_edit_ranges.clear();
16009
16010 // If multiple selections contain a given row, avoid processing that
16011 // row more than once.
16012 let mut start_row = MultiBufferRow(selection.start.row);
16013 if last_toggled_row == Some(start_row) {
16014 start_row = start_row.next_row();
16015 }
16016 let end_row =
16017 if selection.end.row > selection.start.row && selection.end.column == 0 {
16018 MultiBufferRow(selection.end.row - 1)
16019 } else {
16020 MultiBufferRow(selection.end.row)
16021 };
16022 last_toggled_row = Some(end_row);
16023
16024 if start_row > end_row {
16025 continue;
16026 }
16027
16028 // If the language has line comments, toggle those.
16029 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
16030
16031 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
16032 if ignore_indent {
16033 full_comment_prefixes = full_comment_prefixes
16034 .into_iter()
16035 .map(|s| Arc::from(s.trim_end()))
16036 .collect();
16037 }
16038
16039 if !full_comment_prefixes.is_empty() {
16040 let first_prefix = full_comment_prefixes
16041 .first()
16042 .expect("prefixes is non-empty");
16043 let prefix_trimmed_lengths = full_comment_prefixes
16044 .iter()
16045 .map(|p| p.trim_end_matches(' ').len())
16046 .collect::<SmallVec<[usize; 4]>>();
16047
16048 let mut all_selection_lines_are_comments = true;
16049
16050 for row in start_row.0..=end_row.0 {
16051 let row = MultiBufferRow(row);
16052 if start_row < end_row && snapshot.is_line_blank(row) {
16053 continue;
16054 }
16055
16056 let prefix_range = full_comment_prefixes
16057 .iter()
16058 .zip(prefix_trimmed_lengths.iter().copied())
16059 .map(|(prefix, trimmed_prefix_len)| {
16060 comment_prefix_range(
16061 snapshot.deref(),
16062 row,
16063 &prefix[..trimmed_prefix_len],
16064 &prefix[trimmed_prefix_len..],
16065 ignore_indent,
16066 )
16067 })
16068 .max_by_key(|range| range.end.column - range.start.column)
16069 .expect("prefixes is non-empty");
16070
16071 if prefix_range.is_empty() {
16072 all_selection_lines_are_comments = false;
16073 }
16074
16075 selection_edit_ranges.push(prefix_range);
16076 }
16077
16078 if all_selection_lines_are_comments {
16079 edits.extend(
16080 selection_edit_ranges
16081 .iter()
16082 .cloned()
16083 .map(|range| (range, empty_str.clone())),
16084 );
16085 } else {
16086 let min_column = selection_edit_ranges
16087 .iter()
16088 .map(|range| range.start.column)
16089 .min()
16090 .unwrap_or(0);
16091 edits.extend(selection_edit_ranges.iter().map(|range| {
16092 let position = Point::new(range.start.row, min_column);
16093 (position..position, first_prefix.clone())
16094 }));
16095 }
16096 } else if let Some(BlockCommentConfig {
16097 start: full_comment_prefix,
16098 end: comment_suffix,
16099 ..
16100 }) = language.block_comment()
16101 {
16102 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
16103 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
16104 let prefix_range = comment_prefix_range(
16105 snapshot.deref(),
16106 start_row,
16107 comment_prefix,
16108 comment_prefix_whitespace,
16109 ignore_indent,
16110 );
16111 let suffix_range = comment_suffix_range(
16112 snapshot.deref(),
16113 end_row,
16114 comment_suffix.trim_start_matches(' '),
16115 comment_suffix.starts_with(' '),
16116 );
16117
16118 if prefix_range.is_empty() || suffix_range.is_empty() {
16119 edits.push((
16120 prefix_range.start..prefix_range.start,
16121 full_comment_prefix.clone(),
16122 ));
16123 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
16124 suffixes_inserted.push((end_row, comment_suffix.len()));
16125 } else {
16126 edits.push((prefix_range, empty_str.clone()));
16127 edits.push((suffix_range, empty_str.clone()));
16128 }
16129 } else {
16130 continue;
16131 }
16132 }
16133
16134 drop(snapshot);
16135 this.buffer.update(cx, |buffer, cx| {
16136 buffer.edit(edits, None, cx);
16137 });
16138
16139 // Adjust selections so that they end before any comment suffixes that
16140 // were inserted.
16141 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
16142 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16143 let snapshot = this.buffer.read(cx).read(cx);
16144 for selection in &mut selections {
16145 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
16146 match row.cmp(&MultiBufferRow(selection.end.row)) {
16147 Ordering::Less => {
16148 suffixes_inserted.next();
16149 continue;
16150 }
16151 Ordering::Greater => break,
16152 Ordering::Equal => {
16153 if selection.end.column == snapshot.line_len(row) {
16154 if selection.is_empty() {
16155 selection.start.column -= suffix_len as u32;
16156 }
16157 selection.end.column -= suffix_len as u32;
16158 }
16159 break;
16160 }
16161 }
16162 }
16163 }
16164
16165 drop(snapshot);
16166 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
16167
16168 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16169 let selections_on_single_row = selections.windows(2).all(|selections| {
16170 selections[0].start.row == selections[1].start.row
16171 && selections[0].end.row == selections[1].end.row
16172 && selections[0].start.row == selections[0].end.row
16173 });
16174 let selections_selecting = selections
16175 .iter()
16176 .any(|selection| selection.start != selection.end);
16177 let advance_downwards = action.advance_downwards
16178 && selections_on_single_row
16179 && !selections_selecting
16180 && !matches!(this.mode, EditorMode::SingleLine);
16181
16182 if advance_downwards {
16183 let snapshot = this.buffer.read(cx).snapshot(cx);
16184
16185 this.change_selections(Default::default(), window, cx, |s| {
16186 s.move_cursors_with(|display_snapshot, display_point, _| {
16187 let mut point = display_point.to_point(display_snapshot);
16188 point.row += 1;
16189 point = snapshot.clip_point(point, Bias::Left);
16190 let display_point = point.to_display_point(display_snapshot);
16191 let goal = SelectionGoal::HorizontalPosition(
16192 display_snapshot
16193 .x_for_display_point(display_point, text_layout_details)
16194 .into(),
16195 );
16196 (display_point, goal)
16197 })
16198 });
16199 }
16200 });
16201 }
16202
16203 pub fn select_enclosing_symbol(
16204 &mut self,
16205 _: &SelectEnclosingSymbol,
16206 window: &mut Window,
16207 cx: &mut Context<Self>,
16208 ) {
16209 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16210
16211 let buffer = self.buffer.read(cx).snapshot(cx);
16212 let old_selections = self
16213 .selections
16214 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16215 .into_boxed_slice();
16216
16217 fn update_selection(
16218 selection: &Selection<MultiBufferOffset>,
16219 buffer_snap: &MultiBufferSnapshot,
16220 ) -> Option<Selection<MultiBufferOffset>> {
16221 let cursor = selection.head();
16222 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
16223 for symbol in symbols.iter().rev() {
16224 let start = symbol.range.start.to_offset(buffer_snap);
16225 let end = symbol.range.end.to_offset(buffer_snap);
16226 let new_range = start..end;
16227 if start < selection.start || end > selection.end {
16228 return Some(Selection {
16229 id: selection.id,
16230 start: new_range.start,
16231 end: new_range.end,
16232 goal: SelectionGoal::None,
16233 reversed: selection.reversed,
16234 });
16235 }
16236 }
16237 None
16238 }
16239
16240 let mut selected_larger_symbol = false;
16241 let new_selections = old_selections
16242 .iter()
16243 .map(|selection| match update_selection(selection, &buffer) {
16244 Some(new_selection) => {
16245 if new_selection.range() != selection.range() {
16246 selected_larger_symbol = true;
16247 }
16248 new_selection
16249 }
16250 None => selection.clone(),
16251 })
16252 .collect::<Vec<_>>();
16253
16254 if selected_larger_symbol {
16255 self.change_selections(Default::default(), window, cx, |s| {
16256 s.select(new_selections);
16257 });
16258 }
16259 }
16260
16261 pub fn select_larger_syntax_node(
16262 &mut self,
16263 _: &SelectLargerSyntaxNode,
16264 window: &mut Window,
16265 cx: &mut Context<Self>,
16266 ) {
16267 let Some(visible_row_count) = self.visible_row_count() else {
16268 return;
16269 };
16270 let old_selections: Box<[_]> = self
16271 .selections
16272 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16273 .into();
16274 if old_selections.is_empty() {
16275 return;
16276 }
16277
16278 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16279
16280 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16281 let buffer = self.buffer.read(cx).snapshot(cx);
16282
16283 let mut selected_larger_node = false;
16284 let mut new_selections = old_selections
16285 .iter()
16286 .map(|selection| {
16287 let old_range = selection.start..selection.end;
16288
16289 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
16290 // manually select word at selection
16291 if ["string_content", "inline"].contains(&node.kind()) {
16292 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
16293 // ignore if word is already selected
16294 if !word_range.is_empty() && old_range != word_range {
16295 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
16296 // only select word if start and end point belongs to same word
16297 if word_range == last_word_range {
16298 selected_larger_node = true;
16299 return Selection {
16300 id: selection.id,
16301 start: word_range.start,
16302 end: word_range.end,
16303 goal: SelectionGoal::None,
16304 reversed: selection.reversed,
16305 };
16306 }
16307 }
16308 }
16309 }
16310
16311 let mut new_range = old_range.clone();
16312 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
16313 new_range = range;
16314 if !node.is_named() {
16315 continue;
16316 }
16317 if !display_map.intersects_fold(new_range.start)
16318 && !display_map.intersects_fold(new_range.end)
16319 {
16320 break;
16321 }
16322 }
16323
16324 selected_larger_node |= new_range != old_range;
16325 Selection {
16326 id: selection.id,
16327 start: new_range.start,
16328 end: new_range.end,
16329 goal: SelectionGoal::None,
16330 reversed: selection.reversed,
16331 }
16332 })
16333 .collect::<Vec<_>>();
16334
16335 if !selected_larger_node {
16336 return; // don't put this call in the history
16337 }
16338
16339 // scroll based on transformation done to the last selection created by the user
16340 let (last_old, last_new) = old_selections
16341 .last()
16342 .zip(new_selections.last().cloned())
16343 .expect("old_selections isn't empty");
16344
16345 // revert selection
16346 let is_selection_reversed = {
16347 let should_newest_selection_be_reversed = last_old.start != last_new.start;
16348 new_selections.last_mut().expect("checked above").reversed =
16349 should_newest_selection_be_reversed;
16350 should_newest_selection_be_reversed
16351 };
16352
16353 if selected_larger_node {
16354 self.select_syntax_node_history.disable_clearing = true;
16355 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16356 s.select(new_selections.clone());
16357 });
16358 self.select_syntax_node_history.disable_clearing = false;
16359 }
16360
16361 let start_row = last_new.start.to_display_point(&display_map).row().0;
16362 let end_row = last_new.end.to_display_point(&display_map).row().0;
16363 let selection_height = end_row - start_row + 1;
16364 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
16365
16366 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
16367 let scroll_behavior = if fits_on_the_screen {
16368 self.request_autoscroll(Autoscroll::fit(), cx);
16369 SelectSyntaxNodeScrollBehavior::FitSelection
16370 } else if is_selection_reversed {
16371 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16372 SelectSyntaxNodeScrollBehavior::CursorTop
16373 } else {
16374 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16375 SelectSyntaxNodeScrollBehavior::CursorBottom
16376 };
16377
16378 self.select_syntax_node_history.push((
16379 old_selections,
16380 scroll_behavior,
16381 is_selection_reversed,
16382 ));
16383 }
16384
16385 pub fn select_smaller_syntax_node(
16386 &mut self,
16387 _: &SelectSmallerSyntaxNode,
16388 window: &mut Window,
16389 cx: &mut Context<Self>,
16390 ) {
16391 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16392
16393 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
16394 self.select_syntax_node_history.pop()
16395 {
16396 if let Some(selection) = selections.last_mut() {
16397 selection.reversed = is_selection_reversed;
16398 }
16399
16400 self.select_syntax_node_history.disable_clearing = true;
16401 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16402 s.select(selections.to_vec());
16403 });
16404 self.select_syntax_node_history.disable_clearing = false;
16405
16406 match scroll_behavior {
16407 SelectSyntaxNodeScrollBehavior::CursorTop => {
16408 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16409 }
16410 SelectSyntaxNodeScrollBehavior::FitSelection => {
16411 self.request_autoscroll(Autoscroll::fit(), cx);
16412 }
16413 SelectSyntaxNodeScrollBehavior::CursorBottom => {
16414 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16415 }
16416 }
16417 }
16418 }
16419
16420 pub fn unwrap_syntax_node(
16421 &mut self,
16422 _: &UnwrapSyntaxNode,
16423 window: &mut Window,
16424 cx: &mut Context<Self>,
16425 ) {
16426 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16427
16428 let buffer = self.buffer.read(cx).snapshot(cx);
16429 let selections = self
16430 .selections
16431 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16432 .into_iter()
16433 // subtracting the offset requires sorting
16434 .sorted_by_key(|i| i.start);
16435
16436 let full_edits = selections
16437 .into_iter()
16438 .filter_map(|selection| {
16439 let child = if selection.is_empty()
16440 && let Some((_, ancestor_range)) =
16441 buffer.syntax_ancestor(selection.start..selection.end)
16442 {
16443 ancestor_range
16444 } else {
16445 selection.range()
16446 };
16447
16448 let mut parent = child.clone();
16449 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
16450 parent = ancestor_range;
16451 if parent.start < child.start || parent.end > child.end {
16452 break;
16453 }
16454 }
16455
16456 if parent == child {
16457 return None;
16458 }
16459 let text = buffer.text_for_range(child).collect::<String>();
16460 Some((selection.id, parent, text))
16461 })
16462 .collect::<Vec<_>>();
16463 if full_edits.is_empty() {
16464 return;
16465 }
16466
16467 self.transact(window, cx, |this, window, cx| {
16468 this.buffer.update(cx, |buffer, cx| {
16469 buffer.edit(
16470 full_edits
16471 .iter()
16472 .map(|(_, p, t)| (p.clone(), t.clone()))
16473 .collect::<Vec<_>>(),
16474 None,
16475 cx,
16476 );
16477 });
16478 this.change_selections(Default::default(), window, cx, |s| {
16479 let mut offset = 0;
16480 let mut selections = vec![];
16481 for (id, parent, text) in full_edits {
16482 let start = parent.start - offset;
16483 offset += (parent.end - parent.start) - text.len();
16484 selections.push(Selection {
16485 id,
16486 start,
16487 end: start + text.len(),
16488 reversed: false,
16489 goal: Default::default(),
16490 });
16491 }
16492 s.select(selections);
16493 });
16494 });
16495 }
16496
16497 pub fn select_next_syntax_node(
16498 &mut self,
16499 _: &SelectNextSyntaxNode,
16500 window: &mut Window,
16501 cx: &mut Context<Self>,
16502 ) {
16503 let old_selections: Box<[_]> = self
16504 .selections
16505 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16506 .into();
16507 if old_selections.is_empty() {
16508 return;
16509 }
16510
16511 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16512
16513 let buffer = self.buffer.read(cx).snapshot(cx);
16514 let mut selected_sibling = false;
16515
16516 let new_selections = old_selections
16517 .iter()
16518 .map(|selection| {
16519 let old_range = selection.start..selection.end;
16520
16521 let old_range =
16522 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16523 let excerpt = buffer.excerpt_containing(old_range.clone());
16524
16525 if let Some(mut excerpt) = excerpt
16526 && let Some(node) = excerpt
16527 .buffer()
16528 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
16529 {
16530 let new_range = excerpt.map_range_from_buffer(
16531 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16532 );
16533 selected_sibling = true;
16534 Selection {
16535 id: selection.id,
16536 start: new_range.start,
16537 end: new_range.end,
16538 goal: SelectionGoal::None,
16539 reversed: selection.reversed,
16540 }
16541 } else {
16542 selection.clone()
16543 }
16544 })
16545 .collect::<Vec<_>>();
16546
16547 if selected_sibling {
16548 self.change_selections(
16549 SelectionEffects::scroll(Autoscroll::fit()),
16550 window,
16551 cx,
16552 |s| {
16553 s.select(new_selections);
16554 },
16555 );
16556 }
16557 }
16558
16559 pub fn select_prev_syntax_node(
16560 &mut self,
16561 _: &SelectPreviousSyntaxNode,
16562 window: &mut Window,
16563 cx: &mut Context<Self>,
16564 ) {
16565 let old_selections: Box<[_]> = self
16566 .selections
16567 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16568 .into();
16569 if old_selections.is_empty() {
16570 return;
16571 }
16572
16573 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16574
16575 let buffer = self.buffer.read(cx).snapshot(cx);
16576 let mut selected_sibling = false;
16577
16578 let new_selections = old_selections
16579 .iter()
16580 .map(|selection| {
16581 let old_range = selection.start..selection.end;
16582 let old_range =
16583 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16584 let excerpt = buffer.excerpt_containing(old_range.clone());
16585
16586 if let Some(mut excerpt) = excerpt
16587 && let Some(node) = excerpt
16588 .buffer()
16589 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
16590 {
16591 let new_range = excerpt.map_range_from_buffer(
16592 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16593 );
16594 selected_sibling = true;
16595 Selection {
16596 id: selection.id,
16597 start: new_range.start,
16598 end: new_range.end,
16599 goal: SelectionGoal::None,
16600 reversed: selection.reversed,
16601 }
16602 } else {
16603 selection.clone()
16604 }
16605 })
16606 .collect::<Vec<_>>();
16607
16608 if selected_sibling {
16609 self.change_selections(
16610 SelectionEffects::scroll(Autoscroll::fit()),
16611 window,
16612 cx,
16613 |s| {
16614 s.select(new_selections);
16615 },
16616 );
16617 }
16618 }
16619
16620 pub fn move_to_start_of_larger_syntax_node(
16621 &mut self,
16622 _: &MoveToStartOfLargerSyntaxNode,
16623 window: &mut Window,
16624 cx: &mut Context<Self>,
16625 ) {
16626 self.move_cursors_to_syntax_nodes(window, cx, false);
16627 }
16628
16629 pub fn move_to_end_of_larger_syntax_node(
16630 &mut self,
16631 _: &MoveToEndOfLargerSyntaxNode,
16632 window: &mut Window,
16633 cx: &mut Context<Self>,
16634 ) {
16635 self.move_cursors_to_syntax_nodes(window, cx, true);
16636 }
16637
16638 fn find_syntax_node_boundary(
16639 &self,
16640 selection_pos: MultiBufferOffset,
16641 move_to_end: bool,
16642 display_map: &DisplaySnapshot,
16643 buffer: &MultiBufferSnapshot,
16644 ) -> MultiBufferOffset {
16645 let old_range = selection_pos..selection_pos;
16646 let mut new_pos = selection_pos;
16647 let mut search_range = old_range;
16648 while let Some((node, range)) = buffer.syntax_ancestor(search_range.clone()) {
16649 search_range = range.clone();
16650 if !node.is_named()
16651 || display_map.intersects_fold(range.start)
16652 || display_map.intersects_fold(range.end)
16653 // If cursor is already at the end of the syntax node, continue searching
16654 || (move_to_end && range.end == selection_pos)
16655 // If cursor is already at the start of the syntax node, continue searching
16656 || (!move_to_end && range.start == selection_pos)
16657 {
16658 continue;
16659 }
16660
16661 // If we found a string_content node, find the largest parent that is still string_content
16662 // Enables us to skip to the end of strings without taking multiple steps inside the string
16663 let (_, final_range) = if node.kind() == "string_content" {
16664 let mut current_node = node;
16665 let mut current_range = range;
16666 while let Some((parent, parent_range)) =
16667 buffer.syntax_ancestor(current_range.clone())
16668 {
16669 if parent.kind() == "string_content" {
16670 current_node = parent;
16671 current_range = parent_range;
16672 } else {
16673 break;
16674 }
16675 }
16676
16677 (current_node, current_range)
16678 } else {
16679 (node, range)
16680 };
16681
16682 new_pos = if move_to_end {
16683 final_range.end
16684 } else {
16685 final_range.start
16686 };
16687
16688 break;
16689 }
16690
16691 new_pos
16692 }
16693
16694 fn move_cursors_to_syntax_nodes(
16695 &mut self,
16696 window: &mut Window,
16697 cx: &mut Context<Self>,
16698 move_to_end: bool,
16699 ) -> bool {
16700 let old_selections: Box<[_]> = self
16701 .selections
16702 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16703 .into();
16704 if old_selections.is_empty() {
16705 return false;
16706 }
16707
16708 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16709
16710 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16711 let buffer = self.buffer.read(cx).snapshot(cx);
16712
16713 let mut any_cursor_moved = false;
16714 let new_selections = old_selections
16715 .iter()
16716 .map(|selection| {
16717 if !selection.is_empty() {
16718 return selection.clone();
16719 }
16720
16721 let selection_pos = selection.head();
16722 let new_pos = self.find_syntax_node_boundary(
16723 selection_pos,
16724 move_to_end,
16725 &display_map,
16726 &buffer,
16727 );
16728
16729 any_cursor_moved |= new_pos != selection_pos;
16730
16731 Selection {
16732 id: selection.id,
16733 start: new_pos,
16734 end: new_pos,
16735 goal: SelectionGoal::None,
16736 reversed: false,
16737 }
16738 })
16739 .collect::<Vec<_>>();
16740
16741 self.change_selections(Default::default(), window, cx, |s| {
16742 s.select(new_selections);
16743 });
16744 self.request_autoscroll(Autoscroll::newest(), cx);
16745
16746 any_cursor_moved
16747 }
16748
16749 pub fn select_to_start_of_larger_syntax_node(
16750 &mut self,
16751 _: &SelectToStartOfLargerSyntaxNode,
16752 window: &mut Window,
16753 cx: &mut Context<Self>,
16754 ) {
16755 self.select_to_syntax_nodes(window, cx, false);
16756 }
16757
16758 pub fn select_to_end_of_larger_syntax_node(
16759 &mut self,
16760 _: &SelectToEndOfLargerSyntaxNode,
16761 window: &mut Window,
16762 cx: &mut Context<Self>,
16763 ) {
16764 self.select_to_syntax_nodes(window, cx, true);
16765 }
16766
16767 fn select_to_syntax_nodes(
16768 &mut self,
16769 window: &mut Window,
16770 cx: &mut Context<Self>,
16771 move_to_end: bool,
16772 ) {
16773 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16774
16775 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16776 let buffer = self.buffer.read(cx).snapshot(cx);
16777 let old_selections = self.selections.all::<MultiBufferOffset>(&display_map);
16778
16779 let new_selections = old_selections
16780 .iter()
16781 .map(|selection| {
16782 let new_pos = self.find_syntax_node_boundary(
16783 selection.head(),
16784 move_to_end,
16785 &display_map,
16786 &buffer,
16787 );
16788
16789 let mut new_selection = selection.clone();
16790 new_selection.set_head(new_pos, SelectionGoal::None);
16791 new_selection
16792 })
16793 .collect::<Vec<_>>();
16794
16795 self.change_selections(Default::default(), window, cx, |s| {
16796 s.select(new_selections);
16797 });
16798 }
16799
16800 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
16801 if !EditorSettings::get_global(cx).gutter.runnables {
16802 self.clear_tasks();
16803 return Task::ready(());
16804 }
16805 let project = self.project().map(Entity::downgrade);
16806 let task_sources = self.lsp_task_sources(cx);
16807 let multi_buffer = self.buffer.downgrade();
16808 cx.spawn_in(window, async move |editor, cx| {
16809 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
16810 let Some(project) = project.and_then(|p| p.upgrade()) else {
16811 return;
16812 };
16813 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
16814 this.display_map.update(cx, |map, cx| map.snapshot(cx))
16815 }) else {
16816 return;
16817 };
16818
16819 let hide_runnables = project.update(cx, |project, _| project.is_via_collab());
16820 if hide_runnables {
16821 return;
16822 }
16823 let new_rows =
16824 cx.background_spawn({
16825 let snapshot = display_snapshot.clone();
16826 async move {
16827 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
16828 }
16829 })
16830 .await;
16831 let Ok(lsp_tasks) =
16832 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
16833 else {
16834 return;
16835 };
16836 let lsp_tasks = lsp_tasks.await;
16837
16838 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
16839 lsp_tasks
16840 .into_iter()
16841 .flat_map(|(kind, tasks)| {
16842 tasks.into_iter().filter_map(move |(location, task)| {
16843 Some((kind.clone(), location?, task))
16844 })
16845 })
16846 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
16847 let buffer = location.target.buffer;
16848 let buffer_snapshot = buffer.read(cx).snapshot();
16849 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
16850 |(excerpt_id, snapshot, _)| {
16851 if snapshot.remote_id() == buffer_snapshot.remote_id() {
16852 display_snapshot
16853 .buffer_snapshot()
16854 .anchor_in_excerpt(excerpt_id, location.target.range.start)
16855 } else {
16856 None
16857 }
16858 },
16859 );
16860 if let Some(offset) = offset {
16861 let task_buffer_range =
16862 location.target.range.to_point(&buffer_snapshot);
16863 let context_buffer_range =
16864 task_buffer_range.to_offset(&buffer_snapshot);
16865 let context_range = BufferOffset(context_buffer_range.start)
16866 ..BufferOffset(context_buffer_range.end);
16867
16868 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
16869 .or_insert_with(|| RunnableTasks {
16870 templates: Vec::new(),
16871 offset,
16872 column: task_buffer_range.start.column,
16873 extra_variables: HashMap::default(),
16874 context_range,
16875 })
16876 .templates
16877 .push((kind, task.original_task().clone()));
16878 }
16879
16880 acc
16881 })
16882 }) else {
16883 return;
16884 };
16885
16886 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
16887 buffer.language_settings(cx).tasks.prefer_lsp
16888 }) else {
16889 return;
16890 };
16891
16892 let rows = Self::runnable_rows(
16893 project,
16894 display_snapshot,
16895 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
16896 new_rows,
16897 cx.clone(),
16898 )
16899 .await;
16900 editor
16901 .update(cx, |editor, _| {
16902 editor.clear_tasks();
16903 for (key, mut value) in rows {
16904 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
16905 value.templates.extend(lsp_tasks.templates);
16906 }
16907
16908 editor.insert_tasks(key, value);
16909 }
16910 for (key, value) in lsp_tasks_by_rows {
16911 editor.insert_tasks(key, value);
16912 }
16913 })
16914 .ok();
16915 })
16916 }
16917 fn fetch_runnable_ranges(
16918 snapshot: &DisplaySnapshot,
16919 range: Range<Anchor>,
16920 ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
16921 snapshot.buffer_snapshot().runnable_ranges(range).collect()
16922 }
16923
16924 fn runnable_rows(
16925 project: Entity<Project>,
16926 snapshot: DisplaySnapshot,
16927 prefer_lsp: bool,
16928 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
16929 cx: AsyncWindowContext,
16930 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
16931 cx.spawn(async move |cx| {
16932 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
16933 for (run_range, mut runnable) in runnable_ranges {
16934 let Some(tasks) = cx
16935 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
16936 .ok()
16937 else {
16938 continue;
16939 };
16940 let mut tasks = tasks.await;
16941
16942 if prefer_lsp {
16943 tasks.retain(|(task_kind, _)| {
16944 !matches!(task_kind, TaskSourceKind::Language { .. })
16945 });
16946 }
16947 if tasks.is_empty() {
16948 continue;
16949 }
16950
16951 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
16952 let Some(row) = snapshot
16953 .buffer_snapshot()
16954 .buffer_line_for_row(MultiBufferRow(point.row))
16955 .map(|(_, range)| range.start.row)
16956 else {
16957 continue;
16958 };
16959
16960 let context_range =
16961 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
16962 runnable_rows.push((
16963 (runnable.buffer_id, row),
16964 RunnableTasks {
16965 templates: tasks,
16966 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
16967 context_range,
16968 column: point.column,
16969 extra_variables: runnable.extra_captures,
16970 },
16971 ));
16972 }
16973 runnable_rows
16974 })
16975 }
16976
16977 fn templates_with_tags(
16978 project: &Entity<Project>,
16979 runnable: &mut Runnable,
16980 cx: &mut App,
16981 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
16982 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
16983 let (worktree_id, file) = project
16984 .buffer_for_id(runnable.buffer, cx)
16985 .and_then(|buffer| buffer.read(cx).file())
16986 .map(|file| (file.worktree_id(cx), file.clone()))
16987 .unzip();
16988
16989 (
16990 project.task_store().read(cx).task_inventory().cloned(),
16991 worktree_id,
16992 file,
16993 )
16994 });
16995
16996 let tags = mem::take(&mut runnable.tags);
16997 let language = runnable.language.clone();
16998 cx.spawn(async move |cx| {
16999 let mut templates_with_tags = Vec::new();
17000 if let Some(inventory) = inventory {
17001 for RunnableTag(tag) in tags {
17002 let new_tasks = inventory.update(cx, |inventory, cx| {
17003 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
17004 });
17005 templates_with_tags.extend(new_tasks.await.into_iter().filter(
17006 move |(_, template)| {
17007 template.tags.iter().any(|source_tag| source_tag == &tag)
17008 },
17009 ));
17010 }
17011 }
17012 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
17013
17014 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
17015 // Strongest source wins; if we have worktree tag binding, prefer that to
17016 // global and language bindings;
17017 // if we have a global binding, prefer that to language binding.
17018 let first_mismatch = templates_with_tags
17019 .iter()
17020 .position(|(tag_source, _)| tag_source != leading_tag_source);
17021 if let Some(index) = first_mismatch {
17022 templates_with_tags.truncate(index);
17023 }
17024 }
17025
17026 templates_with_tags
17027 })
17028 }
17029
17030 pub fn move_to_enclosing_bracket(
17031 &mut self,
17032 _: &MoveToEnclosingBracket,
17033 window: &mut Window,
17034 cx: &mut Context<Self>,
17035 ) {
17036 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17037 self.change_selections(Default::default(), window, cx, |s| {
17038 s.move_offsets_with(|snapshot, selection| {
17039 let Some(enclosing_bracket_ranges) =
17040 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
17041 else {
17042 return;
17043 };
17044
17045 let mut best_length = usize::MAX;
17046 let mut best_inside = false;
17047 let mut best_in_bracket_range = false;
17048 let mut best_destination = None;
17049 for (open, close) in enclosing_bracket_ranges {
17050 let close = close.to_inclusive();
17051 let length = *close.end() - open.start;
17052 let inside = selection.start >= open.end && selection.end <= *close.start();
17053 let in_bracket_range = open.to_inclusive().contains(&selection.head())
17054 || close.contains(&selection.head());
17055
17056 // If best is next to a bracket and current isn't, skip
17057 if !in_bracket_range && best_in_bracket_range {
17058 continue;
17059 }
17060
17061 // Prefer smaller lengths unless best is inside and current isn't
17062 if length > best_length && (best_inside || !inside) {
17063 continue;
17064 }
17065
17066 best_length = length;
17067 best_inside = inside;
17068 best_in_bracket_range = in_bracket_range;
17069 best_destination = Some(
17070 if close.contains(&selection.start) && close.contains(&selection.end) {
17071 if inside { open.end } else { open.start }
17072 } else if inside {
17073 *close.start()
17074 } else {
17075 *close.end()
17076 },
17077 );
17078 }
17079
17080 if let Some(destination) = best_destination {
17081 selection.collapse_to(destination, SelectionGoal::None);
17082 }
17083 })
17084 });
17085 }
17086
17087 pub fn undo_selection(
17088 &mut self,
17089 _: &UndoSelection,
17090 window: &mut Window,
17091 cx: &mut Context<Self>,
17092 ) {
17093 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17094 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
17095 self.selection_history.mode = SelectionHistoryMode::Undoing;
17096 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17097 this.end_selection(window, cx);
17098 this.change_selections(
17099 SelectionEffects::scroll(Autoscroll::newest()),
17100 window,
17101 cx,
17102 |s| s.select_anchors(entry.selections.to_vec()),
17103 );
17104 });
17105 self.selection_history.mode = SelectionHistoryMode::Normal;
17106
17107 self.select_next_state = entry.select_next_state;
17108 self.select_prev_state = entry.select_prev_state;
17109 self.add_selections_state = entry.add_selections_state;
17110 }
17111 }
17112
17113 pub fn redo_selection(
17114 &mut self,
17115 _: &RedoSelection,
17116 window: &mut Window,
17117 cx: &mut Context<Self>,
17118 ) {
17119 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17120 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
17121 self.selection_history.mode = SelectionHistoryMode::Redoing;
17122 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17123 this.end_selection(window, cx);
17124 this.change_selections(
17125 SelectionEffects::scroll(Autoscroll::newest()),
17126 window,
17127 cx,
17128 |s| s.select_anchors(entry.selections.to_vec()),
17129 );
17130 });
17131 self.selection_history.mode = SelectionHistoryMode::Normal;
17132
17133 self.select_next_state = entry.select_next_state;
17134 self.select_prev_state = entry.select_prev_state;
17135 self.add_selections_state = entry.add_selections_state;
17136 }
17137 }
17138
17139 pub fn expand_excerpts(
17140 &mut self,
17141 action: &ExpandExcerpts,
17142 _: &mut Window,
17143 cx: &mut Context<Self>,
17144 ) {
17145 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
17146 }
17147
17148 pub fn expand_excerpts_down(
17149 &mut self,
17150 action: &ExpandExcerptsDown,
17151 _: &mut Window,
17152 cx: &mut Context<Self>,
17153 ) {
17154 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
17155 }
17156
17157 pub fn expand_excerpts_up(
17158 &mut self,
17159 action: &ExpandExcerptsUp,
17160 _: &mut Window,
17161 cx: &mut Context<Self>,
17162 ) {
17163 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
17164 }
17165
17166 pub fn expand_excerpts_for_direction(
17167 &mut self,
17168 lines: u32,
17169 direction: ExpandExcerptDirection,
17170 cx: &mut Context<Self>,
17171 ) {
17172 let selections = self.selections.disjoint_anchors_arc();
17173
17174 let lines = if lines == 0 {
17175 EditorSettings::get_global(cx).expand_excerpt_lines
17176 } else {
17177 lines
17178 };
17179
17180 let snapshot = self.buffer.read(cx).snapshot(cx);
17181 let mut excerpt_ids = selections
17182 .iter()
17183 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
17184 .collect::<Vec<_>>();
17185 excerpt_ids.sort();
17186 excerpt_ids.dedup();
17187
17188 if self.delegate_expand_excerpts {
17189 cx.emit(EditorEvent::ExpandExcerptsRequested {
17190 excerpt_ids,
17191 lines,
17192 direction,
17193 });
17194 return;
17195 }
17196
17197 self.buffer.update(cx, |buffer, cx| {
17198 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
17199 })
17200 }
17201
17202 pub fn expand_excerpt(
17203 &mut self,
17204 excerpt: ExcerptId,
17205 direction: ExpandExcerptDirection,
17206 window: &mut Window,
17207 cx: &mut Context<Self>,
17208 ) {
17209 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
17210
17211 if self.delegate_expand_excerpts {
17212 cx.emit(EditorEvent::ExpandExcerptsRequested {
17213 excerpt_ids: vec![excerpt],
17214 lines: lines_to_expand,
17215 direction,
17216 });
17217 return;
17218 }
17219
17220 let current_scroll_position = self.scroll_position(cx);
17221 let mut scroll = None;
17222
17223 if direction == ExpandExcerptDirection::Down {
17224 let multi_buffer = self.buffer.read(cx);
17225 let snapshot = multi_buffer.snapshot(cx);
17226 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
17227 && let Some(buffer) = multi_buffer.buffer(buffer_id)
17228 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
17229 {
17230 let buffer_snapshot = buffer.read(cx).snapshot();
17231 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
17232 let last_row = buffer_snapshot.max_point().row;
17233 let lines_below = last_row.saturating_sub(excerpt_end_row);
17234 if lines_below >= lines_to_expand {
17235 scroll = Some(
17236 current_scroll_position
17237 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
17238 );
17239 }
17240 }
17241 }
17242 if direction == ExpandExcerptDirection::Up
17243 && self
17244 .buffer
17245 .read(cx)
17246 .snapshot(cx)
17247 .excerpt_before(excerpt)
17248 .is_none()
17249 {
17250 scroll = Some(current_scroll_position);
17251 }
17252
17253 self.buffer.update(cx, |buffer, cx| {
17254 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
17255 });
17256
17257 if let Some(new_scroll_position) = scroll {
17258 self.set_scroll_position(new_scroll_position, window, cx);
17259 }
17260 }
17261
17262 pub fn go_to_singleton_buffer_point(
17263 &mut self,
17264 point: Point,
17265 window: &mut Window,
17266 cx: &mut Context<Self>,
17267 ) {
17268 self.go_to_singleton_buffer_range(point..point, window, cx);
17269 }
17270
17271 pub fn go_to_singleton_buffer_range(
17272 &mut self,
17273 range: Range<Point>,
17274 window: &mut Window,
17275 cx: &mut Context<Self>,
17276 ) {
17277 let multibuffer = self.buffer().read(cx);
17278 let Some(buffer) = multibuffer.as_singleton() else {
17279 return;
17280 };
17281 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
17282 return;
17283 };
17284 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
17285 return;
17286 };
17287 self.change_selections(
17288 SelectionEffects::default().nav_history(true),
17289 window,
17290 cx,
17291 |s| s.select_anchor_ranges([start..end]),
17292 );
17293 }
17294
17295 pub fn go_to_diagnostic(
17296 &mut self,
17297 action: &GoToDiagnostic,
17298 window: &mut Window,
17299 cx: &mut Context<Self>,
17300 ) {
17301 if !self.diagnostics_enabled() {
17302 return;
17303 }
17304 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17305 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
17306 }
17307
17308 pub fn go_to_prev_diagnostic(
17309 &mut self,
17310 action: &GoToPreviousDiagnostic,
17311 window: &mut Window,
17312 cx: &mut Context<Self>,
17313 ) {
17314 if !self.diagnostics_enabled() {
17315 return;
17316 }
17317 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17318 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
17319 }
17320
17321 pub fn go_to_diagnostic_impl(
17322 &mut self,
17323 direction: Direction,
17324 severity: GoToDiagnosticSeverityFilter,
17325 window: &mut Window,
17326 cx: &mut Context<Self>,
17327 ) {
17328 let buffer = self.buffer.read(cx).snapshot(cx);
17329 let selection = self
17330 .selections
17331 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
17332
17333 let mut active_group_id = None;
17334 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
17335 && active_group.active_range.start.to_offset(&buffer) == selection.start
17336 {
17337 active_group_id = Some(active_group.group_id);
17338 }
17339
17340 fn filtered<'a>(
17341 severity: GoToDiagnosticSeverityFilter,
17342 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
17343 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
17344 diagnostics
17345 .filter(move |entry| severity.matches(entry.diagnostic.severity))
17346 .filter(|entry| entry.range.start != entry.range.end)
17347 .filter(|entry| !entry.diagnostic.is_unnecessary)
17348 }
17349
17350 let before = filtered(
17351 severity,
17352 buffer
17353 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
17354 .filter(|entry| entry.range.start <= selection.start),
17355 );
17356 let after = filtered(
17357 severity,
17358 buffer
17359 .diagnostics_in_range(selection.start..buffer.len())
17360 .filter(|entry| entry.range.start >= selection.start),
17361 );
17362
17363 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
17364 if direction == Direction::Prev {
17365 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
17366 {
17367 for diagnostic in prev_diagnostics.into_iter().rev() {
17368 if diagnostic.range.start != selection.start
17369 || active_group_id
17370 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
17371 {
17372 found = Some(diagnostic);
17373 break 'outer;
17374 }
17375 }
17376 }
17377 } else {
17378 for diagnostic in after.chain(before) {
17379 if diagnostic.range.start != selection.start
17380 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
17381 {
17382 found = Some(diagnostic);
17383 break;
17384 }
17385 }
17386 }
17387 let Some(next_diagnostic) = found else {
17388 return;
17389 };
17390
17391 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
17392 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
17393 return;
17394 };
17395 let snapshot = self.snapshot(window, cx);
17396 if snapshot.intersects_fold(next_diagnostic.range.start) {
17397 self.unfold_ranges(
17398 std::slice::from_ref(&next_diagnostic.range),
17399 true,
17400 false,
17401 cx,
17402 );
17403 }
17404 self.change_selections(Default::default(), window, cx, |s| {
17405 s.select_ranges(vec![
17406 next_diagnostic.range.start..next_diagnostic.range.start,
17407 ])
17408 });
17409 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
17410 self.refresh_edit_prediction(false, true, window, cx);
17411 }
17412
17413 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
17414 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17415 let snapshot = self.snapshot(window, cx);
17416 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
17417 self.go_to_hunk_before_or_after_position(
17418 &snapshot,
17419 selection.head(),
17420 Direction::Next,
17421 window,
17422 cx,
17423 );
17424 }
17425
17426 pub fn go_to_hunk_before_or_after_position(
17427 &mut self,
17428 snapshot: &EditorSnapshot,
17429 position: Point,
17430 direction: Direction,
17431 window: &mut Window,
17432 cx: &mut Context<Editor>,
17433 ) {
17434 let row = if direction == Direction::Next {
17435 self.hunk_after_position(snapshot, position)
17436 .map(|hunk| hunk.row_range.start)
17437 } else {
17438 self.hunk_before_position(snapshot, position)
17439 };
17440
17441 if let Some(row) = row {
17442 let destination = Point::new(row.0, 0);
17443 let autoscroll = Autoscroll::center();
17444
17445 self.unfold_ranges(&[destination..destination], false, false, cx);
17446 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17447 s.select_ranges([destination..destination]);
17448 });
17449 }
17450 }
17451
17452 fn hunk_after_position(
17453 &mut self,
17454 snapshot: &EditorSnapshot,
17455 position: Point,
17456 ) -> Option<MultiBufferDiffHunk> {
17457 snapshot
17458 .buffer_snapshot()
17459 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
17460 .find(|hunk| hunk.row_range.start.0 > position.row)
17461 .or_else(|| {
17462 snapshot
17463 .buffer_snapshot()
17464 .diff_hunks_in_range(Point::zero()..position)
17465 .find(|hunk| hunk.row_range.end.0 < position.row)
17466 })
17467 }
17468
17469 fn go_to_prev_hunk(
17470 &mut self,
17471 _: &GoToPreviousHunk,
17472 window: &mut Window,
17473 cx: &mut Context<Self>,
17474 ) {
17475 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17476 let snapshot = self.snapshot(window, cx);
17477 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
17478 self.go_to_hunk_before_or_after_position(
17479 &snapshot,
17480 selection.head(),
17481 Direction::Prev,
17482 window,
17483 cx,
17484 );
17485 }
17486
17487 fn hunk_before_position(
17488 &mut self,
17489 snapshot: &EditorSnapshot,
17490 position: Point,
17491 ) -> Option<MultiBufferRow> {
17492 snapshot
17493 .buffer_snapshot()
17494 .diff_hunk_before(position)
17495 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
17496 }
17497
17498 fn go_to_next_change(
17499 &mut self,
17500 _: &GoToNextChange,
17501 window: &mut Window,
17502 cx: &mut Context<Self>,
17503 ) {
17504 if let Some(selections) = self
17505 .change_list
17506 .next_change(1, Direction::Next)
17507 .map(|s| s.to_vec())
17508 {
17509 self.change_selections(Default::default(), window, cx, |s| {
17510 let map = s.display_snapshot();
17511 s.select_display_ranges(selections.iter().map(|a| {
17512 let point = a.to_display_point(&map);
17513 point..point
17514 }))
17515 })
17516 }
17517 }
17518
17519 fn go_to_previous_change(
17520 &mut self,
17521 _: &GoToPreviousChange,
17522 window: &mut Window,
17523 cx: &mut Context<Self>,
17524 ) {
17525 if let Some(selections) = self
17526 .change_list
17527 .next_change(1, Direction::Prev)
17528 .map(|s| s.to_vec())
17529 {
17530 self.change_selections(Default::default(), window, cx, |s| {
17531 let map = s.display_snapshot();
17532 s.select_display_ranges(selections.iter().map(|a| {
17533 let point = a.to_display_point(&map);
17534 point..point
17535 }))
17536 })
17537 }
17538 }
17539
17540 pub fn go_to_next_document_highlight(
17541 &mut self,
17542 _: &GoToNextDocumentHighlight,
17543 window: &mut Window,
17544 cx: &mut Context<Self>,
17545 ) {
17546 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
17547 }
17548
17549 pub fn go_to_prev_document_highlight(
17550 &mut self,
17551 _: &GoToPreviousDocumentHighlight,
17552 window: &mut Window,
17553 cx: &mut Context<Self>,
17554 ) {
17555 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
17556 }
17557
17558 pub fn go_to_document_highlight_before_or_after_position(
17559 &mut self,
17560 direction: Direction,
17561 window: &mut Window,
17562 cx: &mut Context<Editor>,
17563 ) {
17564 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17565 let snapshot = self.snapshot(window, cx);
17566 let buffer = &snapshot.buffer_snapshot();
17567 let position = self
17568 .selections
17569 .newest::<Point>(&snapshot.display_snapshot)
17570 .head();
17571 let anchor_position = buffer.anchor_after(position);
17572
17573 // Get all document highlights (both read and write)
17574 let mut all_highlights = Vec::new();
17575
17576 if let Some((_, read_highlights)) = self
17577 .background_highlights
17578 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
17579 {
17580 all_highlights.extend(read_highlights.iter());
17581 }
17582
17583 if let Some((_, write_highlights)) = self
17584 .background_highlights
17585 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
17586 {
17587 all_highlights.extend(write_highlights.iter());
17588 }
17589
17590 if all_highlights.is_empty() {
17591 return;
17592 }
17593
17594 // Sort highlights by position
17595 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
17596
17597 let target_highlight = match direction {
17598 Direction::Next => {
17599 // Find the first highlight after the current position
17600 all_highlights
17601 .iter()
17602 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
17603 }
17604 Direction::Prev => {
17605 // Find the last highlight before the current position
17606 all_highlights
17607 .iter()
17608 .rev()
17609 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
17610 }
17611 };
17612
17613 if let Some(highlight) = target_highlight {
17614 let destination = highlight.start.to_point(buffer);
17615 let autoscroll = Autoscroll::center();
17616
17617 self.unfold_ranges(&[destination..destination], false, false, cx);
17618 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17619 s.select_ranges([destination..destination]);
17620 });
17621 }
17622 }
17623
17624 fn go_to_line<T: 'static>(
17625 &mut self,
17626 position: Anchor,
17627 highlight_color: Option<Hsla>,
17628 window: &mut Window,
17629 cx: &mut Context<Self>,
17630 ) {
17631 let snapshot = self.snapshot(window, cx).display_snapshot;
17632 let position = position.to_point(&snapshot.buffer_snapshot());
17633 let start = snapshot
17634 .buffer_snapshot()
17635 .clip_point(Point::new(position.row, 0), Bias::Left);
17636 let end = start + Point::new(1, 0);
17637 let start = snapshot.buffer_snapshot().anchor_before(start);
17638 let end = snapshot.buffer_snapshot().anchor_before(end);
17639
17640 self.highlight_rows::<T>(
17641 start..end,
17642 highlight_color
17643 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
17644 Default::default(),
17645 cx,
17646 );
17647
17648 if self.buffer.read(cx).is_singleton() {
17649 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
17650 }
17651 }
17652
17653 pub fn go_to_definition(
17654 &mut self,
17655 _: &GoToDefinition,
17656 window: &mut Window,
17657 cx: &mut Context<Self>,
17658 ) -> Task<Result<Navigated>> {
17659 let definition =
17660 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
17661 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
17662 cx.spawn_in(window, async move |editor, cx| {
17663 if definition.await? == Navigated::Yes {
17664 return Ok(Navigated::Yes);
17665 }
17666 match fallback_strategy {
17667 GoToDefinitionFallback::None => Ok(Navigated::No),
17668 GoToDefinitionFallback::FindAllReferences => {
17669 match editor.update_in(cx, |editor, window, cx| {
17670 editor.find_all_references(&FindAllReferences::default(), window, cx)
17671 })? {
17672 Some(references) => references.await,
17673 None => Ok(Navigated::No),
17674 }
17675 }
17676 }
17677 })
17678 }
17679
17680 pub fn go_to_declaration(
17681 &mut self,
17682 _: &GoToDeclaration,
17683 window: &mut Window,
17684 cx: &mut Context<Self>,
17685 ) -> Task<Result<Navigated>> {
17686 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
17687 }
17688
17689 pub fn go_to_declaration_split(
17690 &mut self,
17691 _: &GoToDeclaration,
17692 window: &mut Window,
17693 cx: &mut Context<Self>,
17694 ) -> Task<Result<Navigated>> {
17695 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
17696 }
17697
17698 pub fn go_to_implementation(
17699 &mut self,
17700 _: &GoToImplementation,
17701 window: &mut Window,
17702 cx: &mut Context<Self>,
17703 ) -> Task<Result<Navigated>> {
17704 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
17705 }
17706
17707 pub fn go_to_implementation_split(
17708 &mut self,
17709 _: &GoToImplementationSplit,
17710 window: &mut Window,
17711 cx: &mut Context<Self>,
17712 ) -> Task<Result<Navigated>> {
17713 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
17714 }
17715
17716 pub fn go_to_type_definition(
17717 &mut self,
17718 _: &GoToTypeDefinition,
17719 window: &mut Window,
17720 cx: &mut Context<Self>,
17721 ) -> Task<Result<Navigated>> {
17722 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
17723 }
17724
17725 pub fn go_to_definition_split(
17726 &mut self,
17727 _: &GoToDefinitionSplit,
17728 window: &mut Window,
17729 cx: &mut Context<Self>,
17730 ) -> Task<Result<Navigated>> {
17731 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
17732 }
17733
17734 pub fn go_to_type_definition_split(
17735 &mut self,
17736 _: &GoToTypeDefinitionSplit,
17737 window: &mut Window,
17738 cx: &mut Context<Self>,
17739 ) -> Task<Result<Navigated>> {
17740 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
17741 }
17742
17743 fn go_to_definition_of_kind(
17744 &mut self,
17745 kind: GotoDefinitionKind,
17746 split: bool,
17747 window: &mut Window,
17748 cx: &mut Context<Self>,
17749 ) -> Task<Result<Navigated>> {
17750 let Some(provider) = self.semantics_provider.clone() else {
17751 return Task::ready(Ok(Navigated::No));
17752 };
17753 let head = self
17754 .selections
17755 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
17756 .head();
17757 let buffer = self.buffer.read(cx);
17758 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
17759 return Task::ready(Ok(Navigated::No));
17760 };
17761 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
17762 return Task::ready(Ok(Navigated::No));
17763 };
17764
17765 let nav_entry = self.navigation_entry(self.selections.newest_anchor().head(), cx);
17766
17767 cx.spawn_in(window, async move |editor, cx| {
17768 let Some(definitions) = definitions.await? else {
17769 return Ok(Navigated::No);
17770 };
17771 let navigated = editor
17772 .update_in(cx, |editor, window, cx| {
17773 editor.navigate_to_hover_links(
17774 Some(kind),
17775 definitions
17776 .into_iter()
17777 .filter(|location| {
17778 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
17779 })
17780 .map(HoverLink::Text)
17781 .collect::<Vec<_>>(),
17782 nav_entry,
17783 split,
17784 window,
17785 cx,
17786 )
17787 })?
17788 .await?;
17789 anyhow::Ok(navigated)
17790 })
17791 }
17792
17793 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
17794 let selection = self.selections.newest_anchor();
17795 let head = selection.head();
17796 let tail = selection.tail();
17797
17798 let Some((buffer, start_position)) =
17799 self.buffer.read(cx).text_anchor_for_position(head, cx)
17800 else {
17801 return;
17802 };
17803
17804 let end_position = if head != tail {
17805 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
17806 return;
17807 };
17808 Some(pos)
17809 } else {
17810 None
17811 };
17812
17813 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
17814 let url = if let Some(end_pos) = end_position {
17815 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
17816 } else {
17817 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
17818 };
17819
17820 if let Some(url) = url {
17821 cx.update(|window, cx| {
17822 if parse_zed_link(&url, cx).is_some() {
17823 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17824 } else {
17825 cx.open_url(&url);
17826 }
17827 })?;
17828 }
17829
17830 anyhow::Ok(())
17831 });
17832
17833 url_finder.detach();
17834 }
17835
17836 pub fn open_selected_filename(
17837 &mut self,
17838 _: &OpenSelectedFilename,
17839 window: &mut Window,
17840 cx: &mut Context<Self>,
17841 ) {
17842 let Some(workspace) = self.workspace() else {
17843 return;
17844 };
17845
17846 let position = self.selections.newest_anchor().head();
17847
17848 let Some((buffer, buffer_position)) =
17849 self.buffer.read(cx).text_anchor_for_position(position, cx)
17850 else {
17851 return;
17852 };
17853
17854 let project = self.project.clone();
17855
17856 cx.spawn_in(window, async move |_, cx| {
17857 let result = find_file(&buffer, project, buffer_position, cx).await;
17858
17859 if let Some((_, path)) = result {
17860 workspace
17861 .update_in(cx, |workspace, window, cx| {
17862 workspace.open_resolved_path(path, window, cx)
17863 })?
17864 .await?;
17865 }
17866 anyhow::Ok(())
17867 })
17868 .detach();
17869 }
17870
17871 pub(crate) fn navigate_to_hover_links(
17872 &mut self,
17873 kind: Option<GotoDefinitionKind>,
17874 definitions: Vec<HoverLink>,
17875 origin: Option<NavigationEntry>,
17876 split: bool,
17877 window: &mut Window,
17878 cx: &mut Context<Editor>,
17879 ) -> Task<Result<Navigated>> {
17880 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
17881 let mut first_url_or_file = None;
17882 let definitions: Vec<_> = definitions
17883 .into_iter()
17884 .filter_map(|def| match def {
17885 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
17886 HoverLink::InlayHint(lsp_location, server_id) => {
17887 let computation =
17888 self.compute_target_location(lsp_location, server_id, window, cx);
17889 Some(cx.background_spawn(computation))
17890 }
17891 HoverLink::Url(url) => {
17892 first_url_or_file = Some(Either::Left(url));
17893 None
17894 }
17895 HoverLink::File(path) => {
17896 first_url_or_file = Some(Either::Right(path));
17897 None
17898 }
17899 })
17900 .collect();
17901
17902 let workspace = self.workspace();
17903
17904 cx.spawn_in(window, async move |editor, cx| {
17905 let locations: Vec<Location> = future::join_all(definitions)
17906 .await
17907 .into_iter()
17908 .filter_map(|location| location.transpose())
17909 .collect::<Result<_>>()
17910 .context("location tasks")?;
17911 let mut locations = cx.update(|_, cx| {
17912 locations
17913 .into_iter()
17914 .map(|location| {
17915 let buffer = location.buffer.read(cx);
17916 (location.buffer, location.range.to_point(buffer))
17917 })
17918 .into_group_map()
17919 })?;
17920 let mut num_locations = 0;
17921 for ranges in locations.values_mut() {
17922 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17923 ranges.dedup();
17924 num_locations += ranges.len();
17925 }
17926
17927 if num_locations > 1 {
17928 let tab_kind = match kind {
17929 Some(GotoDefinitionKind::Implementation) => "Implementations",
17930 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
17931 Some(GotoDefinitionKind::Declaration) => "Declarations",
17932 Some(GotoDefinitionKind::Type) => "Types",
17933 };
17934 let title = editor
17935 .update_in(cx, |_, _, cx| {
17936 let target = locations
17937 .iter()
17938 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17939 .map(|(buffer, location)| {
17940 buffer
17941 .read(cx)
17942 .text_for_range(location.clone())
17943 .collect::<String>()
17944 })
17945 .filter(|text| !text.contains('\n'))
17946 .unique()
17947 .take(3)
17948 .join(", ");
17949 if target.is_empty() {
17950 tab_kind.to_owned()
17951 } else {
17952 format!("{tab_kind} for {target}")
17953 }
17954 })
17955 .context("buffer title")?;
17956
17957 let Some(workspace) = workspace else {
17958 return Ok(Navigated::No);
17959 };
17960
17961 let opened = workspace
17962 .update_in(cx, |workspace, window, cx| {
17963 let allow_preview = PreviewTabsSettings::get_global(cx)
17964 .enable_preview_multibuffer_from_code_navigation;
17965 if let Some((target_editor, target_pane)) =
17966 Self::open_locations_in_multibuffer(
17967 workspace,
17968 locations,
17969 title,
17970 split,
17971 allow_preview,
17972 MultibufferSelectionMode::First,
17973 window,
17974 cx,
17975 )
17976 {
17977 // We create our own nav history instead of using
17978 // `target_editor.nav_history` because `nav_history`
17979 // seems to be populated asynchronously when an item
17980 // is added to a pane
17981 let mut nav_history = target_pane
17982 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
17983 target_editor.update(cx, |editor, cx| {
17984 let nav_data = editor
17985 .navigation_data(editor.selections.newest_anchor().head(), cx);
17986 let target =
17987 Some(nav_history.navigation_entry(Some(
17988 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
17989 )));
17990 nav_history.push_tag(origin, target);
17991 })
17992 }
17993 })
17994 .is_ok();
17995
17996 anyhow::Ok(Navigated::from_bool(opened))
17997 } else if num_locations == 0 {
17998 // If there is one url or file, open it directly
17999 match first_url_or_file {
18000 Some(Either::Left(url)) => {
18001 cx.update(|window, cx| {
18002 if parse_zed_link(&url, cx).is_some() {
18003 window
18004 .dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
18005 } else {
18006 cx.open_url(&url);
18007 }
18008 })?;
18009 Ok(Navigated::Yes)
18010 }
18011 Some(Either::Right(path)) => {
18012 // TODO(andrew): respect preview tab settings
18013 // `enable_keep_preview_on_code_navigation` and
18014 // `enable_preview_file_from_code_navigation`
18015 let Some(workspace) = workspace else {
18016 return Ok(Navigated::No);
18017 };
18018 workspace
18019 .update_in(cx, |workspace, window, cx| {
18020 workspace.open_resolved_path(path, window, cx)
18021 })?
18022 .await?;
18023 Ok(Navigated::Yes)
18024 }
18025 None => Ok(Navigated::No),
18026 }
18027 } else {
18028 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18029 let target_range = target_ranges.first().unwrap().clone();
18030
18031 editor.update_in(cx, |editor, window, cx| {
18032 let range = editor.range_for_match(&target_range);
18033 let range = collapse_multiline_range(range);
18034
18035 if !split
18036 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
18037 {
18038 editor.go_to_singleton_buffer_range(range, window, cx);
18039
18040 let target =
18041 editor.navigation_entry(editor.selections.newest_anchor().head(), cx);
18042 if let Some(mut nav_history) = editor.nav_history.clone() {
18043 nav_history.push_tag(origin, target);
18044 }
18045 } else {
18046 let Some(workspace) = workspace else {
18047 return Navigated::No;
18048 };
18049 let pane = workspace.read(cx).active_pane().clone();
18050 window.defer(cx, move |window, cx| {
18051 let (target_editor, target_pane): (Entity<Self>, Entity<Pane>) =
18052 workspace.update(cx, |workspace, cx| {
18053 let pane = if split {
18054 workspace.adjacent_pane(window, cx)
18055 } else {
18056 workspace.active_pane().clone()
18057 };
18058
18059 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18060 let keep_old_preview = preview_tabs_settings
18061 .enable_keep_preview_on_code_navigation;
18062 let allow_new_preview = preview_tabs_settings
18063 .enable_preview_file_from_code_navigation;
18064
18065 let editor = workspace.open_project_item(
18066 pane.clone(),
18067 target_buffer.clone(),
18068 true,
18069 true,
18070 keep_old_preview,
18071 allow_new_preview,
18072 window,
18073 cx,
18074 );
18075 (editor, pane)
18076 });
18077 // We create our own nav history instead of using
18078 // `target_editor.nav_history` because `nav_history`
18079 // seems to be populated asynchronously when an item
18080 // is added to a pane
18081 let mut nav_history = target_pane
18082 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18083 target_editor.update(cx, |target_editor, cx| {
18084 // When selecting a definition in a different buffer, disable the nav history
18085 // to avoid creating a history entry at the previous cursor location.
18086 pane.update(cx, |pane, _| pane.disable_history());
18087 target_editor.go_to_singleton_buffer_range(range, window, cx);
18088
18089 let nav_data = target_editor.navigation_data(
18090 target_editor.selections.newest_anchor().head(),
18091 cx,
18092 );
18093 let target =
18094 Some(nav_history.navigation_entry(Some(
18095 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18096 )));
18097 nav_history.push_tag(origin, target);
18098 pane.update(cx, |pane, _| pane.enable_history());
18099 });
18100 });
18101 }
18102 Navigated::Yes
18103 })
18104 }
18105 })
18106 }
18107
18108 fn compute_target_location(
18109 &self,
18110 lsp_location: lsp::Location,
18111 server_id: LanguageServerId,
18112 window: &mut Window,
18113 cx: &mut Context<Self>,
18114 ) -> Task<anyhow::Result<Option<Location>>> {
18115 let Some(project) = self.project.clone() else {
18116 return Task::ready(Ok(None));
18117 };
18118
18119 cx.spawn_in(window, async move |editor, cx| {
18120 let location_task = editor.update(cx, |_, cx| {
18121 project.update(cx, |project, cx| {
18122 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
18123 })
18124 })?;
18125 let location = Some({
18126 let target_buffer_handle = location_task.await.context("open local buffer")?;
18127 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
18128 let target_start = target_buffer
18129 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
18130 let target_end = target_buffer
18131 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
18132 target_buffer.anchor_after(target_start)
18133 ..target_buffer.anchor_before(target_end)
18134 });
18135 Location {
18136 buffer: target_buffer_handle,
18137 range,
18138 }
18139 });
18140 Ok(location)
18141 })
18142 }
18143
18144 fn go_to_next_reference(
18145 &mut self,
18146 _: &GoToNextReference,
18147 window: &mut Window,
18148 cx: &mut Context<Self>,
18149 ) {
18150 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
18151 if let Some(task) = task {
18152 task.detach();
18153 };
18154 }
18155
18156 fn go_to_prev_reference(
18157 &mut self,
18158 _: &GoToPreviousReference,
18159 window: &mut Window,
18160 cx: &mut Context<Self>,
18161 ) {
18162 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
18163 if let Some(task) = task {
18164 task.detach();
18165 };
18166 }
18167
18168 pub fn go_to_reference_before_or_after_position(
18169 &mut self,
18170 direction: Direction,
18171 count: usize,
18172 window: &mut Window,
18173 cx: &mut Context<Self>,
18174 ) -> Option<Task<Result<()>>> {
18175 let selection = self.selections.newest_anchor();
18176 let head = selection.head();
18177
18178 let multi_buffer = self.buffer.read(cx);
18179
18180 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
18181 let workspace = self.workspace()?;
18182 let project = workspace.read(cx).project().clone();
18183 let references =
18184 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
18185 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
18186 let Some(locations) = references.await? else {
18187 return Ok(());
18188 };
18189
18190 if locations.is_empty() {
18191 // totally normal - the cursor may be on something which is not
18192 // a symbol (e.g. a keyword)
18193 log::info!("no references found under cursor");
18194 return Ok(());
18195 }
18196
18197 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
18198
18199 let (locations, current_location_index) =
18200 multi_buffer.update(cx, |multi_buffer, cx| {
18201 let mut locations = locations
18202 .into_iter()
18203 .filter_map(|loc| {
18204 let start = multi_buffer.buffer_anchor_to_anchor(
18205 &loc.buffer,
18206 loc.range.start,
18207 cx,
18208 )?;
18209 let end = multi_buffer.buffer_anchor_to_anchor(
18210 &loc.buffer,
18211 loc.range.end,
18212 cx,
18213 )?;
18214 Some(start..end)
18215 })
18216 .collect::<Vec<_>>();
18217
18218 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18219 // There is an O(n) implementation, but given this list will be
18220 // small (usually <100 items), the extra O(log(n)) factor isn't
18221 // worth the (surprisingly large amount of) extra complexity.
18222 locations
18223 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
18224
18225 let head_offset = head.to_offset(&multi_buffer_snapshot);
18226
18227 let current_location_index = locations.iter().position(|loc| {
18228 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
18229 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
18230 });
18231
18232 (locations, current_location_index)
18233 });
18234
18235 let Some(current_location_index) = current_location_index else {
18236 // This indicates something has gone wrong, because we already
18237 // handle the "no references" case above
18238 log::error!(
18239 "failed to find current reference under cursor. Total references: {}",
18240 locations.len()
18241 );
18242 return Ok(());
18243 };
18244
18245 let destination_location_index = match direction {
18246 Direction::Next => (current_location_index + count) % locations.len(),
18247 Direction::Prev => {
18248 (current_location_index + locations.len() - count % locations.len())
18249 % locations.len()
18250 }
18251 };
18252
18253 // TODO(cameron): is this needed?
18254 // the thinking is to avoid "jumping to the current location" (avoid
18255 // polluting "jumplist" in vim terms)
18256 if current_location_index == destination_location_index {
18257 return Ok(());
18258 }
18259
18260 let Range { start, end } = locations[destination_location_index];
18261
18262 editor.update_in(cx, |editor, window, cx| {
18263 let effects = SelectionEffects::default();
18264
18265 editor.unfold_ranges(&[start..end], false, false, cx);
18266 editor.change_selections(effects, window, cx, |s| {
18267 s.select_ranges([start..start]);
18268 });
18269 })?;
18270
18271 Ok(())
18272 }))
18273 }
18274
18275 pub fn find_all_references(
18276 &mut self,
18277 action: &FindAllReferences,
18278 window: &mut Window,
18279 cx: &mut Context<Self>,
18280 ) -> Option<Task<Result<Navigated>>> {
18281 let always_open_multibuffer = action.always_open_multibuffer;
18282 let selection = self.selections.newest_anchor();
18283 let multi_buffer = self.buffer.read(cx);
18284 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18285 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
18286 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
18287 let head = selection_offset.head();
18288
18289 let head_anchor = multi_buffer_snapshot.anchor_at(
18290 head,
18291 if head < selection_offset.tail() {
18292 Bias::Right
18293 } else {
18294 Bias::Left
18295 },
18296 );
18297
18298 match self
18299 .find_all_references_task_sources
18300 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18301 {
18302 Ok(_) => {
18303 log::info!(
18304 "Ignoring repeated FindAllReferences invocation with the position of already running task"
18305 );
18306 return None;
18307 }
18308 Err(i) => {
18309 self.find_all_references_task_sources.insert(i, head_anchor);
18310 }
18311 }
18312
18313 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
18314 let workspace = self.workspace()?;
18315 let project = workspace.read(cx).project().clone();
18316 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
18317 Some(cx.spawn_in(window, async move |editor, cx| {
18318 let _cleanup = cx.on_drop(&editor, move |editor, _| {
18319 if let Ok(i) = editor
18320 .find_all_references_task_sources
18321 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18322 {
18323 editor.find_all_references_task_sources.remove(i);
18324 }
18325 });
18326
18327 let Some(locations) = references.await? else {
18328 return anyhow::Ok(Navigated::No);
18329 };
18330 let mut locations = cx.update(|_, cx| {
18331 locations
18332 .into_iter()
18333 .map(|location| {
18334 let buffer = location.buffer.read(cx);
18335 (location.buffer, location.range.to_point(buffer))
18336 })
18337 // if special-casing the single-match case, remove ranges
18338 // that intersect current selection
18339 .filter(|(location_buffer, location)| {
18340 if always_open_multibuffer || &buffer != location_buffer {
18341 return true;
18342 }
18343
18344 !location.contains_inclusive(&selection_point.range())
18345 })
18346 .into_group_map()
18347 })?;
18348 if locations.is_empty() {
18349 return anyhow::Ok(Navigated::No);
18350 }
18351 for ranges in locations.values_mut() {
18352 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18353 ranges.dedup();
18354 }
18355 let mut num_locations = 0;
18356 for ranges in locations.values_mut() {
18357 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18358 ranges.dedup();
18359 num_locations += ranges.len();
18360 }
18361
18362 if num_locations == 1 && !always_open_multibuffer {
18363 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18364 let target_range = target_ranges.first().unwrap().clone();
18365
18366 return editor.update_in(cx, |editor, window, cx| {
18367 let range = target_range.to_point(target_buffer.read(cx));
18368 let range = editor.range_for_match(&range);
18369 let range = range.start..range.start;
18370
18371 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
18372 editor.go_to_singleton_buffer_range(range, window, cx);
18373 } else {
18374 let pane = workspace.read(cx).active_pane().clone();
18375 window.defer(cx, move |window, cx| {
18376 let target_editor: Entity<Self> =
18377 workspace.update(cx, |workspace, cx| {
18378 let pane = workspace.active_pane().clone();
18379
18380 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18381 let keep_old_preview = preview_tabs_settings
18382 .enable_keep_preview_on_code_navigation;
18383 let allow_new_preview = preview_tabs_settings
18384 .enable_preview_file_from_code_navigation;
18385
18386 workspace.open_project_item(
18387 pane,
18388 target_buffer.clone(),
18389 true,
18390 true,
18391 keep_old_preview,
18392 allow_new_preview,
18393 window,
18394 cx,
18395 )
18396 });
18397 target_editor.update(cx, |target_editor, cx| {
18398 // When selecting a definition in a different buffer, disable the nav history
18399 // to avoid creating a history entry at the previous cursor location.
18400 pane.update(cx, |pane, _| pane.disable_history());
18401 target_editor.go_to_singleton_buffer_range(range, window, cx);
18402 pane.update(cx, |pane, _| pane.enable_history());
18403 });
18404 });
18405 }
18406 Navigated::No
18407 });
18408 }
18409
18410 workspace.update_in(cx, |workspace, window, cx| {
18411 let target = locations
18412 .iter()
18413 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18414 .map(|(buffer, location)| {
18415 buffer
18416 .read(cx)
18417 .text_for_range(location.clone())
18418 .collect::<String>()
18419 })
18420 .filter(|text| !text.contains('\n'))
18421 .unique()
18422 .take(3)
18423 .join(", ");
18424 let title = if target.is_empty() {
18425 "References".to_owned()
18426 } else {
18427 format!("References to {target}")
18428 };
18429 let allow_preview = PreviewTabsSettings::get_global(cx)
18430 .enable_preview_multibuffer_from_code_navigation;
18431 Self::open_locations_in_multibuffer(
18432 workspace,
18433 locations,
18434 title,
18435 false,
18436 allow_preview,
18437 MultibufferSelectionMode::First,
18438 window,
18439 cx,
18440 );
18441 Navigated::Yes
18442 })
18443 }))
18444 }
18445
18446 /// Opens a multibuffer with the given project locations in it.
18447 pub fn open_locations_in_multibuffer(
18448 workspace: &mut Workspace,
18449 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
18450 title: String,
18451 split: bool,
18452 allow_preview: bool,
18453 multibuffer_selection_mode: MultibufferSelectionMode,
18454 window: &mut Window,
18455 cx: &mut Context<Workspace>,
18456 ) -> Option<(Entity<Editor>, Entity<Pane>)> {
18457 if locations.is_empty() {
18458 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
18459 return None;
18460 }
18461
18462 let capability = workspace.project().read(cx).capability();
18463 let mut ranges = <Vec<Range<Anchor>>>::new();
18464
18465 // a key to find existing multibuffer editors with the same set of locations
18466 // to prevent us from opening more and more multibuffer tabs for searches and the like
18467 let mut key = (title.clone(), vec![]);
18468 let excerpt_buffer = cx.new(|cx| {
18469 let key = &mut key.1;
18470 let mut multibuffer = MultiBuffer::new(capability);
18471 for (buffer, mut ranges_for_buffer) in locations {
18472 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
18473 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
18474 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
18475 PathKey::for_buffer(&buffer, cx),
18476 buffer.clone(),
18477 ranges_for_buffer,
18478 multibuffer_context_lines(cx),
18479 cx,
18480 );
18481 ranges.extend(new_ranges)
18482 }
18483
18484 multibuffer.with_title(title)
18485 });
18486 let existing = workspace.active_pane().update(cx, |pane, cx| {
18487 pane.items()
18488 .filter_map(|item| item.downcast::<Editor>())
18489 .find(|editor| {
18490 editor
18491 .read(cx)
18492 .lookup_key
18493 .as_ref()
18494 .and_then(|it| {
18495 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
18496 })
18497 .is_some_and(|it| *it == key)
18498 })
18499 });
18500 let was_existing = existing.is_some();
18501 let editor = existing.unwrap_or_else(|| {
18502 cx.new(|cx| {
18503 let mut editor = Editor::for_multibuffer(
18504 excerpt_buffer,
18505 Some(workspace.project().clone()),
18506 window,
18507 cx,
18508 );
18509 editor.lookup_key = Some(Box::new(key));
18510 editor
18511 })
18512 });
18513 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
18514 MultibufferSelectionMode::First => {
18515 if let Some(first_range) = ranges.first() {
18516 editor.change_selections(
18517 SelectionEffects::no_scroll(),
18518 window,
18519 cx,
18520 |selections| {
18521 selections.clear_disjoint();
18522 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
18523 },
18524 );
18525 }
18526 editor.highlight_background::<Self>(
18527 &ranges,
18528 |_, theme| theme.colors().editor_highlighted_line_background,
18529 cx,
18530 );
18531 }
18532 MultibufferSelectionMode::All => {
18533 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
18534 selections.clear_disjoint();
18535 selections.select_anchor_ranges(ranges);
18536 });
18537 }
18538 });
18539
18540 let item = Box::new(editor.clone());
18541
18542 let pane = if split {
18543 workspace.adjacent_pane(window, cx)
18544 } else {
18545 workspace.active_pane().clone()
18546 };
18547 let activate_pane = split;
18548
18549 let mut destination_index = None;
18550 pane.update(cx, |pane, cx| {
18551 if allow_preview && !was_existing {
18552 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
18553 }
18554 if was_existing && !allow_preview {
18555 pane.unpreview_item_if_preview(item.item_id());
18556 }
18557 pane.add_item(item, activate_pane, true, destination_index, window, cx);
18558 });
18559
18560 Some((editor, pane))
18561 }
18562
18563 pub fn rename(
18564 &mut self,
18565 _: &Rename,
18566 window: &mut Window,
18567 cx: &mut Context<Self>,
18568 ) -> Option<Task<Result<()>>> {
18569 use language::ToOffset as _;
18570
18571 let provider = self.semantics_provider.clone()?;
18572 let selection = self.selections.newest_anchor().clone();
18573 let (cursor_buffer, cursor_buffer_position) = self
18574 .buffer
18575 .read(cx)
18576 .text_anchor_for_position(selection.head(), cx)?;
18577 let (tail_buffer, cursor_buffer_position_end) = self
18578 .buffer
18579 .read(cx)
18580 .text_anchor_for_position(selection.tail(), cx)?;
18581 if tail_buffer != cursor_buffer {
18582 return None;
18583 }
18584
18585 let snapshot = cursor_buffer.read(cx).snapshot();
18586 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
18587 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
18588 let prepare_rename = provider
18589 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
18590 .unwrap_or_else(|| Task::ready(Ok(None)));
18591 drop(snapshot);
18592
18593 Some(cx.spawn_in(window, async move |this, cx| {
18594 let rename_range = if let Some(range) = prepare_rename.await? {
18595 Some(range)
18596 } else {
18597 this.update(cx, |this, cx| {
18598 let buffer = this.buffer.read(cx).snapshot(cx);
18599 let mut buffer_highlights = this
18600 .document_highlights_for_position(selection.head(), &buffer)
18601 .filter(|highlight| {
18602 highlight.start.excerpt_id == selection.head().excerpt_id
18603 && highlight.end.excerpt_id == selection.head().excerpt_id
18604 });
18605 buffer_highlights
18606 .next()
18607 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
18608 })?
18609 };
18610 if let Some(rename_range) = rename_range {
18611 this.update_in(cx, |this, window, cx| {
18612 let snapshot = cursor_buffer.read(cx).snapshot();
18613 let rename_buffer_range = rename_range.to_offset(&snapshot);
18614 let cursor_offset_in_rename_range =
18615 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
18616 let cursor_offset_in_rename_range_end =
18617 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
18618
18619 this.take_rename(false, window, cx);
18620 let buffer = this.buffer.read(cx).read(cx);
18621 let cursor_offset = selection.head().to_offset(&buffer);
18622 let rename_start =
18623 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
18624 let rename_end = rename_start + rename_buffer_range.len();
18625 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
18626 let mut old_highlight_id = None;
18627 let old_name: Arc<str> = buffer
18628 .chunks(rename_start..rename_end, true)
18629 .map(|chunk| {
18630 if old_highlight_id.is_none() {
18631 old_highlight_id = chunk.syntax_highlight_id;
18632 }
18633 chunk.text
18634 })
18635 .collect::<String>()
18636 .into();
18637
18638 drop(buffer);
18639
18640 // Position the selection in the rename editor so that it matches the current selection.
18641 this.show_local_selections = false;
18642 let rename_editor = cx.new(|cx| {
18643 let mut editor = Editor::single_line(window, cx);
18644 editor.buffer.update(cx, |buffer, cx| {
18645 buffer.edit(
18646 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
18647 None,
18648 cx,
18649 )
18650 });
18651 let cursor_offset_in_rename_range =
18652 MultiBufferOffset(cursor_offset_in_rename_range);
18653 let cursor_offset_in_rename_range_end =
18654 MultiBufferOffset(cursor_offset_in_rename_range_end);
18655 let rename_selection_range = match cursor_offset_in_rename_range
18656 .cmp(&cursor_offset_in_rename_range_end)
18657 {
18658 Ordering::Equal => {
18659 editor.select_all(&SelectAll, window, cx);
18660 return editor;
18661 }
18662 Ordering::Less => {
18663 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
18664 }
18665 Ordering::Greater => {
18666 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
18667 }
18668 };
18669 if rename_selection_range.end.0 > old_name.len() {
18670 editor.select_all(&SelectAll, window, cx);
18671 } else {
18672 editor.change_selections(Default::default(), window, cx, |s| {
18673 s.select_ranges([rename_selection_range]);
18674 });
18675 }
18676 editor
18677 });
18678 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
18679 if e == &EditorEvent::Focused {
18680 cx.emit(EditorEvent::FocusedIn)
18681 }
18682 })
18683 .detach();
18684
18685 let write_highlights =
18686 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
18687 let read_highlights =
18688 this.clear_background_highlights::<DocumentHighlightRead>(cx);
18689 let ranges = write_highlights
18690 .iter()
18691 .flat_map(|(_, ranges)| ranges.iter())
18692 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
18693 .cloned()
18694 .collect();
18695
18696 this.highlight_text::<Rename>(
18697 ranges,
18698 HighlightStyle {
18699 fade_out: Some(0.6),
18700 ..Default::default()
18701 },
18702 cx,
18703 );
18704 let rename_focus_handle = rename_editor.focus_handle(cx);
18705 window.focus(&rename_focus_handle, cx);
18706 let block_id = this.insert_blocks(
18707 [BlockProperties {
18708 style: BlockStyle::Flex,
18709 placement: BlockPlacement::Below(range.start),
18710 height: Some(1),
18711 render: Arc::new({
18712 let rename_editor = rename_editor.clone();
18713 move |cx: &mut BlockContext| {
18714 let mut text_style = cx.editor_style.text.clone();
18715 if let Some(highlight_style) = old_highlight_id
18716 .and_then(|h| h.style(&cx.editor_style.syntax))
18717 {
18718 text_style = text_style.highlight(highlight_style);
18719 }
18720 div()
18721 .block_mouse_except_scroll()
18722 .pl(cx.anchor_x)
18723 .child(EditorElement::new(
18724 &rename_editor,
18725 EditorStyle {
18726 background: cx.theme().system().transparent,
18727 local_player: cx.editor_style.local_player,
18728 text: text_style,
18729 scrollbar_width: cx.editor_style.scrollbar_width,
18730 syntax: cx.editor_style.syntax.clone(),
18731 status: cx.editor_style.status.clone(),
18732 inlay_hints_style: HighlightStyle {
18733 font_weight: Some(FontWeight::BOLD),
18734 ..make_inlay_hints_style(cx.app)
18735 },
18736 edit_prediction_styles: make_suggestion_styles(
18737 cx.app,
18738 ),
18739 ..EditorStyle::default()
18740 },
18741 ))
18742 .into_any_element()
18743 }
18744 }),
18745 priority: 0,
18746 }],
18747 Some(Autoscroll::fit()),
18748 cx,
18749 )[0];
18750 this.pending_rename = Some(RenameState {
18751 range,
18752 old_name,
18753 editor: rename_editor,
18754 block_id,
18755 });
18756 })?;
18757 }
18758
18759 Ok(())
18760 }))
18761 }
18762
18763 pub fn confirm_rename(
18764 &mut self,
18765 _: &ConfirmRename,
18766 window: &mut Window,
18767 cx: &mut Context<Self>,
18768 ) -> Option<Task<Result<()>>> {
18769 let rename = self.take_rename(false, window, cx)?;
18770 let workspace = self.workspace()?.downgrade();
18771 let (buffer, start) = self
18772 .buffer
18773 .read(cx)
18774 .text_anchor_for_position(rename.range.start, cx)?;
18775 let (end_buffer, _) = self
18776 .buffer
18777 .read(cx)
18778 .text_anchor_for_position(rename.range.end, cx)?;
18779 if buffer != end_buffer {
18780 return None;
18781 }
18782
18783 let old_name = rename.old_name;
18784 let new_name = rename.editor.read(cx).text(cx);
18785
18786 let rename = self.semantics_provider.as_ref()?.perform_rename(
18787 &buffer,
18788 start,
18789 new_name.clone(),
18790 cx,
18791 )?;
18792
18793 Some(cx.spawn_in(window, async move |editor, cx| {
18794 let project_transaction = rename.await?;
18795 Self::open_project_transaction(
18796 &editor,
18797 workspace,
18798 project_transaction,
18799 format!("Rename: {} → {}", old_name, new_name),
18800 cx,
18801 )
18802 .await?;
18803
18804 editor.update(cx, |editor, cx| {
18805 editor.refresh_document_highlights(cx);
18806 })?;
18807 Ok(())
18808 }))
18809 }
18810
18811 fn take_rename(
18812 &mut self,
18813 moving_cursor: bool,
18814 window: &mut Window,
18815 cx: &mut Context<Self>,
18816 ) -> Option<RenameState> {
18817 let rename = self.pending_rename.take()?;
18818 if rename.editor.focus_handle(cx).is_focused(window) {
18819 window.focus(&self.focus_handle, cx);
18820 }
18821
18822 self.remove_blocks(
18823 [rename.block_id].into_iter().collect(),
18824 Some(Autoscroll::fit()),
18825 cx,
18826 );
18827 self.clear_highlights::<Rename>(cx);
18828 self.show_local_selections = true;
18829
18830 if moving_cursor {
18831 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
18832 editor
18833 .selections
18834 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
18835 .head()
18836 });
18837
18838 // Update the selection to match the position of the selection inside
18839 // the rename editor.
18840 let snapshot = self.buffer.read(cx).read(cx);
18841 let rename_range = rename.range.to_offset(&snapshot);
18842 let cursor_in_editor = snapshot
18843 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
18844 .min(rename_range.end);
18845 drop(snapshot);
18846
18847 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18848 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
18849 });
18850 } else {
18851 self.refresh_document_highlights(cx);
18852 }
18853
18854 Some(rename)
18855 }
18856
18857 pub fn pending_rename(&self) -> Option<&RenameState> {
18858 self.pending_rename.as_ref()
18859 }
18860
18861 fn format(
18862 &mut self,
18863 _: &Format,
18864 window: &mut Window,
18865 cx: &mut Context<Self>,
18866 ) -> Option<Task<Result<()>>> {
18867 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18868
18869 let project = match &self.project {
18870 Some(project) => project.clone(),
18871 None => return None,
18872 };
18873
18874 Some(self.perform_format(
18875 project,
18876 FormatTrigger::Manual,
18877 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
18878 window,
18879 cx,
18880 ))
18881 }
18882
18883 fn format_selections(
18884 &mut self,
18885 _: &FormatSelections,
18886 window: &mut Window,
18887 cx: &mut Context<Self>,
18888 ) -> Option<Task<Result<()>>> {
18889 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18890
18891 let project = match &self.project {
18892 Some(project) => project.clone(),
18893 None => return None,
18894 };
18895
18896 let ranges = self
18897 .selections
18898 .all_adjusted(&self.display_snapshot(cx))
18899 .into_iter()
18900 .map(|selection| selection.range())
18901 .collect_vec();
18902
18903 Some(self.perform_format(
18904 project,
18905 FormatTrigger::Manual,
18906 FormatTarget::Ranges(ranges),
18907 window,
18908 cx,
18909 ))
18910 }
18911
18912 fn perform_format(
18913 &mut self,
18914 project: Entity<Project>,
18915 trigger: FormatTrigger,
18916 target: FormatTarget,
18917 window: &mut Window,
18918 cx: &mut Context<Self>,
18919 ) -> Task<Result<()>> {
18920 let buffer = self.buffer.clone();
18921 let (buffers, target) = match target {
18922 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
18923 FormatTarget::Ranges(selection_ranges) => {
18924 let multi_buffer = buffer.read(cx);
18925 let snapshot = multi_buffer.read(cx);
18926 let mut buffers = HashSet::default();
18927 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
18928 BTreeMap::new();
18929 for selection_range in selection_ranges {
18930 for (buffer, buffer_range, _) in
18931 snapshot.range_to_buffer_ranges(selection_range.start..=selection_range.end)
18932 {
18933 let buffer_id = buffer.remote_id();
18934 let start = buffer.anchor_before(buffer_range.start);
18935 let end = buffer.anchor_after(buffer_range.end);
18936 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
18937 buffer_id_to_ranges
18938 .entry(buffer_id)
18939 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
18940 .or_insert_with(|| vec![start..end]);
18941 }
18942 }
18943 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
18944 }
18945 };
18946
18947 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
18948 let selections_prev = transaction_id_prev
18949 .and_then(|transaction_id_prev| {
18950 // default to selections as they were after the last edit, if we have them,
18951 // instead of how they are now.
18952 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
18953 // will take you back to where you made the last edit, instead of staying where you scrolled
18954 self.selection_history
18955 .transaction(transaction_id_prev)
18956 .map(|t| t.0.clone())
18957 })
18958 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
18959
18960 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
18961 let format = project.update(cx, |project, cx| {
18962 project.format(buffers, target, true, trigger, cx)
18963 });
18964
18965 cx.spawn_in(window, async move |editor, cx| {
18966 let transaction = futures::select_biased! {
18967 transaction = format.log_err().fuse() => transaction,
18968 () = timeout => {
18969 log::warn!("timed out waiting for formatting");
18970 None
18971 }
18972 };
18973
18974 buffer.update(cx, |buffer, cx| {
18975 if let Some(transaction) = transaction
18976 && !buffer.is_singleton()
18977 {
18978 buffer.push_transaction(&transaction.0, cx);
18979 }
18980 cx.notify();
18981 });
18982
18983 if let Some(transaction_id_now) =
18984 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))
18985 {
18986 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
18987 if has_new_transaction {
18988 editor
18989 .update(cx, |editor, _| {
18990 editor
18991 .selection_history
18992 .insert_transaction(transaction_id_now, selections_prev);
18993 })
18994 .ok();
18995 }
18996 }
18997
18998 Ok(())
18999 })
19000 }
19001
19002 fn organize_imports(
19003 &mut self,
19004 _: &OrganizeImports,
19005 window: &mut Window,
19006 cx: &mut Context<Self>,
19007 ) -> Option<Task<Result<()>>> {
19008 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19009 let project = match &self.project {
19010 Some(project) => project.clone(),
19011 None => return None,
19012 };
19013 Some(self.perform_code_action_kind(
19014 project,
19015 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
19016 window,
19017 cx,
19018 ))
19019 }
19020
19021 fn perform_code_action_kind(
19022 &mut self,
19023 project: Entity<Project>,
19024 kind: CodeActionKind,
19025 window: &mut Window,
19026 cx: &mut Context<Self>,
19027 ) -> Task<Result<()>> {
19028 let buffer = self.buffer.clone();
19029 let buffers = buffer.read(cx).all_buffers();
19030 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
19031 let apply_action = project.update(cx, |project, cx| {
19032 project.apply_code_action_kind(buffers, kind, true, cx)
19033 });
19034 cx.spawn_in(window, async move |_, cx| {
19035 let transaction = futures::select_biased! {
19036 () = timeout => {
19037 log::warn!("timed out waiting for executing code action");
19038 None
19039 }
19040 transaction = apply_action.log_err().fuse() => transaction,
19041 };
19042 buffer.update(cx, |buffer, cx| {
19043 // check if we need this
19044 if let Some(transaction) = transaction
19045 && !buffer.is_singleton()
19046 {
19047 buffer.push_transaction(&transaction.0, cx);
19048 }
19049 cx.notify();
19050 });
19051 Ok(())
19052 })
19053 }
19054
19055 pub fn restart_language_server(
19056 &mut self,
19057 _: &RestartLanguageServer,
19058 _: &mut Window,
19059 cx: &mut Context<Self>,
19060 ) {
19061 if let Some(project) = self.project.clone() {
19062 self.buffer.update(cx, |multi_buffer, cx| {
19063 project.update(cx, |project, cx| {
19064 project.restart_language_servers_for_buffers(
19065 multi_buffer.all_buffers().into_iter().collect(),
19066 HashSet::default(),
19067 cx,
19068 );
19069 });
19070 })
19071 }
19072 }
19073
19074 pub fn stop_language_server(
19075 &mut self,
19076 _: &StopLanguageServer,
19077 _: &mut Window,
19078 cx: &mut Context<Self>,
19079 ) {
19080 if let Some(project) = self.project.clone() {
19081 self.buffer.update(cx, |multi_buffer, cx| {
19082 project.update(cx, |project, cx| {
19083 project.stop_language_servers_for_buffers(
19084 multi_buffer.all_buffers().into_iter().collect(),
19085 HashSet::default(),
19086 cx,
19087 );
19088 });
19089 });
19090 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19091 }
19092 }
19093
19094 fn cancel_language_server_work(
19095 workspace: &mut Workspace,
19096 _: &actions::CancelLanguageServerWork,
19097 _: &mut Window,
19098 cx: &mut Context<Workspace>,
19099 ) {
19100 let project = workspace.project();
19101 let buffers = workspace
19102 .active_item(cx)
19103 .and_then(|item| item.act_as::<Editor>(cx))
19104 .map_or(HashSet::default(), |editor| {
19105 editor.read(cx).buffer.read(cx).all_buffers()
19106 });
19107 project.update(cx, |project, cx| {
19108 project.cancel_language_server_work_for_buffers(buffers, cx);
19109 });
19110 }
19111
19112 fn show_character_palette(
19113 &mut self,
19114 _: &ShowCharacterPalette,
19115 window: &mut Window,
19116 _: &mut Context<Self>,
19117 ) {
19118 window.show_character_palette();
19119 }
19120
19121 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
19122 if !self.diagnostics_enabled() {
19123 return;
19124 }
19125
19126 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
19127 let buffer = self.buffer.read(cx).snapshot(cx);
19128 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
19129 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
19130 let is_valid = buffer
19131 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
19132 .any(|entry| {
19133 entry.diagnostic.is_primary
19134 && !entry.range.is_empty()
19135 && entry.range.start == primary_range_start
19136 && entry.diagnostic.message == active_diagnostics.active_message
19137 });
19138
19139 if !is_valid {
19140 self.dismiss_diagnostics(cx);
19141 }
19142 }
19143 }
19144
19145 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
19146 match &self.active_diagnostics {
19147 ActiveDiagnostic::Group(group) => Some(group),
19148 _ => None,
19149 }
19150 }
19151
19152 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
19153 if !self.diagnostics_enabled() {
19154 return;
19155 }
19156 self.dismiss_diagnostics(cx);
19157 self.active_diagnostics = ActiveDiagnostic::All;
19158 }
19159
19160 fn activate_diagnostics(
19161 &mut self,
19162 buffer_id: BufferId,
19163 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
19164 window: &mut Window,
19165 cx: &mut Context<Self>,
19166 ) {
19167 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19168 return;
19169 }
19170 self.dismiss_diagnostics(cx);
19171 let snapshot = self.snapshot(window, cx);
19172 let buffer = self.buffer.read(cx).snapshot(cx);
19173 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
19174 return;
19175 };
19176
19177 let diagnostic_group = buffer
19178 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
19179 .collect::<Vec<_>>();
19180
19181 let language_registry = self
19182 .project()
19183 .map(|project| project.read(cx).languages().clone());
19184
19185 let blocks = renderer.render_group(
19186 diagnostic_group,
19187 buffer_id,
19188 snapshot,
19189 cx.weak_entity(),
19190 language_registry,
19191 cx,
19192 );
19193
19194 let blocks = self.display_map.update(cx, |display_map, cx| {
19195 display_map.insert_blocks(blocks, cx).into_iter().collect()
19196 });
19197 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
19198 active_range: buffer.anchor_before(diagnostic.range.start)
19199 ..buffer.anchor_after(diagnostic.range.end),
19200 active_message: diagnostic.diagnostic.message.clone(),
19201 group_id: diagnostic.diagnostic.group_id,
19202 blocks,
19203 });
19204 cx.notify();
19205 }
19206
19207 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
19208 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19209 return;
19210 };
19211
19212 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
19213 if let ActiveDiagnostic::Group(group) = prev {
19214 self.display_map.update(cx, |display_map, cx| {
19215 display_map.remove_blocks(group.blocks, cx);
19216 });
19217 cx.notify();
19218 }
19219 }
19220
19221 /// Disable inline diagnostics rendering for this editor.
19222 pub fn disable_inline_diagnostics(&mut self) {
19223 self.inline_diagnostics_enabled = false;
19224 self.inline_diagnostics_update = Task::ready(());
19225 self.inline_diagnostics.clear();
19226 }
19227
19228 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
19229 self.diagnostics_enabled = false;
19230 self.dismiss_diagnostics(cx);
19231 self.inline_diagnostics_update = Task::ready(());
19232 self.inline_diagnostics.clear();
19233 }
19234
19235 pub fn disable_word_completions(&mut self) {
19236 self.word_completions_enabled = false;
19237 }
19238
19239 pub fn diagnostics_enabled(&self) -> bool {
19240 self.diagnostics_enabled && self.mode.is_full()
19241 }
19242
19243 pub fn inline_diagnostics_enabled(&self) -> bool {
19244 self.inline_diagnostics_enabled && self.diagnostics_enabled()
19245 }
19246
19247 pub fn show_inline_diagnostics(&self) -> bool {
19248 self.show_inline_diagnostics
19249 }
19250
19251 pub fn toggle_inline_diagnostics(
19252 &mut self,
19253 _: &ToggleInlineDiagnostics,
19254 window: &mut Window,
19255 cx: &mut Context<Editor>,
19256 ) {
19257 self.show_inline_diagnostics = !self.show_inline_diagnostics;
19258 self.refresh_inline_diagnostics(false, window, cx);
19259 }
19260
19261 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
19262 self.diagnostics_max_severity = severity;
19263 self.display_map.update(cx, |display_map, _| {
19264 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
19265 });
19266 }
19267
19268 pub fn toggle_diagnostics(
19269 &mut self,
19270 _: &ToggleDiagnostics,
19271 window: &mut Window,
19272 cx: &mut Context<Editor>,
19273 ) {
19274 if !self.diagnostics_enabled() {
19275 return;
19276 }
19277
19278 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19279 EditorSettings::get_global(cx)
19280 .diagnostics_max_severity
19281 .filter(|severity| severity != &DiagnosticSeverity::Off)
19282 .unwrap_or(DiagnosticSeverity::Hint)
19283 } else {
19284 DiagnosticSeverity::Off
19285 };
19286 self.set_max_diagnostics_severity(new_severity, cx);
19287 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19288 self.active_diagnostics = ActiveDiagnostic::None;
19289 self.inline_diagnostics_update = Task::ready(());
19290 self.inline_diagnostics.clear();
19291 } else {
19292 self.refresh_inline_diagnostics(false, window, cx);
19293 }
19294
19295 cx.notify();
19296 }
19297
19298 pub fn toggle_minimap(
19299 &mut self,
19300 _: &ToggleMinimap,
19301 window: &mut Window,
19302 cx: &mut Context<Editor>,
19303 ) {
19304 if self.supports_minimap(cx) {
19305 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
19306 }
19307 }
19308
19309 fn refresh_inline_diagnostics(
19310 &mut self,
19311 debounce: bool,
19312 window: &mut Window,
19313 cx: &mut Context<Self>,
19314 ) {
19315 let max_severity = ProjectSettings::get_global(cx)
19316 .diagnostics
19317 .inline
19318 .max_severity
19319 .unwrap_or(self.diagnostics_max_severity);
19320
19321 if !self.inline_diagnostics_enabled()
19322 || !self.diagnostics_enabled()
19323 || !self.show_inline_diagnostics
19324 || max_severity == DiagnosticSeverity::Off
19325 {
19326 self.inline_diagnostics_update = Task::ready(());
19327 self.inline_diagnostics.clear();
19328 return;
19329 }
19330
19331 let debounce_ms = ProjectSettings::get_global(cx)
19332 .diagnostics
19333 .inline
19334 .update_debounce_ms;
19335 let debounce = if debounce && debounce_ms > 0 {
19336 Some(Duration::from_millis(debounce_ms))
19337 } else {
19338 None
19339 };
19340 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
19341 if let Some(debounce) = debounce {
19342 cx.background_executor().timer(debounce).await;
19343 }
19344 let Some(snapshot) = editor.upgrade().map(|editor| {
19345 editor.update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
19346 }) else {
19347 return;
19348 };
19349
19350 let new_inline_diagnostics = cx
19351 .background_spawn(async move {
19352 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
19353 for diagnostic_entry in
19354 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
19355 {
19356 let message = diagnostic_entry
19357 .diagnostic
19358 .message
19359 .split_once('\n')
19360 .map(|(line, _)| line)
19361 .map(SharedString::new)
19362 .unwrap_or_else(|| {
19363 SharedString::new(&*diagnostic_entry.diagnostic.message)
19364 });
19365 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
19366 let (Ok(i) | Err(i)) = inline_diagnostics
19367 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
19368 inline_diagnostics.insert(
19369 i,
19370 (
19371 start_anchor,
19372 InlineDiagnostic {
19373 message,
19374 group_id: diagnostic_entry.diagnostic.group_id,
19375 start: diagnostic_entry.range.start.to_point(&snapshot),
19376 is_primary: diagnostic_entry.diagnostic.is_primary,
19377 severity: diagnostic_entry.diagnostic.severity,
19378 },
19379 ),
19380 );
19381 }
19382 inline_diagnostics
19383 })
19384 .await;
19385
19386 editor
19387 .update(cx, |editor, cx| {
19388 editor.inline_diagnostics = new_inline_diagnostics;
19389 cx.notify();
19390 })
19391 .ok();
19392 });
19393 }
19394
19395 fn pull_diagnostics(
19396 &mut self,
19397 buffer_id: BufferId,
19398 _window: &Window,
19399 cx: &mut Context<Self>,
19400 ) -> Option<()> {
19401 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
19402 return None;
19403 }
19404 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
19405 .diagnostics
19406 .lsp_pull_diagnostics;
19407 if !pull_diagnostics_settings.enabled {
19408 return None;
19409 }
19410 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
19411 let project = self.project()?.downgrade();
19412 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
19413
19414 self.pull_diagnostics_task = cx.spawn(async move |_, cx| {
19415 cx.background_executor().timer(debounce).await;
19416 if let Ok(task) = project.update(cx, |project, cx| {
19417 project.lsp_store().update(cx, |lsp_store, cx| {
19418 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
19419 })
19420 }) {
19421 task.await.log_err();
19422 }
19423 project
19424 .update(cx, |project, cx| {
19425 project.lsp_store().update(cx, |lsp_store, cx| {
19426 lsp_store.pull_document_diagnostics_for_buffer_edit(buffer_id, cx);
19427 })
19428 })
19429 .log_err();
19430 });
19431
19432 Some(())
19433 }
19434
19435 pub fn set_selections_from_remote(
19436 &mut self,
19437 selections: Vec<Selection<Anchor>>,
19438 pending_selection: Option<Selection<Anchor>>,
19439 window: &mut Window,
19440 cx: &mut Context<Self>,
19441 ) {
19442 let old_cursor_position = self.selections.newest_anchor().head();
19443 self.selections
19444 .change_with(&self.display_snapshot(cx), |s| {
19445 s.select_anchors(selections);
19446 if let Some(pending_selection) = pending_selection {
19447 s.set_pending(pending_selection, SelectMode::Character);
19448 } else {
19449 s.clear_pending();
19450 }
19451 });
19452 self.selections_did_change(
19453 false,
19454 &old_cursor_position,
19455 SelectionEffects::default(),
19456 window,
19457 cx,
19458 );
19459 }
19460
19461 pub fn transact(
19462 &mut self,
19463 window: &mut Window,
19464 cx: &mut Context<Self>,
19465 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
19466 ) -> Option<TransactionId> {
19467 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
19468 this.start_transaction_at(Instant::now(), window, cx);
19469 update(this, window, cx);
19470 this.end_transaction_at(Instant::now(), cx)
19471 })
19472 }
19473
19474 pub fn start_transaction_at(
19475 &mut self,
19476 now: Instant,
19477 window: &mut Window,
19478 cx: &mut Context<Self>,
19479 ) -> Option<TransactionId> {
19480 self.end_selection(window, cx);
19481 if let Some(tx_id) = self
19482 .buffer
19483 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
19484 {
19485 self.selection_history
19486 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
19487 cx.emit(EditorEvent::TransactionBegun {
19488 transaction_id: tx_id,
19489 });
19490 Some(tx_id)
19491 } else {
19492 None
19493 }
19494 }
19495
19496 pub fn end_transaction_at(
19497 &mut self,
19498 now: Instant,
19499 cx: &mut Context<Self>,
19500 ) -> Option<TransactionId> {
19501 if let Some(transaction_id) = self
19502 .buffer
19503 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
19504 {
19505 if let Some((_, end_selections)) =
19506 self.selection_history.transaction_mut(transaction_id)
19507 {
19508 *end_selections = Some(self.selections.disjoint_anchors_arc());
19509 } else {
19510 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
19511 }
19512
19513 cx.emit(EditorEvent::Edited { transaction_id });
19514 Some(transaction_id)
19515 } else {
19516 None
19517 }
19518 }
19519
19520 pub fn modify_transaction_selection_history(
19521 &mut self,
19522 transaction_id: TransactionId,
19523 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
19524 ) -> bool {
19525 self.selection_history
19526 .transaction_mut(transaction_id)
19527 .map(modify)
19528 .is_some()
19529 }
19530
19531 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
19532 if self.selection_mark_mode {
19533 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19534 s.move_with(|_, sel| {
19535 sel.collapse_to(sel.head(), SelectionGoal::None);
19536 });
19537 })
19538 }
19539 self.selection_mark_mode = true;
19540 cx.notify();
19541 }
19542
19543 pub fn swap_selection_ends(
19544 &mut self,
19545 _: &actions::SwapSelectionEnds,
19546 window: &mut Window,
19547 cx: &mut Context<Self>,
19548 ) {
19549 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19550 s.move_with(|_, sel| {
19551 if sel.start != sel.end {
19552 sel.reversed = !sel.reversed
19553 }
19554 });
19555 });
19556 self.request_autoscroll(Autoscroll::newest(), cx);
19557 cx.notify();
19558 }
19559
19560 pub fn toggle_focus(
19561 workspace: &mut Workspace,
19562 _: &actions::ToggleFocus,
19563 window: &mut Window,
19564 cx: &mut Context<Workspace>,
19565 ) {
19566 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
19567 return;
19568 };
19569 workspace.activate_item(&item, true, true, window, cx);
19570 }
19571
19572 pub fn toggle_fold(
19573 &mut self,
19574 _: &actions::ToggleFold,
19575 window: &mut Window,
19576 cx: &mut Context<Self>,
19577 ) {
19578 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19579 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19580 let selection = self.selections.newest::<Point>(&display_map);
19581
19582 let range = if selection.is_empty() {
19583 let point = selection.head().to_display_point(&display_map);
19584 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19585 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19586 .to_point(&display_map);
19587 start..end
19588 } else {
19589 selection.range()
19590 };
19591 if display_map.folds_in_range(range).next().is_some() {
19592 self.unfold_lines(&Default::default(), window, cx)
19593 } else {
19594 self.fold(&Default::default(), window, cx)
19595 }
19596 } else {
19597 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19598 let buffer_ids: HashSet<_> = self
19599 .selections
19600 .disjoint_anchor_ranges()
19601 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19602 .collect();
19603
19604 let should_unfold = buffer_ids
19605 .iter()
19606 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19607
19608 for buffer_id in buffer_ids {
19609 if should_unfold {
19610 self.unfold_buffer(buffer_id, cx);
19611 } else {
19612 self.fold_buffer(buffer_id, cx);
19613 }
19614 }
19615 }
19616 }
19617
19618 pub fn toggle_fold_recursive(
19619 &mut self,
19620 _: &actions::ToggleFoldRecursive,
19621 window: &mut Window,
19622 cx: &mut Context<Self>,
19623 ) {
19624 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
19625
19626 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19627 let range = if selection.is_empty() {
19628 let point = selection.head().to_display_point(&display_map);
19629 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19630 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19631 .to_point(&display_map);
19632 start..end
19633 } else {
19634 selection.range()
19635 };
19636 if display_map.folds_in_range(range).next().is_some() {
19637 self.unfold_recursive(&Default::default(), window, cx)
19638 } else {
19639 self.fold_recursive(&Default::default(), window, cx)
19640 }
19641 }
19642
19643 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
19644 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19645 let mut to_fold = Vec::new();
19646 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19647 let selections = self.selections.all_adjusted(&display_map);
19648
19649 for selection in selections {
19650 let range = selection.range().sorted();
19651 let buffer_start_row = range.start.row;
19652
19653 if range.start.row != range.end.row {
19654 let mut found = false;
19655 let mut row = range.start.row;
19656 while row <= range.end.row {
19657 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19658 {
19659 found = true;
19660 row = crease.range().end.row + 1;
19661 to_fold.push(crease);
19662 } else {
19663 row += 1
19664 }
19665 }
19666 if found {
19667 continue;
19668 }
19669 }
19670
19671 for row in (0..=range.start.row).rev() {
19672 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19673 && crease.range().end.row >= buffer_start_row
19674 {
19675 to_fold.push(crease);
19676 if row <= range.start.row {
19677 break;
19678 }
19679 }
19680 }
19681 }
19682
19683 self.fold_creases(to_fold, true, window, cx);
19684 } else {
19685 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19686 let buffer_ids = self
19687 .selections
19688 .disjoint_anchor_ranges()
19689 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19690 .collect::<HashSet<_>>();
19691 for buffer_id in buffer_ids {
19692 self.fold_buffer(buffer_id, cx);
19693 }
19694 }
19695 }
19696
19697 pub fn toggle_fold_all(
19698 &mut self,
19699 _: &actions::ToggleFoldAll,
19700 window: &mut Window,
19701 cx: &mut Context<Self>,
19702 ) {
19703 let has_folds = if self.buffer.read(cx).is_singleton() {
19704 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19705 let has_folds = display_map
19706 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
19707 .next()
19708 .is_some();
19709 has_folds
19710 } else {
19711 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
19712 let has_folds = buffer_ids
19713 .iter()
19714 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19715 has_folds
19716 };
19717
19718 if has_folds {
19719 self.unfold_all(&actions::UnfoldAll, window, cx);
19720 } else {
19721 self.fold_all(&actions::FoldAll, window, cx);
19722 }
19723 }
19724
19725 fn fold_at_level(
19726 &mut self,
19727 fold_at: &FoldAtLevel,
19728 window: &mut Window,
19729 cx: &mut Context<Self>,
19730 ) {
19731 if !self.buffer.read(cx).is_singleton() {
19732 return;
19733 }
19734
19735 let fold_at_level = fold_at.0;
19736 let snapshot = self.buffer.read(cx).snapshot(cx);
19737 let mut to_fold = Vec::new();
19738 let mut stack = vec![(0, snapshot.max_row().0, 1)];
19739
19740 let row_ranges_to_keep: Vec<Range<u32>> = self
19741 .selections
19742 .all::<Point>(&self.display_snapshot(cx))
19743 .into_iter()
19744 .map(|sel| sel.start.row..sel.end.row)
19745 .collect();
19746
19747 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
19748 while start_row < end_row {
19749 match self
19750 .snapshot(window, cx)
19751 .crease_for_buffer_row(MultiBufferRow(start_row))
19752 {
19753 Some(crease) => {
19754 let nested_start_row = crease.range().start.row + 1;
19755 let nested_end_row = crease.range().end.row;
19756
19757 if current_level < fold_at_level {
19758 stack.push((nested_start_row, nested_end_row, current_level + 1));
19759 } else if current_level == fold_at_level {
19760 // Fold iff there is no selection completely contained within the fold region
19761 if !row_ranges_to_keep.iter().any(|selection| {
19762 selection.end >= nested_start_row
19763 && selection.start <= nested_end_row
19764 }) {
19765 to_fold.push(crease);
19766 }
19767 }
19768
19769 start_row = nested_end_row + 1;
19770 }
19771 None => start_row += 1,
19772 }
19773 }
19774 }
19775
19776 self.fold_creases(to_fold, true, window, cx);
19777 }
19778
19779 pub fn fold_at_level_1(
19780 &mut self,
19781 _: &actions::FoldAtLevel1,
19782 window: &mut Window,
19783 cx: &mut Context<Self>,
19784 ) {
19785 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
19786 }
19787
19788 pub fn fold_at_level_2(
19789 &mut self,
19790 _: &actions::FoldAtLevel2,
19791 window: &mut Window,
19792 cx: &mut Context<Self>,
19793 ) {
19794 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
19795 }
19796
19797 pub fn fold_at_level_3(
19798 &mut self,
19799 _: &actions::FoldAtLevel3,
19800 window: &mut Window,
19801 cx: &mut Context<Self>,
19802 ) {
19803 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
19804 }
19805
19806 pub fn fold_at_level_4(
19807 &mut self,
19808 _: &actions::FoldAtLevel4,
19809 window: &mut Window,
19810 cx: &mut Context<Self>,
19811 ) {
19812 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
19813 }
19814
19815 pub fn fold_at_level_5(
19816 &mut self,
19817 _: &actions::FoldAtLevel5,
19818 window: &mut Window,
19819 cx: &mut Context<Self>,
19820 ) {
19821 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
19822 }
19823
19824 pub fn fold_at_level_6(
19825 &mut self,
19826 _: &actions::FoldAtLevel6,
19827 window: &mut Window,
19828 cx: &mut Context<Self>,
19829 ) {
19830 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
19831 }
19832
19833 pub fn fold_at_level_7(
19834 &mut self,
19835 _: &actions::FoldAtLevel7,
19836 window: &mut Window,
19837 cx: &mut Context<Self>,
19838 ) {
19839 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
19840 }
19841
19842 pub fn fold_at_level_8(
19843 &mut self,
19844 _: &actions::FoldAtLevel8,
19845 window: &mut Window,
19846 cx: &mut Context<Self>,
19847 ) {
19848 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
19849 }
19850
19851 pub fn fold_at_level_9(
19852 &mut self,
19853 _: &actions::FoldAtLevel9,
19854 window: &mut Window,
19855 cx: &mut Context<Self>,
19856 ) {
19857 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
19858 }
19859
19860 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
19861 if self.buffer.read(cx).is_singleton() {
19862 let mut fold_ranges = Vec::new();
19863 let snapshot = self.buffer.read(cx).snapshot(cx);
19864
19865 for row in 0..snapshot.max_row().0 {
19866 if let Some(foldable_range) = self
19867 .snapshot(window, cx)
19868 .crease_for_buffer_row(MultiBufferRow(row))
19869 {
19870 fold_ranges.push(foldable_range);
19871 }
19872 }
19873
19874 self.fold_creases(fold_ranges, true, window, cx);
19875 } else {
19876 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
19877 editor
19878 .update_in(cx, |editor, _, cx| {
19879 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19880 editor.fold_buffer(buffer_id, cx);
19881 }
19882 })
19883 .ok();
19884 });
19885 }
19886 cx.emit(SearchEvent::ResultsCollapsedChanged(
19887 CollapseDirection::Collapsed,
19888 ));
19889 }
19890
19891 pub fn fold_function_bodies(
19892 &mut self,
19893 _: &actions::FoldFunctionBodies,
19894 window: &mut Window,
19895 cx: &mut Context<Self>,
19896 ) {
19897 let snapshot = self.buffer.read(cx).snapshot(cx);
19898
19899 let ranges = snapshot
19900 .text_object_ranges(
19901 MultiBufferOffset(0)..snapshot.len(),
19902 TreeSitterOptions::default(),
19903 )
19904 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
19905 .collect::<Vec<_>>();
19906
19907 let creases = ranges
19908 .into_iter()
19909 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
19910 .collect();
19911
19912 self.fold_creases(creases, true, window, cx);
19913 }
19914
19915 pub fn fold_recursive(
19916 &mut self,
19917 _: &actions::FoldRecursive,
19918 window: &mut Window,
19919 cx: &mut Context<Self>,
19920 ) {
19921 let mut to_fold = Vec::new();
19922 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19923 let selections = self.selections.all_adjusted(&display_map);
19924
19925 for selection in selections {
19926 let range = selection.range().sorted();
19927 let buffer_start_row = range.start.row;
19928
19929 if range.start.row != range.end.row {
19930 let mut found = false;
19931 for row in range.start.row..=range.end.row {
19932 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19933 found = true;
19934 to_fold.push(crease);
19935 }
19936 }
19937 if found {
19938 continue;
19939 }
19940 }
19941
19942 for row in (0..=range.start.row).rev() {
19943 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19944 if crease.range().end.row >= buffer_start_row {
19945 to_fold.push(crease);
19946 } else {
19947 break;
19948 }
19949 }
19950 }
19951 }
19952
19953 self.fold_creases(to_fold, true, window, cx);
19954 }
19955
19956 pub fn fold_at(
19957 &mut self,
19958 buffer_row: MultiBufferRow,
19959 window: &mut Window,
19960 cx: &mut Context<Self>,
19961 ) {
19962 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19963
19964 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
19965 let autoscroll = self
19966 .selections
19967 .all::<Point>(&display_map)
19968 .iter()
19969 .any(|selection| crease.range().overlaps(&selection.range()));
19970
19971 self.fold_creases(vec![crease], autoscroll, window, cx);
19972 }
19973 }
19974
19975 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
19976 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19977 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19978 let buffer = display_map.buffer_snapshot();
19979 let selections = self.selections.all::<Point>(&display_map);
19980 let ranges = selections
19981 .iter()
19982 .map(|s| {
19983 let range = s.display_range(&display_map).sorted();
19984 let mut start = range.start.to_point(&display_map);
19985 let mut end = range.end.to_point(&display_map);
19986 start.column = 0;
19987 end.column = buffer.line_len(MultiBufferRow(end.row));
19988 start..end
19989 })
19990 .collect::<Vec<_>>();
19991
19992 self.unfold_ranges(&ranges, true, true, cx);
19993 } else {
19994 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19995 let buffer_ids = self
19996 .selections
19997 .disjoint_anchor_ranges()
19998 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19999 .collect::<HashSet<_>>();
20000 for buffer_id in buffer_ids {
20001 self.unfold_buffer(buffer_id, cx);
20002 }
20003 }
20004 }
20005
20006 pub fn unfold_recursive(
20007 &mut self,
20008 _: &UnfoldRecursive,
20009 _window: &mut Window,
20010 cx: &mut Context<Self>,
20011 ) {
20012 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20013 let selections = self.selections.all::<Point>(&display_map);
20014 let ranges = selections
20015 .iter()
20016 .map(|s| {
20017 let mut range = s.display_range(&display_map).sorted();
20018 *range.start.column_mut() = 0;
20019 *range.end.column_mut() = display_map.line_len(range.end.row());
20020 let start = range.start.to_point(&display_map);
20021 let end = range.end.to_point(&display_map);
20022 start..end
20023 })
20024 .collect::<Vec<_>>();
20025
20026 self.unfold_ranges(&ranges, true, true, cx);
20027 }
20028
20029 pub fn unfold_at(
20030 &mut self,
20031 buffer_row: MultiBufferRow,
20032 _window: &mut Window,
20033 cx: &mut Context<Self>,
20034 ) {
20035 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20036
20037 let intersection_range = Point::new(buffer_row.0, 0)
20038 ..Point::new(
20039 buffer_row.0,
20040 display_map.buffer_snapshot().line_len(buffer_row),
20041 );
20042
20043 let autoscroll = self
20044 .selections
20045 .all::<Point>(&display_map)
20046 .iter()
20047 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
20048
20049 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
20050 }
20051
20052 pub fn unfold_all(
20053 &mut self,
20054 _: &actions::UnfoldAll,
20055 _window: &mut Window,
20056 cx: &mut Context<Self>,
20057 ) {
20058 if self.buffer.read(cx).is_singleton() {
20059 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20060 self.unfold_ranges(
20061 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
20062 true,
20063 true,
20064 cx,
20065 );
20066 } else {
20067 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
20068 editor
20069 .update(cx, |editor, cx| {
20070 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
20071 editor.unfold_buffer(buffer_id, cx);
20072 }
20073 })
20074 .ok();
20075 });
20076 }
20077 cx.emit(SearchEvent::ResultsCollapsedChanged(
20078 CollapseDirection::Expanded,
20079 ));
20080 }
20081
20082 pub fn fold_selected_ranges(
20083 &mut self,
20084 _: &FoldSelectedRanges,
20085 window: &mut Window,
20086 cx: &mut Context<Self>,
20087 ) {
20088 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20089 let selections = self.selections.all_adjusted(&display_map);
20090 let ranges = selections
20091 .into_iter()
20092 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
20093 .collect::<Vec<_>>();
20094 self.fold_creases(ranges, true, window, cx);
20095 }
20096
20097 pub fn fold_ranges<T: ToOffset + Clone>(
20098 &mut self,
20099 ranges: Vec<Range<T>>,
20100 auto_scroll: bool,
20101 window: &mut Window,
20102 cx: &mut Context<Self>,
20103 ) {
20104 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20105 let ranges = ranges
20106 .into_iter()
20107 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
20108 .collect::<Vec<_>>();
20109 self.fold_creases(ranges, auto_scroll, window, cx);
20110 }
20111
20112 pub fn fold_creases<T: ToOffset + Clone>(
20113 &mut self,
20114 creases: Vec<Crease<T>>,
20115 auto_scroll: bool,
20116 _window: &mut Window,
20117 cx: &mut Context<Self>,
20118 ) {
20119 if creases.is_empty() {
20120 return;
20121 }
20122
20123 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
20124
20125 if auto_scroll {
20126 self.request_autoscroll(Autoscroll::fit(), cx);
20127 }
20128
20129 cx.notify();
20130
20131 self.scrollbar_marker_state.dirty = true;
20132 self.folds_did_change(cx);
20133 }
20134
20135 /// Removes any folds whose ranges intersect any of the given ranges.
20136 pub fn unfold_ranges<T: ToOffset + Clone>(
20137 &mut self,
20138 ranges: &[Range<T>],
20139 inclusive: bool,
20140 auto_scroll: bool,
20141 cx: &mut Context<Self>,
20142 ) {
20143 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20144 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx);
20145 });
20146 self.folds_did_change(cx);
20147 }
20148
20149 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20150 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
20151 return;
20152 }
20153
20154 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
20155 self.display_map.update(cx, |display_map, cx| {
20156 display_map.fold_buffers([buffer_id], cx)
20157 });
20158
20159 let snapshot = self.display_snapshot(cx);
20160 self.selections.change_with(&snapshot, |selections| {
20161 selections.remove_selections_from_buffer(buffer_id);
20162 });
20163
20164 cx.emit(EditorEvent::BufferFoldToggled {
20165 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
20166 folded: true,
20167 });
20168 cx.notify();
20169 }
20170
20171 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20172 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
20173 return;
20174 }
20175 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
20176 self.display_map.update(cx, |display_map, cx| {
20177 display_map.unfold_buffers([buffer_id], cx);
20178 });
20179 cx.emit(EditorEvent::BufferFoldToggled {
20180 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
20181 folded: false,
20182 });
20183 cx.notify();
20184 }
20185
20186 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
20187 self.display_map.read(cx).is_buffer_folded(buffer)
20188 }
20189
20190 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
20191 self.display_map.read(cx).folded_buffers()
20192 }
20193
20194 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20195 self.display_map.update(cx, |display_map, cx| {
20196 display_map.disable_header_for_buffer(buffer_id, cx);
20197 });
20198 cx.notify();
20199 }
20200
20201 /// Removes any folds with the given ranges.
20202 pub fn remove_folds_with_type<T: ToOffset + Clone>(
20203 &mut self,
20204 ranges: &[Range<T>],
20205 type_id: TypeId,
20206 auto_scroll: bool,
20207 cx: &mut Context<Self>,
20208 ) {
20209 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20210 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
20211 });
20212 self.folds_did_change(cx);
20213 }
20214
20215 fn remove_folds_with<T: ToOffset + Clone>(
20216 &mut self,
20217 ranges: &[Range<T>],
20218 auto_scroll: bool,
20219 cx: &mut Context<Self>,
20220 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
20221 ) {
20222 if ranges.is_empty() {
20223 return;
20224 }
20225
20226 let mut buffers_affected = HashSet::default();
20227 let multi_buffer = self.buffer().read(cx);
20228 for range in ranges {
20229 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
20230 buffers_affected.insert(buffer.read(cx).remote_id());
20231 };
20232 }
20233
20234 self.display_map.update(cx, update);
20235
20236 if auto_scroll {
20237 self.request_autoscroll(Autoscroll::fit(), cx);
20238 }
20239
20240 cx.notify();
20241 self.scrollbar_marker_state.dirty = true;
20242 self.active_indent_guides_state.dirty = true;
20243 }
20244
20245 pub fn update_renderer_widths(
20246 &mut self,
20247 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
20248 cx: &mut Context<Self>,
20249 ) -> bool {
20250 self.display_map
20251 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
20252 }
20253
20254 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
20255 self.display_map.read(cx).fold_placeholder.clone()
20256 }
20257
20258 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
20259 self.buffer.update(cx, |buffer, cx| {
20260 buffer.set_all_diff_hunks_expanded(cx);
20261 });
20262 }
20263
20264 pub fn expand_all_diff_hunks(
20265 &mut self,
20266 _: &ExpandAllDiffHunks,
20267 _window: &mut Window,
20268 cx: &mut Context<Self>,
20269 ) {
20270 self.buffer.update(cx, |buffer, cx| {
20271 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20272 });
20273 }
20274
20275 pub fn collapse_all_diff_hunks(
20276 &mut self,
20277 _: &CollapseAllDiffHunks,
20278 _window: &mut Window,
20279 cx: &mut Context<Self>,
20280 ) {
20281 self.buffer.update(cx, |buffer, cx| {
20282 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20283 });
20284 }
20285
20286 pub fn toggle_selected_diff_hunks(
20287 &mut self,
20288 _: &ToggleSelectedDiffHunks,
20289 _window: &mut Window,
20290 cx: &mut Context<Self>,
20291 ) {
20292 let ranges: Vec<_> = self
20293 .selections
20294 .disjoint_anchors()
20295 .iter()
20296 .map(|s| s.range())
20297 .collect();
20298 self.toggle_diff_hunks_in_ranges(ranges, cx);
20299 }
20300
20301 pub fn diff_hunks_in_ranges<'a>(
20302 &'a self,
20303 ranges: &'a [Range<Anchor>],
20304 buffer: &'a MultiBufferSnapshot,
20305 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
20306 ranges.iter().flat_map(move |range| {
20307 let end_excerpt_id = range.end.excerpt_id;
20308 let range = range.to_point(buffer);
20309 let mut peek_end = range.end;
20310 if range.end.row < buffer.max_row().0 {
20311 peek_end = Point::new(range.end.row + 1, 0);
20312 }
20313 buffer
20314 .diff_hunks_in_range(range.start..peek_end)
20315 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
20316 })
20317 }
20318
20319 pub fn has_stageable_diff_hunks_in_ranges(
20320 &self,
20321 ranges: &[Range<Anchor>],
20322 snapshot: &MultiBufferSnapshot,
20323 ) -> bool {
20324 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
20325 hunks.any(|hunk| hunk.status().has_secondary_hunk())
20326 }
20327
20328 pub fn toggle_staged_selected_diff_hunks(
20329 &mut self,
20330 _: &::git::ToggleStaged,
20331 _: &mut Window,
20332 cx: &mut Context<Self>,
20333 ) {
20334 let snapshot = self.buffer.read(cx).snapshot(cx);
20335 let ranges: Vec<_> = self
20336 .selections
20337 .disjoint_anchors()
20338 .iter()
20339 .map(|s| s.range())
20340 .collect();
20341 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
20342 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20343 }
20344
20345 pub fn set_render_diff_hunk_controls(
20346 &mut self,
20347 render_diff_hunk_controls: RenderDiffHunkControlsFn,
20348 cx: &mut Context<Self>,
20349 ) {
20350 self.render_diff_hunk_controls = render_diff_hunk_controls;
20351 cx.notify();
20352 }
20353
20354 pub fn stage_and_next(
20355 &mut self,
20356 _: &::git::StageAndNext,
20357 window: &mut Window,
20358 cx: &mut Context<Self>,
20359 ) {
20360 self.do_stage_or_unstage_and_next(true, window, cx);
20361 }
20362
20363 pub fn unstage_and_next(
20364 &mut self,
20365 _: &::git::UnstageAndNext,
20366 window: &mut Window,
20367 cx: &mut Context<Self>,
20368 ) {
20369 self.do_stage_or_unstage_and_next(false, window, cx);
20370 }
20371
20372 pub fn stage_or_unstage_diff_hunks(
20373 &mut self,
20374 stage: bool,
20375 ranges: Vec<Range<Anchor>>,
20376 cx: &mut Context<Self>,
20377 ) {
20378 if self.delegate_stage_and_restore {
20379 let snapshot = self.buffer.read(cx).snapshot(cx);
20380 let hunks: Vec<_> = self.diff_hunks_in_ranges(&ranges, &snapshot).collect();
20381 if !hunks.is_empty() {
20382 cx.emit(EditorEvent::StageOrUnstageRequested { stage, hunks });
20383 }
20384 return;
20385 }
20386 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
20387 cx.spawn(async move |this, cx| {
20388 task.await?;
20389 this.update(cx, |this, cx| {
20390 let snapshot = this.buffer.read(cx).snapshot(cx);
20391 let chunk_by = this
20392 .diff_hunks_in_ranges(&ranges, &snapshot)
20393 .chunk_by(|hunk| hunk.buffer_id);
20394 for (buffer_id, hunks) in &chunk_by {
20395 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
20396 }
20397 })
20398 })
20399 .detach_and_log_err(cx);
20400 }
20401
20402 fn save_buffers_for_ranges_if_needed(
20403 &mut self,
20404 ranges: &[Range<Anchor>],
20405 cx: &mut Context<Editor>,
20406 ) -> Task<Result<()>> {
20407 let multibuffer = self.buffer.read(cx);
20408 let snapshot = multibuffer.read(cx);
20409 let buffer_ids: HashSet<_> = ranges
20410 .iter()
20411 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
20412 .collect();
20413 drop(snapshot);
20414
20415 let mut buffers = HashSet::default();
20416 for buffer_id in buffer_ids {
20417 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
20418 let buffer = buffer_entity.read(cx);
20419 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
20420 {
20421 buffers.insert(buffer_entity);
20422 }
20423 }
20424 }
20425
20426 if let Some(project) = &self.project {
20427 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
20428 } else {
20429 Task::ready(Ok(()))
20430 }
20431 }
20432
20433 fn do_stage_or_unstage_and_next(
20434 &mut self,
20435 stage: bool,
20436 window: &mut Window,
20437 cx: &mut Context<Self>,
20438 ) {
20439 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
20440
20441 if ranges.iter().any(|range| range.start != range.end) {
20442 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20443 return;
20444 }
20445
20446 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20447 let snapshot = self.snapshot(window, cx);
20448 let position = self
20449 .selections
20450 .newest::<Point>(&snapshot.display_snapshot)
20451 .head();
20452 let mut row = snapshot
20453 .buffer_snapshot()
20454 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
20455 .find(|hunk| hunk.row_range.start.0 > position.row)
20456 .map(|hunk| hunk.row_range.start);
20457
20458 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
20459 // Outside of the project diff editor, wrap around to the beginning.
20460 if !all_diff_hunks_expanded {
20461 row = row.or_else(|| {
20462 snapshot
20463 .buffer_snapshot()
20464 .diff_hunks_in_range(Point::zero()..position)
20465 .find(|hunk| hunk.row_range.end.0 < position.row)
20466 .map(|hunk| hunk.row_range.start)
20467 });
20468 }
20469
20470 if let Some(row) = row {
20471 let destination = Point::new(row.0, 0);
20472 let autoscroll = Autoscroll::center();
20473
20474 self.unfold_ranges(&[destination..destination], false, false, cx);
20475 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
20476 s.select_ranges([destination..destination]);
20477 });
20478 }
20479 }
20480
20481 pub(crate) fn do_stage_or_unstage(
20482 &self,
20483 stage: bool,
20484 buffer_id: BufferId,
20485 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
20486 cx: &mut App,
20487 ) -> Option<()> {
20488 let project = self.project()?;
20489 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
20490 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
20491 let buffer_snapshot = buffer.read(cx).snapshot();
20492 let file_exists = buffer_snapshot
20493 .file()
20494 .is_some_and(|file| file.disk_state().exists());
20495 diff.update(cx, |diff, cx| {
20496 diff.stage_or_unstage_hunks(
20497 stage,
20498 &hunks
20499 .map(|hunk| buffer_diff::DiffHunk {
20500 buffer_range: hunk.buffer_range,
20501 // We don't need to pass in word diffs here because they're only used for rendering and
20502 // this function changes internal state
20503 base_word_diffs: Vec::default(),
20504 buffer_word_diffs: Vec::default(),
20505 diff_base_byte_range: hunk.diff_base_byte_range.start.0
20506 ..hunk.diff_base_byte_range.end.0,
20507 secondary_status: hunk.status.secondary,
20508 range: Point::zero()..Point::zero(), // unused
20509 })
20510 .collect::<Vec<_>>(),
20511 &buffer_snapshot,
20512 file_exists,
20513 cx,
20514 )
20515 });
20516 None
20517 }
20518
20519 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
20520 let ranges: Vec<_> = self
20521 .selections
20522 .disjoint_anchors()
20523 .iter()
20524 .map(|s| s.range())
20525 .collect();
20526 self.buffer
20527 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
20528 }
20529
20530 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
20531 self.buffer.update(cx, |buffer, cx| {
20532 let ranges = vec![Anchor::min()..Anchor::max()];
20533 if !buffer.all_diff_hunks_expanded()
20534 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
20535 {
20536 buffer.collapse_diff_hunks(ranges, cx);
20537 true
20538 } else {
20539 false
20540 }
20541 })
20542 }
20543
20544 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
20545 if self.buffer.read(cx).all_diff_hunks_expanded() {
20546 return true;
20547 }
20548 let ranges = vec![Anchor::min()..Anchor::max()];
20549 self.buffer
20550 .read(cx)
20551 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
20552 }
20553
20554 fn toggle_diff_hunks_in_ranges(
20555 &mut self,
20556 ranges: Vec<Range<Anchor>>,
20557 cx: &mut Context<Editor>,
20558 ) {
20559 self.buffer.update(cx, |buffer, cx| {
20560 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
20561 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
20562 })
20563 }
20564
20565 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
20566 self.buffer.update(cx, |buffer, cx| {
20567 let snapshot = buffer.snapshot(cx);
20568 let excerpt_id = range.end.excerpt_id;
20569 let point_range = range.to_point(&snapshot);
20570 let expand = !buffer.single_hunk_is_expanded(range, cx);
20571 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
20572 })
20573 }
20574
20575 pub(crate) fn apply_all_diff_hunks(
20576 &mut self,
20577 _: &ApplyAllDiffHunks,
20578 window: &mut Window,
20579 cx: &mut Context<Self>,
20580 ) {
20581 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20582
20583 let buffers = self.buffer.read(cx).all_buffers();
20584 for branch_buffer in buffers {
20585 branch_buffer.update(cx, |branch_buffer, cx| {
20586 branch_buffer.merge_into_base(Vec::new(), cx);
20587 });
20588 }
20589
20590 if let Some(project) = self.project.clone() {
20591 self.save(
20592 SaveOptions {
20593 format: true,
20594 autosave: false,
20595 },
20596 project,
20597 window,
20598 cx,
20599 )
20600 .detach_and_log_err(cx);
20601 }
20602 }
20603
20604 pub(crate) fn apply_selected_diff_hunks(
20605 &mut self,
20606 _: &ApplyDiffHunk,
20607 window: &mut Window,
20608 cx: &mut Context<Self>,
20609 ) {
20610 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20611 let snapshot = self.snapshot(window, cx);
20612 let hunks = snapshot.hunks_for_ranges(
20613 self.selections
20614 .all(&snapshot.display_snapshot)
20615 .into_iter()
20616 .map(|selection| selection.range()),
20617 );
20618 let mut ranges_by_buffer = HashMap::default();
20619 self.transact(window, cx, |editor, _window, cx| {
20620 for hunk in hunks {
20621 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
20622 ranges_by_buffer
20623 .entry(buffer.clone())
20624 .or_insert_with(Vec::new)
20625 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
20626 }
20627 }
20628
20629 for (buffer, ranges) in ranges_by_buffer {
20630 buffer.update(cx, |buffer, cx| {
20631 buffer.merge_into_base(ranges, cx);
20632 });
20633 }
20634 });
20635
20636 if let Some(project) = self.project.clone() {
20637 self.save(
20638 SaveOptions {
20639 format: true,
20640 autosave: false,
20641 },
20642 project,
20643 window,
20644 cx,
20645 )
20646 .detach_and_log_err(cx);
20647 }
20648 }
20649
20650 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
20651 if hovered != self.gutter_hovered {
20652 self.gutter_hovered = hovered;
20653 cx.notify();
20654 }
20655 }
20656
20657 pub fn insert_blocks(
20658 &mut self,
20659 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
20660 autoscroll: Option<Autoscroll>,
20661 cx: &mut Context<Self>,
20662 ) -> Vec<CustomBlockId> {
20663 let blocks = self
20664 .display_map
20665 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
20666 if let Some(autoscroll) = autoscroll {
20667 self.request_autoscroll(autoscroll, cx);
20668 }
20669 cx.notify();
20670 blocks
20671 }
20672
20673 pub fn resize_blocks(
20674 &mut self,
20675 heights: HashMap<CustomBlockId, u32>,
20676 autoscroll: Option<Autoscroll>,
20677 cx: &mut Context<Self>,
20678 ) {
20679 self.display_map
20680 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
20681 if let Some(autoscroll) = autoscroll {
20682 self.request_autoscroll(autoscroll, cx);
20683 }
20684 cx.notify();
20685 }
20686
20687 pub fn replace_blocks(
20688 &mut self,
20689 renderers: HashMap<CustomBlockId, RenderBlock>,
20690 autoscroll: Option<Autoscroll>,
20691 cx: &mut Context<Self>,
20692 ) {
20693 self.display_map
20694 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
20695 if let Some(autoscroll) = autoscroll {
20696 self.request_autoscroll(autoscroll, cx);
20697 }
20698 cx.notify();
20699 }
20700
20701 pub fn remove_blocks(
20702 &mut self,
20703 block_ids: HashSet<CustomBlockId>,
20704 autoscroll: Option<Autoscroll>,
20705 cx: &mut Context<Self>,
20706 ) {
20707 self.display_map.update(cx, |display_map, cx| {
20708 display_map.remove_blocks(block_ids, cx)
20709 });
20710 if let Some(autoscroll) = autoscroll {
20711 self.request_autoscroll(autoscroll, cx);
20712 }
20713 cx.notify();
20714 }
20715
20716 pub fn row_for_block(
20717 &self,
20718 block_id: CustomBlockId,
20719 cx: &mut Context<Self>,
20720 ) -> Option<DisplayRow> {
20721 self.display_map
20722 .update(cx, |map, cx| map.row_for_block(block_id, cx))
20723 }
20724
20725 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
20726 self.focused_block = Some(focused_block);
20727 }
20728
20729 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
20730 self.focused_block.take()
20731 }
20732
20733 pub fn insert_creases(
20734 &mut self,
20735 creases: impl IntoIterator<Item = Crease<Anchor>>,
20736 cx: &mut Context<Self>,
20737 ) -> Vec<CreaseId> {
20738 self.display_map
20739 .update(cx, |map, cx| map.insert_creases(creases, cx))
20740 }
20741
20742 pub fn remove_creases(
20743 &mut self,
20744 ids: impl IntoIterator<Item = CreaseId>,
20745 cx: &mut Context<Self>,
20746 ) -> Vec<(CreaseId, Range<Anchor>)> {
20747 self.display_map
20748 .update(cx, |map, cx| map.remove_creases(ids, cx))
20749 }
20750
20751 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
20752 self.display_map
20753 .update(cx, |map, cx| map.snapshot(cx))
20754 .longest_row()
20755 }
20756
20757 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
20758 self.display_map
20759 .update(cx, |map, cx| map.snapshot(cx))
20760 .max_point()
20761 }
20762
20763 pub fn text(&self, cx: &App) -> String {
20764 self.buffer.read(cx).read(cx).text()
20765 }
20766
20767 pub fn is_empty(&self, cx: &App) -> bool {
20768 self.buffer.read(cx).read(cx).is_empty()
20769 }
20770
20771 pub fn text_option(&self, cx: &App) -> Option<String> {
20772 let text = self.text(cx);
20773 let text = text.trim();
20774
20775 if text.is_empty() {
20776 return None;
20777 }
20778
20779 Some(text.to_string())
20780 }
20781
20782 pub fn set_text(
20783 &mut self,
20784 text: impl Into<Arc<str>>,
20785 window: &mut Window,
20786 cx: &mut Context<Self>,
20787 ) {
20788 self.transact(window, cx, |this, _, cx| {
20789 this.buffer
20790 .read(cx)
20791 .as_singleton()
20792 .expect("you can only call set_text on editors for singleton buffers")
20793 .update(cx, |buffer, cx| buffer.set_text(text, cx));
20794 });
20795 }
20796
20797 pub fn display_text(&self, cx: &mut App) -> String {
20798 self.display_map
20799 .update(cx, |map, cx| map.snapshot(cx))
20800 .text()
20801 }
20802
20803 fn create_minimap(
20804 &self,
20805 minimap_settings: MinimapSettings,
20806 window: &mut Window,
20807 cx: &mut Context<Self>,
20808 ) -> Option<Entity<Self>> {
20809 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
20810 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
20811 }
20812
20813 fn initialize_new_minimap(
20814 &self,
20815 minimap_settings: MinimapSettings,
20816 window: &mut Window,
20817 cx: &mut Context<Self>,
20818 ) -> Entity<Self> {
20819 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
20820 const MINIMAP_FONT_FAMILY: SharedString = SharedString::new_static(".ZedMono");
20821
20822 let mut minimap = Editor::new_internal(
20823 EditorMode::Minimap {
20824 parent: cx.weak_entity(),
20825 },
20826 self.buffer.clone(),
20827 None,
20828 Some(self.display_map.clone()),
20829 window,
20830 cx,
20831 );
20832 let my_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20833 let minimap_snapshot = minimap.display_map.update(cx, |map, cx| map.snapshot(cx));
20834 minimap.scroll_manager.clone_state(
20835 &self.scroll_manager,
20836 &my_snapshot,
20837 &minimap_snapshot,
20838 cx,
20839 );
20840 minimap.set_text_style_refinement(TextStyleRefinement {
20841 font_size: Some(MINIMAP_FONT_SIZE),
20842 font_weight: Some(MINIMAP_FONT_WEIGHT),
20843 font_family: Some(MINIMAP_FONT_FAMILY),
20844 ..Default::default()
20845 });
20846 minimap.update_minimap_configuration(minimap_settings, cx);
20847 cx.new(|_| minimap)
20848 }
20849
20850 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
20851 let current_line_highlight = minimap_settings
20852 .current_line_highlight
20853 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
20854 self.set_current_line_highlight(Some(current_line_highlight));
20855 }
20856
20857 pub fn minimap(&self) -> Option<&Entity<Self>> {
20858 self.minimap
20859 .as_ref()
20860 .filter(|_| self.minimap_visibility.visible())
20861 }
20862
20863 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
20864 let mut wrap_guides = smallvec![];
20865
20866 if self.show_wrap_guides == Some(false) {
20867 return wrap_guides;
20868 }
20869
20870 let settings = self.buffer.read(cx).language_settings(cx);
20871 if settings.show_wrap_guides {
20872 match self.soft_wrap_mode(cx) {
20873 SoftWrap::Column(soft_wrap) => {
20874 wrap_guides.push((soft_wrap as usize, true));
20875 }
20876 SoftWrap::Bounded(soft_wrap) => {
20877 wrap_guides.push((soft_wrap as usize, true));
20878 }
20879 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
20880 }
20881 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
20882 }
20883
20884 wrap_guides
20885 }
20886
20887 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
20888 let settings = self.buffer.read(cx).language_settings(cx);
20889 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
20890 match mode {
20891 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
20892 SoftWrap::None
20893 }
20894 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
20895 language_settings::SoftWrap::PreferredLineLength => {
20896 SoftWrap::Column(settings.preferred_line_length)
20897 }
20898 language_settings::SoftWrap::Bounded => {
20899 SoftWrap::Bounded(settings.preferred_line_length)
20900 }
20901 }
20902 }
20903
20904 pub fn set_soft_wrap_mode(
20905 &mut self,
20906 mode: language_settings::SoftWrap,
20907 cx: &mut Context<Self>,
20908 ) {
20909 self.soft_wrap_mode_override = Some(mode);
20910 cx.notify();
20911 }
20912
20913 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
20914 self.hard_wrap = hard_wrap;
20915 cx.notify();
20916 }
20917
20918 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
20919 self.text_style_refinement = Some(style);
20920 }
20921
20922 /// called by the Element so we know what style we were most recently rendered with.
20923 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
20924 // We intentionally do not inform the display map about the minimap style
20925 // so that wrapping is not recalculated and stays consistent for the editor
20926 // and its linked minimap.
20927 if !self.mode.is_minimap() {
20928 let font = style.text.font();
20929 let font_size = style.text.font_size.to_pixels(window.rem_size());
20930 let display_map = self
20931 .placeholder_display_map
20932 .as_ref()
20933 .filter(|_| self.is_empty(cx))
20934 .unwrap_or(&self.display_map);
20935
20936 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
20937 }
20938 self.style = Some(style);
20939 }
20940
20941 pub fn style(&mut self, cx: &App) -> &EditorStyle {
20942 if self.style.is_none() {
20943 self.style = Some(self.create_style(cx));
20944 }
20945 self.style.as_ref().unwrap()
20946 }
20947
20948 // Called by the element. This method is not designed to be called outside of the editor
20949 // element's layout code because it does not notify when rewrapping is computed synchronously.
20950 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
20951 if self.is_empty(cx) {
20952 self.placeholder_display_map
20953 .as_ref()
20954 .map_or(false, |display_map| {
20955 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
20956 })
20957 } else {
20958 self.display_map
20959 .update(cx, |map, cx| map.set_wrap_width(width, cx))
20960 }
20961 }
20962
20963 pub fn set_soft_wrap(&mut self) {
20964 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
20965 }
20966
20967 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
20968 if self.soft_wrap_mode_override.is_some() {
20969 self.soft_wrap_mode_override.take();
20970 } else {
20971 let soft_wrap = match self.soft_wrap_mode(cx) {
20972 SoftWrap::GitDiff => return,
20973 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
20974 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
20975 language_settings::SoftWrap::None
20976 }
20977 };
20978 self.soft_wrap_mode_override = Some(soft_wrap);
20979 }
20980 cx.notify();
20981 }
20982
20983 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
20984 let Some(workspace) = self.workspace() else {
20985 return;
20986 };
20987 let fs = workspace.read(cx).app_state().fs.clone();
20988 let current_show = TabBarSettings::get_global(cx).show;
20989 update_settings_file(fs, cx, move |setting, _| {
20990 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
20991 });
20992 }
20993
20994 pub fn toggle_indent_guides(
20995 &mut self,
20996 _: &ToggleIndentGuides,
20997 _: &mut Window,
20998 cx: &mut Context<Self>,
20999 ) {
21000 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
21001 self.buffer
21002 .read(cx)
21003 .language_settings(cx)
21004 .indent_guides
21005 .enabled
21006 });
21007 self.show_indent_guides = Some(!currently_enabled);
21008 cx.notify();
21009 }
21010
21011 fn should_show_indent_guides(&self) -> Option<bool> {
21012 self.show_indent_guides
21013 }
21014
21015 pub fn disable_indent_guides_for_buffer(
21016 &mut self,
21017 buffer_id: BufferId,
21018 cx: &mut Context<Self>,
21019 ) {
21020 self.buffers_with_disabled_indent_guides.insert(buffer_id);
21021 cx.notify();
21022 }
21023
21024 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
21025 self.buffers_with_disabled_indent_guides
21026 .contains(&buffer_id)
21027 }
21028
21029 pub fn toggle_line_numbers(
21030 &mut self,
21031 _: &ToggleLineNumbers,
21032 _: &mut Window,
21033 cx: &mut Context<Self>,
21034 ) {
21035 let mut editor_settings = EditorSettings::get_global(cx).clone();
21036 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
21037 EditorSettings::override_global(editor_settings, cx);
21038 }
21039
21040 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
21041 if let Some(show_line_numbers) = self.show_line_numbers {
21042 return show_line_numbers;
21043 }
21044 EditorSettings::get_global(cx).gutter.line_numbers
21045 }
21046
21047 pub fn relative_line_numbers(&self, cx: &App) -> RelativeLineNumbers {
21048 match (
21049 self.use_relative_line_numbers,
21050 EditorSettings::get_global(cx).relative_line_numbers,
21051 ) {
21052 (None, setting) => setting,
21053 (Some(false), _) => RelativeLineNumbers::Disabled,
21054 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
21055 (Some(true), _) => RelativeLineNumbers::Enabled,
21056 }
21057 }
21058
21059 pub fn toggle_relative_line_numbers(
21060 &mut self,
21061 _: &ToggleRelativeLineNumbers,
21062 _: &mut Window,
21063 cx: &mut Context<Self>,
21064 ) {
21065 let is_relative = self.relative_line_numbers(cx);
21066 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
21067 }
21068
21069 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
21070 self.use_relative_line_numbers = is_relative;
21071 cx.notify();
21072 }
21073
21074 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
21075 self.show_gutter = show_gutter;
21076 cx.notify();
21077 }
21078
21079 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
21080 self.show_scrollbars = ScrollbarAxes {
21081 horizontal: show,
21082 vertical: show,
21083 };
21084 cx.notify();
21085 }
21086
21087 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21088 self.show_scrollbars.vertical = show;
21089 cx.notify();
21090 }
21091
21092 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21093 self.show_scrollbars.horizontal = show;
21094 cx.notify();
21095 }
21096
21097 pub fn set_minimap_visibility(
21098 &mut self,
21099 minimap_visibility: MinimapVisibility,
21100 window: &mut Window,
21101 cx: &mut Context<Self>,
21102 ) {
21103 if self.minimap_visibility != minimap_visibility {
21104 if minimap_visibility.visible() && self.minimap.is_none() {
21105 let minimap_settings = EditorSettings::get_global(cx).minimap;
21106 self.minimap =
21107 self.create_minimap(minimap_settings.with_show_override(), window, cx);
21108 }
21109 self.minimap_visibility = minimap_visibility;
21110 cx.notify();
21111 }
21112 }
21113
21114 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21115 self.set_show_scrollbars(false, cx);
21116 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
21117 }
21118
21119 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21120 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
21121 }
21122
21123 /// Normally the text in full mode and auto height editors is padded on the
21124 /// left side by roughly half a character width for improved hit testing.
21125 ///
21126 /// Use this method to disable this for cases where this is not wanted (e.g.
21127 /// if you want to align the editor text with some other text above or below)
21128 /// or if you want to add this padding to single-line editors.
21129 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
21130 self.offset_content = offset_content;
21131 cx.notify();
21132 }
21133
21134 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
21135 self.show_line_numbers = Some(show_line_numbers);
21136 cx.notify();
21137 }
21138
21139 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
21140 self.disable_expand_excerpt_buttons = true;
21141 cx.notify();
21142 }
21143
21144 pub fn set_number_deleted_lines(&mut self, number: bool, cx: &mut Context<Self>) {
21145 self.number_deleted_lines = number;
21146 cx.notify();
21147 }
21148
21149 pub fn set_delegate_expand_excerpts(&mut self, delegate: bool) {
21150 self.delegate_expand_excerpts = delegate;
21151 }
21152
21153 pub fn set_delegate_stage_and_restore(&mut self, delegate: bool) {
21154 self.delegate_stage_and_restore = delegate;
21155 }
21156
21157 pub fn set_on_local_selections_changed(
21158 &mut self,
21159 callback: Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
21160 ) {
21161 self.on_local_selections_changed = callback;
21162 }
21163
21164 pub fn set_suppress_selection_callback(&mut self, suppress: bool) {
21165 self.suppress_selection_callback = suppress;
21166 }
21167
21168 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
21169 self.show_git_diff_gutter = Some(show_git_diff_gutter);
21170 cx.notify();
21171 }
21172
21173 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
21174 self.show_code_actions = Some(show_code_actions);
21175 cx.notify();
21176 }
21177
21178 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
21179 self.show_runnables = Some(show_runnables);
21180 cx.notify();
21181 }
21182
21183 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
21184 self.show_breakpoints = Some(show_breakpoints);
21185 cx.notify();
21186 }
21187
21188 pub fn set_show_diff_review_button(&mut self, show: bool, cx: &mut Context<Self>) {
21189 self.show_diff_review_button = show;
21190 cx.notify();
21191 }
21192
21193 pub fn show_diff_review_button(&self) -> bool {
21194 self.show_diff_review_button
21195 }
21196
21197 pub fn render_diff_review_button(
21198 &self,
21199 display_row: DisplayRow,
21200 width: Pixels,
21201 cx: &mut Context<Self>,
21202 ) -> impl IntoElement {
21203 let text_color = cx.theme().colors().text;
21204 let icon_color = cx.theme().colors().icon_accent;
21205
21206 h_flex()
21207 .id("diff_review_button")
21208 .cursor_pointer()
21209 .w(width - px(1.))
21210 .h(relative(0.9))
21211 .justify_center()
21212 .rounded_sm()
21213 .border_1()
21214 .border_color(text_color.opacity(0.1))
21215 .bg(text_color.opacity(0.15))
21216 .hover(|s| {
21217 s.bg(icon_color.opacity(0.4))
21218 .border_color(icon_color.opacity(0.5))
21219 })
21220 .child(Icon::new(IconName::Plus).size(IconSize::Small))
21221 .tooltip(Tooltip::text("Add Review (drag to select multiple lines)"))
21222 .on_mouse_down(
21223 gpui::MouseButton::Left,
21224 cx.listener(move |editor, _event: &gpui::MouseDownEvent, window, cx| {
21225 editor.start_diff_review_drag(display_row, window, cx);
21226 }),
21227 )
21228 }
21229
21230 pub fn start_diff_review_drag(
21231 &mut self,
21232 display_row: DisplayRow,
21233 window: &mut Window,
21234 cx: &mut Context<Self>,
21235 ) {
21236 let snapshot = self.snapshot(window, cx);
21237 let point = snapshot
21238 .display_snapshot
21239 .display_point_to_point(DisplayPoint::new(display_row, 0), Bias::Left);
21240 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21241 self.diff_review_drag_state = Some(DiffReviewDragState {
21242 start_anchor: anchor,
21243 current_anchor: anchor,
21244 });
21245 cx.notify();
21246 }
21247
21248 pub fn update_diff_review_drag(
21249 &mut self,
21250 display_row: DisplayRow,
21251 window: &mut Window,
21252 cx: &mut Context<Self>,
21253 ) {
21254 if self.diff_review_drag_state.is_none() {
21255 return;
21256 }
21257 let snapshot = self.snapshot(window, cx);
21258 let point = snapshot
21259 .display_snapshot
21260 .display_point_to_point(display_row.as_display_point(), Bias::Left);
21261 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21262 if let Some(drag_state) = &mut self.diff_review_drag_state {
21263 drag_state.current_anchor = anchor;
21264 cx.notify();
21265 }
21266 }
21267
21268 pub fn end_diff_review_drag(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21269 if let Some(drag_state) = self.diff_review_drag_state.take() {
21270 let snapshot = self.snapshot(window, cx);
21271 let range = drag_state.row_range(&snapshot.display_snapshot);
21272 self.show_diff_review_overlay(*range.start()..*range.end(), window, cx);
21273 }
21274 cx.notify();
21275 }
21276
21277 pub fn cancel_diff_review_drag(&mut self, cx: &mut Context<Self>) {
21278 self.diff_review_drag_state = None;
21279 cx.notify();
21280 }
21281
21282 /// Calculates the appropriate block height for the diff review overlay.
21283 /// Height is in lines: 2 for input row, 1 for header when comments exist,
21284 /// and 2 lines per comment when expanded.
21285 fn calculate_overlay_height(
21286 &self,
21287 hunk_key: &DiffHunkKey,
21288 comments_expanded: bool,
21289 snapshot: &MultiBufferSnapshot,
21290 ) -> u32 {
21291 let comment_count = self.hunk_comment_count(hunk_key, snapshot);
21292 let base_height: u32 = 2; // Input row with avatar and buttons
21293
21294 if comment_count == 0 {
21295 base_height
21296 } else if comments_expanded {
21297 // Header (1 line) + 2 lines per comment
21298 base_height + 1 + (comment_count as u32 * 2)
21299 } else {
21300 // Just header when collapsed
21301 base_height + 1
21302 }
21303 }
21304
21305 pub fn show_diff_review_overlay(
21306 &mut self,
21307 display_range: Range<DisplayRow>,
21308 window: &mut Window,
21309 cx: &mut Context<Self>,
21310 ) {
21311 let Range { start, end } = display_range.sorted();
21312
21313 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21314 let editor_snapshot = self.snapshot(window, cx);
21315
21316 // Convert display rows to multibuffer points
21317 let start_point = editor_snapshot
21318 .display_snapshot
21319 .display_point_to_point(start.as_display_point(), Bias::Left);
21320 let end_point = editor_snapshot
21321 .display_snapshot
21322 .display_point_to_point(end.as_display_point(), Bias::Left);
21323 let end_multi_buffer_row = MultiBufferRow(end_point.row);
21324
21325 // Create anchor range for the selected lines (start of first line to end of last line)
21326 let line_end = Point::new(
21327 end_point.row,
21328 buffer_snapshot.line_len(end_multi_buffer_row),
21329 );
21330 let anchor_range =
21331 buffer_snapshot.anchor_after(start_point)..buffer_snapshot.anchor_before(line_end);
21332
21333 // Compute the hunk key for this display row
21334 let file_path = buffer_snapshot
21335 .file_at(start_point)
21336 .map(|file: &Arc<dyn language::File>| file.path().clone())
21337 .unwrap_or_else(|| Arc::from(util::rel_path::RelPath::empty()));
21338 let hunk_start_anchor = buffer_snapshot.anchor_before(start_point);
21339 let new_hunk_key = DiffHunkKey {
21340 file_path,
21341 hunk_start_anchor,
21342 };
21343
21344 // Check if we already have an overlay for this hunk
21345 if let Some(existing_overlay) = self.diff_review_overlays.iter().find(|overlay| {
21346 Self::hunk_keys_match(&overlay.hunk_key, &new_hunk_key, &buffer_snapshot)
21347 }) {
21348 // Just focus the existing overlay's prompt editor
21349 let focus_handle = existing_overlay.prompt_editor.focus_handle(cx);
21350 window.focus(&focus_handle, cx);
21351 return;
21352 }
21353
21354 // Dismiss overlays that have no comments for their hunks
21355 self.dismiss_overlays_without_comments(cx);
21356
21357 // Get the current user's avatar URI from the project's user_store
21358 let user_avatar_uri = self.project.as_ref().and_then(|project| {
21359 let user_store = project.read(cx).user_store();
21360 user_store
21361 .read(cx)
21362 .current_user()
21363 .map(|user| user.avatar_uri.clone())
21364 });
21365
21366 // Create anchor at the end of the last row so the block appears immediately below it
21367 // Use multibuffer coordinates for anchor creation
21368 let line_len = buffer_snapshot.line_len(end_multi_buffer_row);
21369 let anchor = buffer_snapshot.anchor_after(Point::new(end_multi_buffer_row.0, line_len));
21370
21371 // Use the hunk key we already computed
21372 let hunk_key = new_hunk_key;
21373
21374 // Create the prompt editor for the review input
21375 let prompt_editor = cx.new(|cx| {
21376 let mut editor = Editor::single_line(window, cx);
21377 editor.set_placeholder_text("Add a review comment...", window, cx);
21378 editor
21379 });
21380
21381 // Register the Newline action on the prompt editor to submit the review
21382 let parent_editor = cx.entity().downgrade();
21383 let subscription = prompt_editor.update(cx, |prompt_editor, _cx| {
21384 prompt_editor.register_action({
21385 let parent_editor = parent_editor.clone();
21386 move |_: &crate::actions::Newline, window, cx| {
21387 if let Some(editor) = parent_editor.upgrade() {
21388 editor.update(cx, |editor, cx| {
21389 editor.submit_diff_review_comment(window, cx);
21390 });
21391 }
21392 }
21393 })
21394 });
21395
21396 // Calculate initial height based on existing comments for this hunk
21397 let initial_height = self.calculate_overlay_height(&hunk_key, true, &buffer_snapshot);
21398
21399 // Create the overlay block
21400 let prompt_editor_for_render = prompt_editor.clone();
21401 let hunk_key_for_render = hunk_key.clone();
21402 let editor_handle = cx.entity().downgrade();
21403 let block = BlockProperties {
21404 style: BlockStyle::Sticky,
21405 placement: BlockPlacement::Below(anchor),
21406 height: Some(initial_height),
21407 render: Arc::new(move |cx| {
21408 Self::render_diff_review_overlay(
21409 &prompt_editor_for_render,
21410 &hunk_key_for_render,
21411 &editor_handle,
21412 cx,
21413 )
21414 }),
21415 priority: 0,
21416 };
21417
21418 let block_ids = self.insert_blocks([block], None, cx);
21419 let Some(block_id) = block_ids.into_iter().next() else {
21420 log::error!("Failed to insert diff review overlay block");
21421 return;
21422 };
21423
21424 self.diff_review_overlays.push(DiffReviewOverlay {
21425 anchor_range,
21426 block_id,
21427 prompt_editor: prompt_editor.clone(),
21428 hunk_key,
21429 comments_expanded: true,
21430 inline_edit_editors: HashMap::default(),
21431 inline_edit_subscriptions: HashMap::default(),
21432 user_avatar_uri,
21433 _subscription: subscription,
21434 });
21435
21436 // Focus the prompt editor
21437 let focus_handle = prompt_editor.focus_handle(cx);
21438 window.focus(&focus_handle, cx);
21439
21440 cx.notify();
21441 }
21442
21443 /// Dismisses all diff review overlays.
21444 pub fn dismiss_all_diff_review_overlays(&mut self, cx: &mut Context<Self>) {
21445 if self.diff_review_overlays.is_empty() {
21446 return;
21447 }
21448 let block_ids: HashSet<_> = self
21449 .diff_review_overlays
21450 .drain(..)
21451 .map(|overlay| overlay.block_id)
21452 .collect();
21453 self.remove_blocks(block_ids, None, cx);
21454 cx.notify();
21455 }
21456
21457 /// Dismisses overlays that have no comments stored for their hunks.
21458 /// Keeps overlays that have at least one comment.
21459 fn dismiss_overlays_without_comments(&mut self, cx: &mut Context<Self>) {
21460 let snapshot = self.buffer.read(cx).snapshot(cx);
21461
21462 // First, compute which overlays have comments (to avoid borrow issues with retain)
21463 let overlays_with_comments: Vec<bool> = self
21464 .diff_review_overlays
21465 .iter()
21466 .map(|overlay| self.hunk_comment_count(&overlay.hunk_key, &snapshot) > 0)
21467 .collect();
21468
21469 // Now collect block IDs to remove and retain overlays
21470 let mut block_ids_to_remove = HashSet::default();
21471 let mut index = 0;
21472 self.diff_review_overlays.retain(|overlay| {
21473 let has_comments = overlays_with_comments[index];
21474 index += 1;
21475 if !has_comments {
21476 block_ids_to_remove.insert(overlay.block_id);
21477 }
21478 has_comments
21479 });
21480
21481 if !block_ids_to_remove.is_empty() {
21482 self.remove_blocks(block_ids_to_remove, None, cx);
21483 cx.notify();
21484 }
21485 }
21486
21487 /// Refreshes the diff review overlay block to update its height and render function.
21488 /// Uses resize_blocks and replace_blocks to avoid visual flicker from remove+insert.
21489 fn refresh_diff_review_overlay_height(
21490 &mut self,
21491 hunk_key: &DiffHunkKey,
21492 _window: &mut Window,
21493 cx: &mut Context<Self>,
21494 ) {
21495 // Extract all needed data from overlay first to avoid borrow conflicts
21496 let snapshot = self.buffer.read(cx).snapshot(cx);
21497 let (comments_expanded, block_id, prompt_editor) = {
21498 let Some(overlay) = self
21499 .diff_review_overlays
21500 .iter()
21501 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
21502 else {
21503 return;
21504 };
21505
21506 (
21507 overlay.comments_expanded,
21508 overlay.block_id,
21509 overlay.prompt_editor.clone(),
21510 )
21511 };
21512
21513 // Calculate new height
21514 let snapshot = self.buffer.read(cx).snapshot(cx);
21515 let new_height = self.calculate_overlay_height(hunk_key, comments_expanded, &snapshot);
21516
21517 // Update the block height using resize_blocks (avoids flicker)
21518 let mut heights = HashMap::default();
21519 heights.insert(block_id, new_height);
21520 self.resize_blocks(heights, None, cx);
21521
21522 // Update the render function using replace_blocks (avoids flicker)
21523 let hunk_key_for_render = hunk_key.clone();
21524 let editor_handle = cx.entity().downgrade();
21525 let render: Arc<dyn Fn(&mut BlockContext) -> AnyElement + Send + Sync> =
21526 Arc::new(move |cx| {
21527 Self::render_diff_review_overlay(
21528 &prompt_editor,
21529 &hunk_key_for_render,
21530 &editor_handle,
21531 cx,
21532 )
21533 });
21534
21535 let mut renderers = HashMap::default();
21536 renderers.insert(block_id, render);
21537 self.replace_blocks(renderers, None, cx);
21538 }
21539
21540 /// Action handler for SubmitDiffReviewComment.
21541 pub fn submit_diff_review_comment_action(
21542 &mut self,
21543 _: &SubmitDiffReviewComment,
21544 window: &mut Window,
21545 cx: &mut Context<Self>,
21546 ) {
21547 self.submit_diff_review_comment(window, cx);
21548 }
21549
21550 /// Stores the diff review comment locally.
21551 /// Comments are stored per-hunk and can later be batch-submitted to the Agent panel.
21552 pub fn submit_diff_review_comment(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21553 // Find the overlay that currently has focus
21554 let overlay_index = self
21555 .diff_review_overlays
21556 .iter()
21557 .position(|overlay| overlay.prompt_editor.focus_handle(cx).is_focused(window));
21558 let Some(overlay_index) = overlay_index else {
21559 return;
21560 };
21561 let overlay = &self.diff_review_overlays[overlay_index];
21562
21563 let comment_text = overlay.prompt_editor.read(cx).text(cx).trim().to_string();
21564 if comment_text.is_empty() {
21565 return;
21566 }
21567
21568 let anchor_range = overlay.anchor_range.clone();
21569 let hunk_key = overlay.hunk_key.clone();
21570
21571 self.add_review_comment(hunk_key.clone(), comment_text, anchor_range, cx);
21572
21573 // Clear the prompt editor but keep the overlay open
21574 if let Some(overlay) = self.diff_review_overlays.get(overlay_index) {
21575 overlay.prompt_editor.update(cx, |editor, cx| {
21576 editor.clear(window, cx);
21577 });
21578 }
21579
21580 // Refresh the overlay to update the block height for the new comment
21581 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
21582
21583 cx.notify();
21584 }
21585
21586 /// Returns the prompt editor for the diff review overlay, if one is active.
21587 /// This is primarily used for testing.
21588 pub fn diff_review_prompt_editor(&self) -> Option<&Entity<Editor>> {
21589 self.diff_review_overlays
21590 .first()
21591 .map(|overlay| &overlay.prompt_editor)
21592 }
21593
21594 /// Returns the line range for the first diff review overlay, if one is active.
21595 /// Returns (start_row, end_row) as physical line numbers in the underlying file.
21596 pub fn diff_review_line_range(&self, cx: &App) -> Option<(u32, u32)> {
21597 let overlay = self.diff_review_overlays.first()?;
21598 let snapshot = self.buffer.read(cx).snapshot(cx);
21599 let start_point = overlay.anchor_range.start.to_point(&snapshot);
21600 let end_point = overlay.anchor_range.end.to_point(&snapshot);
21601 let start_row = snapshot
21602 .point_to_buffer_point(start_point)
21603 .map(|(_, p, _)| p.row)
21604 .unwrap_or(start_point.row);
21605 let end_row = snapshot
21606 .point_to_buffer_point(end_point)
21607 .map(|(_, p, _)| p.row)
21608 .unwrap_or(end_point.row);
21609 Some((start_row, end_row))
21610 }
21611
21612 /// Sets whether the comments section is expanded in the diff review overlay.
21613 /// This is primarily used for testing.
21614 pub fn set_diff_review_comments_expanded(&mut self, expanded: bool, cx: &mut Context<Self>) {
21615 for overlay in &mut self.diff_review_overlays {
21616 overlay.comments_expanded = expanded;
21617 }
21618 cx.notify();
21619 }
21620
21621 /// Compares two DiffHunkKeys for equality by resolving their anchors.
21622 fn hunk_keys_match(a: &DiffHunkKey, b: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> bool {
21623 a.file_path == b.file_path
21624 && a.hunk_start_anchor.to_point(snapshot) == b.hunk_start_anchor.to_point(snapshot)
21625 }
21626
21627 /// Returns comments for a specific hunk, ordered by creation time.
21628 pub fn comments_for_hunk<'a>(
21629 &'a self,
21630 key: &DiffHunkKey,
21631 snapshot: &MultiBufferSnapshot,
21632 ) -> &'a [StoredReviewComment] {
21633 let key_point = key.hunk_start_anchor.to_point(snapshot);
21634 self.stored_review_comments
21635 .iter()
21636 .find(|(k, _)| {
21637 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
21638 })
21639 .map(|(_, comments)| comments.as_slice())
21640 .unwrap_or(&[])
21641 }
21642
21643 /// Returns the total count of stored review comments across all hunks.
21644 pub fn total_review_comment_count(&self) -> usize {
21645 self.stored_review_comments
21646 .iter()
21647 .map(|(_, v)| v.len())
21648 .sum()
21649 }
21650
21651 /// Returns the count of comments for a specific hunk.
21652 pub fn hunk_comment_count(&self, key: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> usize {
21653 let key_point = key.hunk_start_anchor.to_point(snapshot);
21654 self.stored_review_comments
21655 .iter()
21656 .find(|(k, _)| {
21657 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
21658 })
21659 .map(|(_, v)| v.len())
21660 .unwrap_or(0)
21661 }
21662
21663 /// Adds a new review comment to a specific hunk.
21664 pub fn add_review_comment(
21665 &mut self,
21666 hunk_key: DiffHunkKey,
21667 comment: String,
21668 anchor_range: Range<Anchor>,
21669 cx: &mut Context<Self>,
21670 ) -> usize {
21671 let id = self.next_review_comment_id;
21672 self.next_review_comment_id += 1;
21673
21674 let stored_comment = StoredReviewComment::new(id, comment, anchor_range);
21675
21676 let snapshot = self.buffer.read(cx).snapshot(cx);
21677 let key_point = hunk_key.hunk_start_anchor.to_point(&snapshot);
21678
21679 // Find existing entry for this hunk or add a new one
21680 if let Some((_, comments)) = self.stored_review_comments.iter_mut().find(|(k, _)| {
21681 k.file_path == hunk_key.file_path
21682 && k.hunk_start_anchor.to_point(&snapshot) == key_point
21683 }) {
21684 comments.push(stored_comment);
21685 } else {
21686 self.stored_review_comments
21687 .push((hunk_key, vec![stored_comment]));
21688 }
21689
21690 cx.emit(EditorEvent::ReviewCommentsChanged {
21691 total_count: self.total_review_comment_count(),
21692 });
21693 cx.notify();
21694 id
21695 }
21696
21697 /// Removes a review comment by ID from any hunk.
21698 pub fn remove_review_comment(&mut self, id: usize, cx: &mut Context<Self>) -> bool {
21699 for (_, comments) in self.stored_review_comments.iter_mut() {
21700 if let Some(index) = comments.iter().position(|c| c.id == id) {
21701 comments.remove(index);
21702 cx.emit(EditorEvent::ReviewCommentsChanged {
21703 total_count: self.total_review_comment_count(),
21704 });
21705 cx.notify();
21706 return true;
21707 }
21708 }
21709 false
21710 }
21711
21712 /// Updates a review comment's text by ID.
21713 pub fn update_review_comment(
21714 &mut self,
21715 id: usize,
21716 new_comment: String,
21717 cx: &mut Context<Self>,
21718 ) -> bool {
21719 for (_, comments) in self.stored_review_comments.iter_mut() {
21720 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
21721 comment.comment = new_comment;
21722 comment.is_editing = false;
21723 cx.emit(EditorEvent::ReviewCommentsChanged {
21724 total_count: self.total_review_comment_count(),
21725 });
21726 cx.notify();
21727 return true;
21728 }
21729 }
21730 false
21731 }
21732
21733 /// Sets a comment's editing state.
21734 pub fn set_comment_editing(&mut self, id: usize, is_editing: bool, cx: &mut Context<Self>) {
21735 for (_, comments) in self.stored_review_comments.iter_mut() {
21736 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
21737 comment.is_editing = is_editing;
21738 cx.notify();
21739 return;
21740 }
21741 }
21742 }
21743
21744 /// Takes all stored comments from all hunks, clearing the storage.
21745 /// Returns a Vec of (hunk_key, comments) pairs.
21746 pub fn take_all_review_comments(
21747 &mut self,
21748 cx: &mut Context<Self>,
21749 ) -> Vec<(DiffHunkKey, Vec<StoredReviewComment>)> {
21750 // Dismiss all overlays when taking comments (e.g., when sending to agent)
21751 self.dismiss_all_diff_review_overlays(cx);
21752 let comments = std::mem::take(&mut self.stored_review_comments);
21753 // Reset the ID counter since all comments have been taken
21754 self.next_review_comment_id = 0;
21755 cx.emit(EditorEvent::ReviewCommentsChanged { total_count: 0 });
21756 cx.notify();
21757 comments
21758 }
21759
21760 /// Removes review comments whose anchors are no longer valid or whose
21761 /// associated diff hunks no longer exist.
21762 ///
21763 /// This should be called when the buffer changes to prevent orphaned comments
21764 /// from accumulating.
21765 pub fn cleanup_orphaned_review_comments(&mut self, cx: &mut Context<Self>) {
21766 let snapshot = self.buffer.read(cx).snapshot(cx);
21767 let original_count = self.total_review_comment_count();
21768
21769 // Remove comments with invalid hunk anchors
21770 self.stored_review_comments
21771 .retain(|(hunk_key, _)| hunk_key.hunk_start_anchor.is_valid(&snapshot));
21772
21773 // Also clean up individual comments with invalid anchor ranges
21774 for (_, comments) in &mut self.stored_review_comments {
21775 comments.retain(|comment| {
21776 comment.range.start.is_valid(&snapshot) && comment.range.end.is_valid(&snapshot)
21777 });
21778 }
21779
21780 // Remove empty hunk entries
21781 self.stored_review_comments
21782 .retain(|(_, comments)| !comments.is_empty());
21783
21784 let new_count = self.total_review_comment_count();
21785 if new_count != original_count {
21786 cx.emit(EditorEvent::ReviewCommentsChanged {
21787 total_count: new_count,
21788 });
21789 cx.notify();
21790 }
21791 }
21792
21793 /// Toggles the expanded state of the comments section in the overlay.
21794 pub fn toggle_review_comments_expanded(
21795 &mut self,
21796 _: &ToggleReviewCommentsExpanded,
21797 window: &mut Window,
21798 cx: &mut Context<Self>,
21799 ) {
21800 // Find the overlay that currently has focus, or use the first one
21801 let overlay_info = self.diff_review_overlays.iter_mut().find_map(|overlay| {
21802 if overlay.prompt_editor.focus_handle(cx).is_focused(window) {
21803 overlay.comments_expanded = !overlay.comments_expanded;
21804 Some(overlay.hunk_key.clone())
21805 } else {
21806 None
21807 }
21808 });
21809
21810 // If no focused overlay found, toggle the first one
21811 let hunk_key = overlay_info.or_else(|| {
21812 self.diff_review_overlays.first_mut().map(|overlay| {
21813 overlay.comments_expanded = !overlay.comments_expanded;
21814 overlay.hunk_key.clone()
21815 })
21816 });
21817
21818 if let Some(hunk_key) = hunk_key {
21819 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
21820 cx.notify();
21821 }
21822 }
21823
21824 /// Handles the EditReviewComment action - sets a comment into editing mode.
21825 pub fn edit_review_comment(
21826 &mut self,
21827 action: &EditReviewComment,
21828 window: &mut Window,
21829 cx: &mut Context<Self>,
21830 ) {
21831 let comment_id = action.id;
21832
21833 // Set the comment to editing mode
21834 self.set_comment_editing(comment_id, true, cx);
21835
21836 // Find the overlay that contains this comment and create an inline editor if needed
21837 // First, find which hunk this comment belongs to
21838 let hunk_key = self
21839 .stored_review_comments
21840 .iter()
21841 .find_map(|(key, comments)| {
21842 if comments.iter().any(|c| c.id == comment_id) {
21843 Some(key.clone())
21844 } else {
21845 None
21846 }
21847 });
21848
21849 let snapshot = self.buffer.read(cx).snapshot(cx);
21850 if let Some(hunk_key) = hunk_key {
21851 if let Some(overlay) = self
21852 .diff_review_overlays
21853 .iter_mut()
21854 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
21855 {
21856 if let std::collections::hash_map::Entry::Vacant(entry) =
21857 overlay.inline_edit_editors.entry(comment_id)
21858 {
21859 // Find the comment text
21860 let comment_text = self
21861 .stored_review_comments
21862 .iter()
21863 .flat_map(|(_, comments)| comments)
21864 .find(|c| c.id == comment_id)
21865 .map(|c| c.comment.clone())
21866 .unwrap_or_default();
21867
21868 // Create inline editor
21869 let parent_editor = cx.entity().downgrade();
21870 let inline_editor = cx.new(|cx| {
21871 let mut editor = Editor::single_line(window, cx);
21872 editor.set_text(&*comment_text, window, cx);
21873 // Select all text for easy replacement
21874 editor.select_all(&crate::actions::SelectAll, window, cx);
21875 editor
21876 });
21877
21878 // Register the Newline action to confirm the edit
21879 let subscription = inline_editor.update(cx, |inline_editor, _cx| {
21880 inline_editor.register_action({
21881 let parent_editor = parent_editor.clone();
21882 move |_: &crate::actions::Newline, window, cx| {
21883 if let Some(editor) = parent_editor.upgrade() {
21884 editor.update(cx, |editor, cx| {
21885 editor.confirm_edit_review_comment(comment_id, window, cx);
21886 });
21887 }
21888 }
21889 })
21890 });
21891
21892 // Store the subscription to keep the action handler alive
21893 overlay
21894 .inline_edit_subscriptions
21895 .insert(comment_id, subscription);
21896
21897 // Focus the inline editor
21898 let focus_handle = inline_editor.focus_handle(cx);
21899 window.focus(&focus_handle, cx);
21900
21901 entry.insert(inline_editor);
21902 }
21903 }
21904 }
21905
21906 cx.notify();
21907 }
21908
21909 /// Confirms an inline edit of a review comment.
21910 pub fn confirm_edit_review_comment(
21911 &mut self,
21912 comment_id: usize,
21913 _window: &mut Window,
21914 cx: &mut Context<Self>,
21915 ) {
21916 // Get the new text from the inline editor
21917 // Find the overlay containing this comment's inline editor
21918 let snapshot = self.buffer.read(cx).snapshot(cx);
21919 let hunk_key = self
21920 .stored_review_comments
21921 .iter()
21922 .find_map(|(key, comments)| {
21923 if comments.iter().any(|c| c.id == comment_id) {
21924 Some(key.clone())
21925 } else {
21926 None
21927 }
21928 });
21929
21930 let new_text = hunk_key
21931 .as_ref()
21932 .and_then(|hunk_key| {
21933 self.diff_review_overlays
21934 .iter()
21935 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
21936 })
21937 .as_ref()
21938 .and_then(|overlay| overlay.inline_edit_editors.get(&comment_id))
21939 .map(|editor| editor.read(cx).text(cx).trim().to_string());
21940
21941 if let Some(new_text) = new_text {
21942 if !new_text.is_empty() {
21943 self.update_review_comment(comment_id, new_text, cx);
21944 }
21945 }
21946
21947 // Remove the inline editor and its subscription
21948 if let Some(hunk_key) = hunk_key {
21949 if let Some(overlay) = self
21950 .diff_review_overlays
21951 .iter_mut()
21952 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
21953 {
21954 overlay.inline_edit_editors.remove(&comment_id);
21955 overlay.inline_edit_subscriptions.remove(&comment_id);
21956 }
21957 }
21958
21959 // Clear editing state
21960 self.set_comment_editing(comment_id, false, cx);
21961 }
21962
21963 /// Cancels an inline edit of a review comment.
21964 pub fn cancel_edit_review_comment(
21965 &mut self,
21966 comment_id: usize,
21967 _window: &mut Window,
21968 cx: &mut Context<Self>,
21969 ) {
21970 // Find which hunk this comment belongs to
21971 let hunk_key = self
21972 .stored_review_comments
21973 .iter()
21974 .find_map(|(key, comments)| {
21975 if comments.iter().any(|c| c.id == comment_id) {
21976 Some(key.clone())
21977 } else {
21978 None
21979 }
21980 });
21981
21982 // Remove the inline editor and its subscription
21983 if let Some(hunk_key) = hunk_key {
21984 let snapshot = self.buffer.read(cx).snapshot(cx);
21985 if let Some(overlay) = self
21986 .diff_review_overlays
21987 .iter_mut()
21988 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
21989 {
21990 overlay.inline_edit_editors.remove(&comment_id);
21991 overlay.inline_edit_subscriptions.remove(&comment_id);
21992 }
21993 }
21994
21995 // Clear editing state
21996 self.set_comment_editing(comment_id, false, cx);
21997 }
21998
21999 /// Action handler for ConfirmEditReviewComment.
22000 pub fn confirm_edit_review_comment_action(
22001 &mut self,
22002 action: &ConfirmEditReviewComment,
22003 window: &mut Window,
22004 cx: &mut Context<Self>,
22005 ) {
22006 self.confirm_edit_review_comment(action.id, window, cx);
22007 }
22008
22009 /// Action handler for CancelEditReviewComment.
22010 pub fn cancel_edit_review_comment_action(
22011 &mut self,
22012 action: &CancelEditReviewComment,
22013 window: &mut Window,
22014 cx: &mut Context<Self>,
22015 ) {
22016 self.cancel_edit_review_comment(action.id, window, cx);
22017 }
22018
22019 /// Handles the DeleteReviewComment action - removes a comment.
22020 pub fn delete_review_comment(
22021 &mut self,
22022 action: &DeleteReviewComment,
22023 window: &mut Window,
22024 cx: &mut Context<Self>,
22025 ) {
22026 // Get the hunk key before removing the comment
22027 // Find the hunk key from the comment itself
22028 let comment_id = action.id;
22029 let hunk_key = self
22030 .stored_review_comments
22031 .iter()
22032 .find_map(|(key, comments)| {
22033 if comments.iter().any(|c| c.id == comment_id) {
22034 Some(key.clone())
22035 } else {
22036 None
22037 }
22038 });
22039
22040 // Also get it from the overlay for refresh purposes
22041 let overlay_hunk_key = self
22042 .diff_review_overlays
22043 .first()
22044 .map(|o| o.hunk_key.clone());
22045
22046 self.remove_review_comment(action.id, cx);
22047
22048 // Refresh the overlay height after removing a comment
22049 if let Some(hunk_key) = hunk_key.or(overlay_hunk_key) {
22050 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22051 }
22052 }
22053
22054 fn render_diff_review_overlay(
22055 prompt_editor: &Entity<Editor>,
22056 hunk_key: &DiffHunkKey,
22057 editor_handle: &WeakEntity<Editor>,
22058 cx: &mut BlockContext,
22059 ) -> AnyElement {
22060 fn format_line_ranges(ranges: &[(u32, u32)]) -> Option<String> {
22061 if ranges.is_empty() {
22062 return None;
22063 }
22064 let formatted: Vec<String> = ranges
22065 .iter()
22066 .map(|(start, end)| {
22067 let start_line = start + 1;
22068 let end_line = end + 1;
22069 if start_line == end_line {
22070 format!("Line {start_line}")
22071 } else {
22072 format!("Lines {start_line}-{end_line}")
22073 }
22074 })
22075 .collect();
22076 // Don't show label for single line in single excerpt
22077 if ranges.len() == 1 && ranges[0].0 == ranges[0].1 {
22078 return None;
22079 }
22080 Some(formatted.join(" ⋯ "))
22081 }
22082
22083 let theme = cx.theme();
22084 let colors = theme.colors();
22085
22086 let (comments, comments_expanded, inline_editors, user_avatar_uri, line_ranges) =
22087 editor_handle
22088 .upgrade()
22089 .map(|editor| {
22090 let editor = editor.read(cx);
22091 let snapshot = editor.buffer().read(cx).snapshot(cx);
22092 let comments = editor.comments_for_hunk(hunk_key, &snapshot).to_vec();
22093 let (expanded, editors, avatar_uri, line_ranges) = editor
22094 .diff_review_overlays
22095 .iter()
22096 .find(|overlay| {
22097 Editor::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot)
22098 })
22099 .map(|o| {
22100 let start_point = o.anchor_range.start.to_point(&snapshot);
22101 let end_point = o.anchor_range.end.to_point(&snapshot);
22102 // Get line ranges per excerpt to detect discontinuities
22103 let buffer_ranges =
22104 snapshot.range_to_buffer_ranges(start_point..end_point);
22105 let ranges: Vec<(u32, u32)> = buffer_ranges
22106 .iter()
22107 .map(|(buffer, range, _)| {
22108 let start = buffer.offset_to_point(range.start.0).row;
22109 let end = buffer.offset_to_point(range.end.0).row;
22110 (start, end)
22111 })
22112 .collect();
22113 (
22114 o.comments_expanded,
22115 o.inline_edit_editors.clone(),
22116 o.user_avatar_uri.clone(),
22117 if ranges.is_empty() {
22118 None
22119 } else {
22120 Some(ranges)
22121 },
22122 )
22123 })
22124 .unwrap_or((true, HashMap::default(), None, None));
22125 (comments, expanded, editors, avatar_uri, line_ranges)
22126 })
22127 .unwrap_or((Vec::new(), true, HashMap::default(), None, None));
22128
22129 let comment_count = comments.len();
22130 let avatar_size = px(20.);
22131 let action_icon_size = IconSize::XSmall;
22132
22133 v_flex()
22134 .w_full()
22135 .bg(colors.editor_background)
22136 .border_b_1()
22137 .border_color(colors.border)
22138 .px_2()
22139 .pb_2()
22140 .gap_2()
22141 // Line range indicator (only shown for multi-line selections or multiple excerpts)
22142 .when_some(line_ranges, |el, ranges| {
22143 let label = format_line_ranges(&ranges);
22144 if let Some(label) = label {
22145 el.child(
22146 h_flex()
22147 .w_full()
22148 .px_2()
22149 .child(Label::new(label).size(LabelSize::Small).color(Color::Muted)),
22150 )
22151 } else {
22152 el
22153 }
22154 })
22155 // Top row: editable input with user's avatar
22156 .child(
22157 h_flex()
22158 .w_full()
22159 .items_center()
22160 .gap_2()
22161 .px_2()
22162 .py_1p5()
22163 .rounded_md()
22164 .bg(colors.surface_background)
22165 .child(
22166 div()
22167 .size(avatar_size)
22168 .flex_shrink_0()
22169 .rounded_full()
22170 .overflow_hidden()
22171 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22172 Avatar::new(avatar_uri.clone())
22173 .size(avatar_size)
22174 .into_any_element()
22175 } else {
22176 Icon::new(IconName::Person)
22177 .size(IconSize::Small)
22178 .color(ui::Color::Muted)
22179 .into_any_element()
22180 }),
22181 )
22182 .child(
22183 div()
22184 .flex_1()
22185 .border_1()
22186 .border_color(colors.border)
22187 .rounded_md()
22188 .bg(colors.editor_background)
22189 .px_2()
22190 .py_1()
22191 .child(prompt_editor.clone()),
22192 )
22193 .child(
22194 h_flex()
22195 .flex_shrink_0()
22196 .gap_1()
22197 .child(
22198 IconButton::new("diff-review-close", IconName::Close)
22199 .icon_color(ui::Color::Muted)
22200 .icon_size(action_icon_size)
22201 .tooltip(Tooltip::text("Close"))
22202 .on_click(|_, window, cx| {
22203 window
22204 .dispatch_action(Box::new(crate::actions::Cancel), cx);
22205 }),
22206 )
22207 .child(
22208 IconButton::new("diff-review-add", IconName::Return)
22209 .icon_color(ui::Color::Muted)
22210 .icon_size(action_icon_size)
22211 .tooltip(Tooltip::text("Add comment"))
22212 .on_click(|_, window, cx| {
22213 window.dispatch_action(
22214 Box::new(crate::actions::SubmitDiffReviewComment),
22215 cx,
22216 );
22217 }),
22218 ),
22219 ),
22220 )
22221 // Expandable comments section (only shown when there are comments)
22222 .when(comment_count > 0, |el| {
22223 el.child(Self::render_comments_section(
22224 comments,
22225 comments_expanded,
22226 inline_editors,
22227 user_avatar_uri,
22228 avatar_size,
22229 action_icon_size,
22230 colors,
22231 ))
22232 })
22233 .into_any_element()
22234 }
22235
22236 fn render_comments_section(
22237 comments: Vec<StoredReviewComment>,
22238 expanded: bool,
22239 inline_editors: HashMap<usize, Entity<Editor>>,
22240 user_avatar_uri: Option<SharedUri>,
22241 avatar_size: Pixels,
22242 action_icon_size: IconSize,
22243 colors: &theme::ThemeColors,
22244 ) -> impl IntoElement {
22245 let comment_count = comments.len();
22246
22247 v_flex()
22248 .w_full()
22249 .gap_1()
22250 // Header with expand/collapse toggle
22251 .child(
22252 h_flex()
22253 .id("review-comments-header")
22254 .w_full()
22255 .items_center()
22256 .gap_1()
22257 .px_2()
22258 .py_1()
22259 .cursor_pointer()
22260 .rounded_md()
22261 .hover(|style| style.bg(colors.ghost_element_hover))
22262 .on_click(|_, window: &mut Window, cx| {
22263 window.dispatch_action(
22264 Box::new(crate::actions::ToggleReviewCommentsExpanded),
22265 cx,
22266 );
22267 })
22268 .child(
22269 Icon::new(if expanded {
22270 IconName::ChevronDown
22271 } else {
22272 IconName::ChevronRight
22273 })
22274 .size(IconSize::Small)
22275 .color(ui::Color::Muted),
22276 )
22277 .child(
22278 Label::new(format!(
22279 "{} Comment{}",
22280 comment_count,
22281 if comment_count == 1 { "" } else { "s" }
22282 ))
22283 .size(LabelSize::Small)
22284 .color(Color::Muted),
22285 ),
22286 )
22287 // Comments list (when expanded)
22288 .when(expanded, |el| {
22289 el.children(comments.into_iter().map(|comment| {
22290 let inline_editor = inline_editors.get(&comment.id).cloned();
22291 Self::render_comment_row(
22292 comment,
22293 inline_editor,
22294 user_avatar_uri.clone(),
22295 avatar_size,
22296 action_icon_size,
22297 colors,
22298 )
22299 }))
22300 })
22301 }
22302
22303 fn render_comment_row(
22304 comment: StoredReviewComment,
22305 inline_editor: Option<Entity<Editor>>,
22306 user_avatar_uri: Option<SharedUri>,
22307 avatar_size: Pixels,
22308 action_icon_size: IconSize,
22309 colors: &theme::ThemeColors,
22310 ) -> impl IntoElement {
22311 let comment_id = comment.id;
22312 let is_editing = inline_editor.is_some();
22313
22314 h_flex()
22315 .w_full()
22316 .items_center()
22317 .gap_2()
22318 .px_2()
22319 .py_1p5()
22320 .rounded_md()
22321 .bg(colors.surface_background)
22322 .child(
22323 div()
22324 .size(avatar_size)
22325 .flex_shrink_0()
22326 .rounded_full()
22327 .overflow_hidden()
22328 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22329 Avatar::new(avatar_uri.clone())
22330 .size(avatar_size)
22331 .into_any_element()
22332 } else {
22333 Icon::new(IconName::Person)
22334 .size(IconSize::Small)
22335 .color(ui::Color::Muted)
22336 .into_any_element()
22337 }),
22338 )
22339 .child(if let Some(editor) = inline_editor {
22340 // Inline edit mode: show an editable text field
22341 div()
22342 .flex_1()
22343 .border_1()
22344 .border_color(colors.border)
22345 .rounded_md()
22346 .bg(colors.editor_background)
22347 .px_2()
22348 .py_1()
22349 .child(editor)
22350 .into_any_element()
22351 } else {
22352 // Display mode: show the comment text
22353 div()
22354 .flex_1()
22355 .text_sm()
22356 .text_color(colors.text)
22357 .child(comment.comment)
22358 .into_any_element()
22359 })
22360 .child(if is_editing {
22361 // Editing mode: show close and confirm buttons
22362 h_flex()
22363 .gap_1()
22364 .child(
22365 IconButton::new(
22366 format!("diff-review-cancel-edit-{comment_id}"),
22367 IconName::Close,
22368 )
22369 .icon_color(ui::Color::Muted)
22370 .icon_size(action_icon_size)
22371 .tooltip(Tooltip::text("Cancel"))
22372 .on_click(move |_, window, cx| {
22373 window.dispatch_action(
22374 Box::new(crate::actions::CancelEditReviewComment {
22375 id: comment_id,
22376 }),
22377 cx,
22378 );
22379 }),
22380 )
22381 .child(
22382 IconButton::new(
22383 format!("diff-review-confirm-edit-{comment_id}"),
22384 IconName::Return,
22385 )
22386 .icon_color(ui::Color::Muted)
22387 .icon_size(action_icon_size)
22388 .tooltip(Tooltip::text("Confirm"))
22389 .on_click(move |_, window, cx| {
22390 window.dispatch_action(
22391 Box::new(crate::actions::ConfirmEditReviewComment {
22392 id: comment_id,
22393 }),
22394 cx,
22395 );
22396 }),
22397 )
22398 .into_any_element()
22399 } else {
22400 // Display mode: no action buttons for now (edit/delete not yet implemented)
22401 gpui::Empty.into_any_element()
22402 })
22403 }
22404
22405 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
22406 if self.display_map.read(cx).masked != masked {
22407 self.display_map.update(cx, |map, _| map.masked = masked);
22408 }
22409 cx.notify()
22410 }
22411
22412 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
22413 self.show_wrap_guides = Some(show_wrap_guides);
22414 cx.notify();
22415 }
22416
22417 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
22418 self.show_indent_guides = Some(show_indent_guides);
22419 cx.notify();
22420 }
22421
22422 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
22423 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
22424 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
22425 && let Some(dir) = file.abs_path(cx).parent()
22426 {
22427 return Some(dir.to_owned());
22428 }
22429 }
22430
22431 None
22432 }
22433
22434 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
22435 self.active_excerpt(cx)?
22436 .1
22437 .read(cx)
22438 .file()
22439 .and_then(|f| f.as_local())
22440 }
22441
22442 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
22443 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22444 let buffer = buffer.read(cx);
22445 if let Some(project_path) = buffer.project_path(cx) {
22446 let project = self.project()?.read(cx);
22447 project.absolute_path(&project_path, cx)
22448 } else {
22449 buffer
22450 .file()
22451 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
22452 }
22453 })
22454 }
22455
22456 pub fn reveal_in_finder(
22457 &mut self,
22458 _: &RevealInFileManager,
22459 _window: &mut Window,
22460 cx: &mut Context<Self>,
22461 ) {
22462 if let Some(path) = self.target_file_abs_path(cx) {
22463 if let Some(project) = self.project() {
22464 project.update(cx, |project, cx| project.reveal_path(&path, cx));
22465 } else {
22466 cx.reveal_path(&path);
22467 }
22468 }
22469 }
22470
22471 pub fn copy_path(
22472 &mut self,
22473 _: &zed_actions::workspace::CopyPath,
22474 _window: &mut Window,
22475 cx: &mut Context<Self>,
22476 ) {
22477 if let Some(path) = self.target_file_abs_path(cx)
22478 && let Some(path) = path.to_str()
22479 {
22480 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22481 } else {
22482 cx.propagate();
22483 }
22484 }
22485
22486 pub fn copy_relative_path(
22487 &mut self,
22488 _: &zed_actions::workspace::CopyRelativePath,
22489 _window: &mut Window,
22490 cx: &mut Context<Self>,
22491 ) {
22492 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22493 let project = self.project()?.read(cx);
22494 let path = buffer.read(cx).file()?.path();
22495 let path = path.display(project.path_style(cx));
22496 Some(path)
22497 }) {
22498 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22499 } else {
22500 cx.propagate();
22501 }
22502 }
22503
22504 /// Returns the project path for the editor's buffer, if any buffer is
22505 /// opened in the editor.
22506 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
22507 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
22508 buffer.read(cx).project_path(cx)
22509 } else {
22510 None
22511 }
22512 }
22513
22514 // Returns true if the editor handled a go-to-line request
22515 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
22516 maybe!({
22517 let breakpoint_store = self.breakpoint_store.as_ref()?;
22518
22519 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
22520 else {
22521 self.clear_row_highlights::<ActiveDebugLine>();
22522 return None;
22523 };
22524
22525 let position = active_stack_frame.position;
22526 let buffer_id = position.buffer_id?;
22527 let snapshot = self
22528 .project
22529 .as_ref()?
22530 .read(cx)
22531 .buffer_for_id(buffer_id, cx)?
22532 .read(cx)
22533 .snapshot();
22534
22535 let mut handled = false;
22536 for (id, ExcerptRange { context, .. }) in
22537 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
22538 {
22539 if context.start.cmp(&position, &snapshot).is_ge()
22540 || context.end.cmp(&position, &snapshot).is_lt()
22541 {
22542 continue;
22543 }
22544 let snapshot = self.buffer.read(cx).snapshot(cx);
22545 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
22546
22547 handled = true;
22548 self.clear_row_highlights::<ActiveDebugLine>();
22549
22550 self.go_to_line::<ActiveDebugLine>(
22551 multibuffer_anchor,
22552 Some(cx.theme().colors().editor_debugger_active_line_background),
22553 window,
22554 cx,
22555 );
22556
22557 cx.notify();
22558 }
22559
22560 handled.then_some(())
22561 })
22562 .is_some()
22563 }
22564
22565 pub fn copy_file_name_without_extension(
22566 &mut self,
22567 _: &CopyFileNameWithoutExtension,
22568 _: &mut Window,
22569 cx: &mut Context<Self>,
22570 ) {
22571 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22572 let file = buffer.read(cx).file()?;
22573 file.path().file_stem()
22574 }) {
22575 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
22576 }
22577 }
22578
22579 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
22580 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22581 let file = buffer.read(cx).file()?;
22582 Some(file.file_name(cx))
22583 }) {
22584 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
22585 }
22586 }
22587
22588 pub fn toggle_git_blame(
22589 &mut self,
22590 _: &::git::Blame,
22591 window: &mut Window,
22592 cx: &mut Context<Self>,
22593 ) {
22594 self.show_git_blame_gutter = !self.show_git_blame_gutter;
22595
22596 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
22597 self.start_git_blame(true, window, cx);
22598 }
22599
22600 cx.notify();
22601 }
22602
22603 pub fn toggle_git_blame_inline(
22604 &mut self,
22605 _: &ToggleGitBlameInline,
22606 window: &mut Window,
22607 cx: &mut Context<Self>,
22608 ) {
22609 self.toggle_git_blame_inline_internal(true, window, cx);
22610 cx.notify();
22611 }
22612
22613 pub fn open_git_blame_commit(
22614 &mut self,
22615 _: &OpenGitBlameCommit,
22616 window: &mut Window,
22617 cx: &mut Context<Self>,
22618 ) {
22619 self.open_git_blame_commit_internal(window, cx);
22620 }
22621
22622 fn open_git_blame_commit_internal(
22623 &mut self,
22624 window: &mut Window,
22625 cx: &mut Context<Self>,
22626 ) -> Option<()> {
22627 let blame = self.blame.as_ref()?;
22628 let snapshot = self.snapshot(window, cx);
22629 let cursor = self
22630 .selections
22631 .newest::<Point>(&snapshot.display_snapshot)
22632 .head();
22633 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
22634 let (_, blame_entry) = blame
22635 .update(cx, |blame, cx| {
22636 blame
22637 .blame_for_rows(
22638 &[RowInfo {
22639 buffer_id: Some(buffer.remote_id()),
22640 buffer_row: Some(point.row),
22641 ..Default::default()
22642 }],
22643 cx,
22644 )
22645 .next()
22646 })
22647 .flatten()?;
22648 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22649 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
22650 let workspace = self.workspace()?.downgrade();
22651 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
22652 None
22653 }
22654
22655 pub fn git_blame_inline_enabled(&self) -> bool {
22656 self.git_blame_inline_enabled
22657 }
22658
22659 pub fn toggle_selection_menu(
22660 &mut self,
22661 _: &ToggleSelectionMenu,
22662 _: &mut Window,
22663 cx: &mut Context<Self>,
22664 ) {
22665 self.show_selection_menu = self
22666 .show_selection_menu
22667 .map(|show_selections_menu| !show_selections_menu)
22668 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
22669
22670 cx.notify();
22671 }
22672
22673 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
22674 self.show_selection_menu
22675 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
22676 }
22677
22678 fn start_git_blame(
22679 &mut self,
22680 user_triggered: bool,
22681 window: &mut Window,
22682 cx: &mut Context<Self>,
22683 ) {
22684 if let Some(project) = self.project() {
22685 if let Some(buffer) = self.buffer().read(cx).as_singleton()
22686 && buffer.read(cx).file().is_none()
22687 {
22688 return;
22689 }
22690
22691 let focused = self.focus_handle(cx).contains_focused(window, cx);
22692
22693 let project = project.clone();
22694 let blame = cx
22695 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
22696 self.blame_subscription =
22697 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
22698 self.blame = Some(blame);
22699 }
22700 }
22701
22702 fn toggle_git_blame_inline_internal(
22703 &mut self,
22704 user_triggered: bool,
22705 window: &mut Window,
22706 cx: &mut Context<Self>,
22707 ) {
22708 if self.git_blame_inline_enabled {
22709 self.git_blame_inline_enabled = false;
22710 self.show_git_blame_inline = false;
22711 self.show_git_blame_inline_delay_task.take();
22712 } else {
22713 self.git_blame_inline_enabled = true;
22714 self.start_git_blame_inline(user_triggered, window, cx);
22715 }
22716
22717 cx.notify();
22718 }
22719
22720 fn start_git_blame_inline(
22721 &mut self,
22722 user_triggered: bool,
22723 window: &mut Window,
22724 cx: &mut Context<Self>,
22725 ) {
22726 self.start_git_blame(user_triggered, window, cx);
22727
22728 if ProjectSettings::get_global(cx)
22729 .git
22730 .inline_blame_delay()
22731 .is_some()
22732 {
22733 self.start_inline_blame_timer(window, cx);
22734 } else {
22735 self.show_git_blame_inline = true
22736 }
22737 }
22738
22739 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
22740 self.blame.as_ref()
22741 }
22742
22743 pub fn show_git_blame_gutter(&self) -> bool {
22744 self.show_git_blame_gutter
22745 }
22746
22747 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
22748 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
22749 }
22750
22751 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
22752 self.show_git_blame_inline
22753 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
22754 && !self.newest_selection_head_on_empty_line(cx)
22755 && self.has_blame_entries(cx)
22756 }
22757
22758 fn has_blame_entries(&self, cx: &App) -> bool {
22759 self.blame()
22760 .is_some_and(|blame| blame.read(cx).has_generated_entries())
22761 }
22762
22763 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
22764 let cursor_anchor = self.selections.newest_anchor().head();
22765
22766 let snapshot = self.buffer.read(cx).snapshot(cx);
22767 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
22768
22769 snapshot.line_len(buffer_row) == 0
22770 }
22771
22772 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
22773 let buffer_and_selection = maybe!({
22774 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
22775 let selection_range = selection.range();
22776
22777 let multi_buffer = self.buffer().read(cx);
22778 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
22779 let buffer_ranges = multi_buffer_snapshot
22780 .range_to_buffer_ranges(selection_range.start..=selection_range.end);
22781
22782 let (buffer, range, _) = if selection.reversed {
22783 buffer_ranges.first()
22784 } else {
22785 buffer_ranges.last()
22786 }?;
22787
22788 let buffer_range = range.to_point(buffer);
22789
22790 let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else {
22791 return Some((
22792 multi_buffer.buffer(buffer.remote_id()).unwrap(),
22793 buffer_range.start.row..buffer_range.end.row,
22794 ));
22795 };
22796
22797 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
22798 let start =
22799 buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.start, buffer);
22800 let end =
22801 buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.end, buffer);
22802
22803 Some((
22804 multi_buffer.buffer(buffer.remote_id()).unwrap(),
22805 start.row..end.row,
22806 ))
22807 });
22808
22809 let Some((buffer, selection)) = buffer_and_selection else {
22810 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
22811 };
22812
22813 let Some(project) = self.project() else {
22814 return Task::ready(Err(anyhow!("editor does not have project")));
22815 };
22816
22817 project.update(cx, |project, cx| {
22818 project.get_permalink_to_line(&buffer, selection, cx)
22819 })
22820 }
22821
22822 pub fn copy_permalink_to_line(
22823 &mut self,
22824 _: &CopyPermalinkToLine,
22825 window: &mut Window,
22826 cx: &mut Context<Self>,
22827 ) {
22828 let permalink_task = self.get_permalink_to_line(cx);
22829 let workspace = self.workspace();
22830
22831 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
22832 Ok(permalink) => {
22833 cx.update(|_, cx| {
22834 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
22835 })
22836 .ok();
22837 }
22838 Err(err) => {
22839 let message = format!("Failed to copy permalink: {err}");
22840
22841 anyhow::Result::<()>::Err(err).log_err();
22842
22843 if let Some(workspace) = workspace {
22844 workspace
22845 .update_in(cx, |workspace, _, cx| {
22846 struct CopyPermalinkToLine;
22847
22848 workspace.show_toast(
22849 Toast::new(
22850 NotificationId::unique::<CopyPermalinkToLine>(),
22851 message,
22852 ),
22853 cx,
22854 )
22855 })
22856 .ok();
22857 }
22858 }
22859 })
22860 .detach();
22861 }
22862
22863 pub fn copy_file_location(
22864 &mut self,
22865 _: &CopyFileLocation,
22866 _: &mut Window,
22867 cx: &mut Context<Self>,
22868 ) {
22869 let selection = self
22870 .selections
22871 .newest::<Point>(&self.display_snapshot(cx))
22872 .start
22873 .row
22874 + 1;
22875 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22876 let project = self.project()?.read(cx);
22877 let file = buffer.read(cx).file()?;
22878 let path = file.path().display(project.path_style(cx));
22879
22880 Some(format!("{path}:{selection}"))
22881 }) {
22882 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
22883 }
22884 }
22885
22886 pub fn open_permalink_to_line(
22887 &mut self,
22888 _: &OpenPermalinkToLine,
22889 window: &mut Window,
22890 cx: &mut Context<Self>,
22891 ) {
22892 let permalink_task = self.get_permalink_to_line(cx);
22893 let workspace = self.workspace();
22894
22895 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
22896 Ok(permalink) => {
22897 cx.update(|_, cx| {
22898 cx.open_url(permalink.as_ref());
22899 })
22900 .ok();
22901 }
22902 Err(err) => {
22903 let message = format!("Failed to open permalink: {err}");
22904
22905 anyhow::Result::<()>::Err(err).log_err();
22906
22907 if let Some(workspace) = workspace {
22908 workspace.update(cx, |workspace, cx| {
22909 struct OpenPermalinkToLine;
22910
22911 workspace.show_toast(
22912 Toast::new(NotificationId::unique::<OpenPermalinkToLine>(), message),
22913 cx,
22914 )
22915 });
22916 }
22917 }
22918 })
22919 .detach();
22920 }
22921
22922 pub fn insert_uuid_v4(
22923 &mut self,
22924 _: &InsertUuidV4,
22925 window: &mut Window,
22926 cx: &mut Context<Self>,
22927 ) {
22928 self.insert_uuid(UuidVersion::V4, window, cx);
22929 }
22930
22931 pub fn insert_uuid_v7(
22932 &mut self,
22933 _: &InsertUuidV7,
22934 window: &mut Window,
22935 cx: &mut Context<Self>,
22936 ) {
22937 self.insert_uuid(UuidVersion::V7, window, cx);
22938 }
22939
22940 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
22941 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
22942 self.transact(window, cx, |this, window, cx| {
22943 let edits = this
22944 .selections
22945 .all::<Point>(&this.display_snapshot(cx))
22946 .into_iter()
22947 .map(|selection| {
22948 let uuid = match version {
22949 UuidVersion::V4 => uuid::Uuid::new_v4(),
22950 UuidVersion::V7 => uuid::Uuid::now_v7(),
22951 };
22952
22953 (selection.range(), uuid.to_string())
22954 });
22955 this.edit(edits, cx);
22956 this.refresh_edit_prediction(true, false, window, cx);
22957 });
22958 }
22959
22960 pub fn open_selections_in_multibuffer(
22961 &mut self,
22962 _: &OpenSelectionsInMultibuffer,
22963 window: &mut Window,
22964 cx: &mut Context<Self>,
22965 ) {
22966 let multibuffer = self.buffer.read(cx);
22967
22968 let Some(buffer) = multibuffer.as_singleton() else {
22969 return;
22970 };
22971
22972 let Some(workspace) = self.workspace() else {
22973 return;
22974 };
22975
22976 let title = multibuffer.title(cx).to_string();
22977
22978 let locations = self
22979 .selections
22980 .all_anchors(&self.display_snapshot(cx))
22981 .iter()
22982 .map(|selection| {
22983 (
22984 buffer.clone(),
22985 (selection.start.text_anchor..selection.end.text_anchor)
22986 .to_point(buffer.read(cx)),
22987 )
22988 })
22989 .into_group_map();
22990
22991 cx.spawn_in(window, async move |_, cx| {
22992 workspace.update_in(cx, |workspace, window, cx| {
22993 Self::open_locations_in_multibuffer(
22994 workspace,
22995 locations,
22996 format!("Selections for '{title}'"),
22997 false,
22998 false,
22999 MultibufferSelectionMode::All,
23000 window,
23001 cx,
23002 );
23003 })
23004 })
23005 .detach();
23006 }
23007
23008 /// Adds a row highlight for the given range. If a row has multiple highlights, the
23009 /// last highlight added will be used.
23010 ///
23011 /// If the range ends at the beginning of a line, then that line will not be highlighted.
23012 pub fn highlight_rows<T: 'static>(
23013 &mut self,
23014 range: Range<Anchor>,
23015 color: Hsla,
23016 options: RowHighlightOptions,
23017 cx: &mut Context<Self>,
23018 ) {
23019 let snapshot = self.buffer().read(cx).snapshot(cx);
23020 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23021 let ix = row_highlights.binary_search_by(|highlight| {
23022 Ordering::Equal
23023 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
23024 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
23025 });
23026
23027 if let Err(mut ix) = ix {
23028 let index = post_inc(&mut self.highlight_order);
23029
23030 // If this range intersects with the preceding highlight, then merge it with
23031 // the preceding highlight. Otherwise insert a new highlight.
23032 let mut merged = false;
23033 if ix > 0 {
23034 let prev_highlight = &mut row_highlights[ix - 1];
23035 if prev_highlight
23036 .range
23037 .end
23038 .cmp(&range.start, &snapshot)
23039 .is_ge()
23040 {
23041 ix -= 1;
23042 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
23043 prev_highlight.range.end = range.end;
23044 }
23045 merged = true;
23046 prev_highlight.index = index;
23047 prev_highlight.color = color;
23048 prev_highlight.options = options;
23049 }
23050 }
23051
23052 if !merged {
23053 row_highlights.insert(
23054 ix,
23055 RowHighlight {
23056 range,
23057 index,
23058 color,
23059 options,
23060 type_id: TypeId::of::<T>(),
23061 },
23062 );
23063 }
23064
23065 // If any of the following highlights intersect with this one, merge them.
23066 while let Some(next_highlight) = row_highlights.get(ix + 1) {
23067 let highlight = &row_highlights[ix];
23068 if next_highlight
23069 .range
23070 .start
23071 .cmp(&highlight.range.end, &snapshot)
23072 .is_le()
23073 {
23074 if next_highlight
23075 .range
23076 .end
23077 .cmp(&highlight.range.end, &snapshot)
23078 .is_gt()
23079 {
23080 row_highlights[ix].range.end = next_highlight.range.end;
23081 }
23082 row_highlights.remove(ix + 1);
23083 } else {
23084 break;
23085 }
23086 }
23087 }
23088 }
23089
23090 /// Remove any highlighted row ranges of the given type that intersect the
23091 /// given ranges.
23092 pub fn remove_highlighted_rows<T: 'static>(
23093 &mut self,
23094 ranges_to_remove: Vec<Range<Anchor>>,
23095 cx: &mut Context<Self>,
23096 ) {
23097 let snapshot = self.buffer().read(cx).snapshot(cx);
23098 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23099 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23100 row_highlights.retain(|highlight| {
23101 while let Some(range_to_remove) = ranges_to_remove.peek() {
23102 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
23103 Ordering::Less | Ordering::Equal => {
23104 ranges_to_remove.next();
23105 }
23106 Ordering::Greater => {
23107 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
23108 Ordering::Less | Ordering::Equal => {
23109 return false;
23110 }
23111 Ordering::Greater => break,
23112 }
23113 }
23114 }
23115 }
23116
23117 true
23118 })
23119 }
23120
23121 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
23122 pub fn clear_row_highlights<T: 'static>(&mut self) {
23123 self.highlighted_rows.remove(&TypeId::of::<T>());
23124 }
23125
23126 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
23127 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
23128 self.highlighted_rows
23129 .get(&TypeId::of::<T>())
23130 .map_or(&[] as &[_], |vec| vec.as_slice())
23131 .iter()
23132 .map(|highlight| (highlight.range.clone(), highlight.color))
23133 }
23134
23135 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
23136 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
23137 /// Allows to ignore certain kinds of highlights.
23138 pub fn highlighted_display_rows(
23139 &self,
23140 window: &mut Window,
23141 cx: &mut App,
23142 ) -> BTreeMap<DisplayRow, LineHighlight> {
23143 let snapshot = self.snapshot(window, cx);
23144 let mut used_highlight_orders = HashMap::default();
23145 self.highlighted_rows
23146 .iter()
23147 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
23148 .fold(
23149 BTreeMap::<DisplayRow, LineHighlight>::new(),
23150 |mut unique_rows, highlight| {
23151 let start = highlight.range.start.to_display_point(&snapshot);
23152 let end = highlight.range.end.to_display_point(&snapshot);
23153 let start_row = start.row().0;
23154 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
23155 {
23156 end.row().0.saturating_sub(1)
23157 } else {
23158 end.row().0
23159 };
23160 for row in start_row..=end_row {
23161 let used_index =
23162 used_highlight_orders.entry(row).or_insert(highlight.index);
23163 if highlight.index >= *used_index {
23164 *used_index = highlight.index;
23165 unique_rows.insert(
23166 DisplayRow(row),
23167 LineHighlight {
23168 include_gutter: highlight.options.include_gutter,
23169 border: None,
23170 background: highlight.color.into(),
23171 type_id: Some(highlight.type_id),
23172 },
23173 );
23174 }
23175 }
23176 unique_rows
23177 },
23178 )
23179 }
23180
23181 pub fn highlighted_display_row_for_autoscroll(
23182 &self,
23183 snapshot: &DisplaySnapshot,
23184 ) -> Option<DisplayRow> {
23185 self.highlighted_rows
23186 .values()
23187 .flat_map(|highlighted_rows| highlighted_rows.iter())
23188 .filter_map(|highlight| {
23189 if highlight.options.autoscroll {
23190 Some(highlight.range.start.to_display_point(snapshot).row())
23191 } else {
23192 None
23193 }
23194 })
23195 .min()
23196 }
23197
23198 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
23199 self.highlight_background::<SearchWithinRange>(
23200 ranges,
23201 |_, colors| colors.colors().editor_document_highlight_read_background,
23202 cx,
23203 )
23204 }
23205
23206 pub fn set_breadcrumb_header(&mut self, new_header: String) {
23207 self.breadcrumb_header = Some(new_header);
23208 }
23209
23210 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
23211 self.clear_background_highlights::<SearchWithinRange>(cx);
23212 }
23213
23214 pub fn highlight_background<T: 'static>(
23215 &mut self,
23216 ranges: &[Range<Anchor>],
23217 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
23218 cx: &mut Context<Self>,
23219 ) {
23220 self.background_highlights.insert(
23221 HighlightKey::Type(TypeId::of::<T>()),
23222 (Arc::new(color_fetcher), Arc::from(ranges)),
23223 );
23224 self.scrollbar_marker_state.dirty = true;
23225 cx.notify();
23226 }
23227
23228 pub fn highlight_background_key<T: 'static>(
23229 &mut self,
23230 key: usize,
23231 ranges: &[Range<Anchor>],
23232 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
23233 cx: &mut Context<Self>,
23234 ) {
23235 self.background_highlights.insert(
23236 HighlightKey::TypePlus(TypeId::of::<T>(), key),
23237 (Arc::new(color_fetcher), Arc::from(ranges)),
23238 );
23239 self.scrollbar_marker_state.dirty = true;
23240 cx.notify();
23241 }
23242
23243 pub fn clear_background_highlights<T: 'static>(
23244 &mut self,
23245 cx: &mut Context<Self>,
23246 ) -> Option<BackgroundHighlight> {
23247 let text_highlights = self
23248 .background_highlights
23249 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
23250 if !text_highlights.1.is_empty() {
23251 self.scrollbar_marker_state.dirty = true;
23252 cx.notify();
23253 }
23254 Some(text_highlights)
23255 }
23256
23257 pub fn clear_background_highlights_key<T: 'static>(
23258 &mut self,
23259 key: usize,
23260 cx: &mut Context<Self>,
23261 ) -> Option<BackgroundHighlight> {
23262 let text_highlights = self
23263 .background_highlights
23264 .remove(&HighlightKey::TypePlus(TypeId::of::<T>(), key))?;
23265 if !text_highlights.1.is_empty() {
23266 self.scrollbar_marker_state.dirty = true;
23267 cx.notify();
23268 }
23269 Some(text_highlights)
23270 }
23271
23272 pub fn highlight_gutter<T: 'static>(
23273 &mut self,
23274 ranges: impl Into<Vec<Range<Anchor>>>,
23275 color_fetcher: fn(&App) -> Hsla,
23276 cx: &mut Context<Self>,
23277 ) {
23278 self.gutter_highlights
23279 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
23280 cx.notify();
23281 }
23282
23283 pub fn clear_gutter_highlights<T: 'static>(
23284 &mut self,
23285 cx: &mut Context<Self>,
23286 ) -> Option<GutterHighlight> {
23287 cx.notify();
23288 self.gutter_highlights.remove(&TypeId::of::<T>())
23289 }
23290
23291 pub fn insert_gutter_highlight<T: 'static>(
23292 &mut self,
23293 range: Range<Anchor>,
23294 color_fetcher: fn(&App) -> Hsla,
23295 cx: &mut Context<Self>,
23296 ) {
23297 let snapshot = self.buffer().read(cx).snapshot(cx);
23298 let mut highlights = self
23299 .gutter_highlights
23300 .remove(&TypeId::of::<T>())
23301 .map(|(_, highlights)| highlights)
23302 .unwrap_or_default();
23303 let ix = highlights.binary_search_by(|highlight| {
23304 Ordering::Equal
23305 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
23306 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
23307 });
23308 if let Err(ix) = ix {
23309 highlights.insert(ix, range);
23310 }
23311 self.gutter_highlights
23312 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
23313 }
23314
23315 pub fn remove_gutter_highlights<T: 'static>(
23316 &mut self,
23317 ranges_to_remove: Vec<Range<Anchor>>,
23318 cx: &mut Context<Self>,
23319 ) {
23320 let snapshot = self.buffer().read(cx).snapshot(cx);
23321 let Some((color_fetcher, mut gutter_highlights)) =
23322 self.gutter_highlights.remove(&TypeId::of::<T>())
23323 else {
23324 return;
23325 };
23326 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23327 gutter_highlights.retain(|highlight| {
23328 while let Some(range_to_remove) = ranges_to_remove.peek() {
23329 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
23330 Ordering::Less | Ordering::Equal => {
23331 ranges_to_remove.next();
23332 }
23333 Ordering::Greater => {
23334 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
23335 Ordering::Less | Ordering::Equal => {
23336 return false;
23337 }
23338 Ordering::Greater => break,
23339 }
23340 }
23341 }
23342 }
23343
23344 true
23345 });
23346 self.gutter_highlights
23347 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
23348 }
23349
23350 #[cfg(feature = "test-support")]
23351 pub fn all_text_highlights(
23352 &self,
23353 window: &mut Window,
23354 cx: &mut Context<Self>,
23355 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
23356 let snapshot = self.snapshot(window, cx);
23357 self.display_map.update(cx, |display_map, _| {
23358 display_map
23359 .all_text_highlights()
23360 .map(|highlight| {
23361 let (style, ranges) = highlight.as_ref();
23362 (
23363 *style,
23364 ranges
23365 .iter()
23366 .map(|range| range.clone().to_display_points(&snapshot))
23367 .collect(),
23368 )
23369 })
23370 .collect()
23371 })
23372 }
23373
23374 #[cfg(feature = "test-support")]
23375 pub fn all_text_background_highlights(
23376 &self,
23377 window: &mut Window,
23378 cx: &mut Context<Self>,
23379 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23380 let snapshot = self.snapshot(window, cx);
23381 let buffer = &snapshot.buffer_snapshot();
23382 let start = buffer.anchor_before(MultiBufferOffset(0));
23383 let end = buffer.anchor_after(buffer.len());
23384 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
23385 }
23386
23387 #[cfg(any(test, feature = "test-support"))]
23388 pub fn sorted_background_highlights_in_range(
23389 &self,
23390 search_range: Range<Anchor>,
23391 display_snapshot: &DisplaySnapshot,
23392 theme: &Theme,
23393 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23394 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
23395 res.sort_by(|a, b| {
23396 a.0.start
23397 .cmp(&b.0.start)
23398 .then_with(|| a.0.end.cmp(&b.0.end))
23399 .then_with(|| a.1.cmp(&b.1))
23400 });
23401 res
23402 }
23403
23404 #[cfg(feature = "test-support")]
23405 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
23406 let snapshot = self.buffer().read(cx).snapshot(cx);
23407
23408 let highlights = self
23409 .background_highlights
23410 .get(&HighlightKey::Type(TypeId::of::<
23411 items::BufferSearchHighlights,
23412 >()));
23413
23414 if let Some((_color, ranges)) = highlights {
23415 ranges
23416 .iter()
23417 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
23418 .collect_vec()
23419 } else {
23420 vec![]
23421 }
23422 }
23423
23424 fn document_highlights_for_position<'a>(
23425 &'a self,
23426 position: Anchor,
23427 buffer: &'a MultiBufferSnapshot,
23428 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
23429 let read_highlights = self
23430 .background_highlights
23431 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
23432 .map(|h| &h.1);
23433 let write_highlights = self
23434 .background_highlights
23435 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
23436 .map(|h| &h.1);
23437 let left_position = position.bias_left(buffer);
23438 let right_position = position.bias_right(buffer);
23439 read_highlights
23440 .into_iter()
23441 .chain(write_highlights)
23442 .flat_map(move |ranges| {
23443 let start_ix = match ranges.binary_search_by(|probe| {
23444 let cmp = probe.end.cmp(&left_position, buffer);
23445 if cmp.is_ge() {
23446 Ordering::Greater
23447 } else {
23448 Ordering::Less
23449 }
23450 }) {
23451 Ok(i) | Err(i) => i,
23452 };
23453
23454 ranges[start_ix..]
23455 .iter()
23456 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
23457 })
23458 }
23459
23460 pub fn has_background_highlights<T: 'static>(&self) -> bool {
23461 self.background_highlights
23462 .get(&HighlightKey::Type(TypeId::of::<T>()))
23463 .is_some_and(|(_, highlights)| !highlights.is_empty())
23464 }
23465
23466 /// Returns all background highlights for a given range.
23467 ///
23468 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
23469 pub fn background_highlights_in_range(
23470 &self,
23471 search_range: Range<Anchor>,
23472 display_snapshot: &DisplaySnapshot,
23473 theme: &Theme,
23474 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23475 let mut results = Vec::new();
23476 for (color_fetcher, ranges) in self.background_highlights.values() {
23477 let start_ix = match ranges.binary_search_by(|probe| {
23478 let cmp = probe
23479 .end
23480 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23481 if cmp.is_gt() {
23482 Ordering::Greater
23483 } else {
23484 Ordering::Less
23485 }
23486 }) {
23487 Ok(i) | Err(i) => i,
23488 };
23489 for (index, range) in ranges[start_ix..].iter().enumerate() {
23490 if range
23491 .start
23492 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23493 .is_ge()
23494 {
23495 break;
23496 }
23497
23498 let color = color_fetcher(&(start_ix + index), theme);
23499 let start = range.start.to_display_point(display_snapshot);
23500 let end = range.end.to_display_point(display_snapshot);
23501 results.push((start..end, color))
23502 }
23503 }
23504 results
23505 }
23506
23507 pub fn gutter_highlights_in_range(
23508 &self,
23509 search_range: Range<Anchor>,
23510 display_snapshot: &DisplaySnapshot,
23511 cx: &App,
23512 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23513 let mut results = Vec::new();
23514 for (color_fetcher, ranges) in self.gutter_highlights.values() {
23515 let color = color_fetcher(cx);
23516 let start_ix = match ranges.binary_search_by(|probe| {
23517 let cmp = probe
23518 .end
23519 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23520 if cmp.is_gt() {
23521 Ordering::Greater
23522 } else {
23523 Ordering::Less
23524 }
23525 }) {
23526 Ok(i) | Err(i) => i,
23527 };
23528 for range in &ranges[start_ix..] {
23529 if range
23530 .start
23531 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23532 .is_ge()
23533 {
23534 break;
23535 }
23536
23537 let start = range.start.to_display_point(display_snapshot);
23538 let end = range.end.to_display_point(display_snapshot);
23539 results.push((start..end, color))
23540 }
23541 }
23542 results
23543 }
23544
23545 /// Get the text ranges corresponding to the redaction query
23546 pub fn redacted_ranges(
23547 &self,
23548 search_range: Range<Anchor>,
23549 display_snapshot: &DisplaySnapshot,
23550 cx: &App,
23551 ) -> Vec<Range<DisplayPoint>> {
23552 display_snapshot
23553 .buffer_snapshot()
23554 .redacted_ranges(search_range, |file| {
23555 if let Some(file) = file {
23556 file.is_private()
23557 && EditorSettings::get(
23558 Some(SettingsLocation {
23559 worktree_id: file.worktree_id(cx),
23560 path: file.path().as_ref(),
23561 }),
23562 cx,
23563 )
23564 .redact_private_values
23565 } else {
23566 false
23567 }
23568 })
23569 .map(|range| {
23570 range.start.to_display_point(display_snapshot)
23571 ..range.end.to_display_point(display_snapshot)
23572 })
23573 .collect()
23574 }
23575
23576 pub fn highlight_text_key<T: 'static>(
23577 &mut self,
23578 key: usize,
23579 ranges: Vec<Range<Anchor>>,
23580 style: HighlightStyle,
23581 merge: bool,
23582 cx: &mut Context<Self>,
23583 ) {
23584 self.display_map.update(cx, |map, cx| {
23585 map.highlight_text(
23586 HighlightKey::TypePlus(TypeId::of::<T>(), key),
23587 ranges,
23588 style,
23589 merge,
23590 cx,
23591 );
23592 });
23593 cx.notify();
23594 }
23595
23596 pub fn highlight_text<T: 'static>(
23597 &mut self,
23598 ranges: Vec<Range<Anchor>>,
23599 style: HighlightStyle,
23600 cx: &mut Context<Self>,
23601 ) {
23602 self.display_map.update(cx, |map, cx| {
23603 map.highlight_text(
23604 HighlightKey::Type(TypeId::of::<T>()),
23605 ranges,
23606 style,
23607 false,
23608 cx,
23609 )
23610 });
23611 cx.notify();
23612 }
23613
23614 pub fn text_highlights<'a, T: 'static>(
23615 &'a self,
23616 cx: &'a App,
23617 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
23618 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
23619 }
23620
23621 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
23622 let cleared = self
23623 .display_map
23624 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
23625 if cleared {
23626 cx.notify();
23627 }
23628 }
23629
23630 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
23631 (self.read_only(cx) || self.blink_manager.read(cx).visible())
23632 && self.focus_handle.is_focused(window)
23633 }
23634
23635 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
23636 self.show_cursor_when_unfocused = is_enabled;
23637 cx.notify();
23638 }
23639
23640 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
23641 cx.notify();
23642 }
23643
23644 fn on_debug_session_event(
23645 &mut self,
23646 _session: Entity<Session>,
23647 event: &SessionEvent,
23648 cx: &mut Context<Self>,
23649 ) {
23650 if let SessionEvent::InvalidateInlineValue = event {
23651 self.refresh_inline_values(cx);
23652 }
23653 }
23654
23655 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
23656 let Some(project) = self.project.clone() else {
23657 return;
23658 };
23659
23660 if !self.inline_value_cache.enabled {
23661 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
23662 self.splice_inlays(&inlays, Vec::new(), cx);
23663 return;
23664 }
23665
23666 let current_execution_position = self
23667 .highlighted_rows
23668 .get(&TypeId::of::<ActiveDebugLine>())
23669 .and_then(|lines| lines.last().map(|line| line.range.end));
23670
23671 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
23672 let inline_values = editor
23673 .update(cx, |editor, cx| {
23674 let Some(current_execution_position) = current_execution_position else {
23675 return Some(Task::ready(Ok(Vec::new())));
23676 };
23677
23678 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
23679 let snapshot = buffer.snapshot(cx);
23680
23681 let excerpt = snapshot.excerpt_containing(
23682 current_execution_position..current_execution_position,
23683 )?;
23684
23685 editor.buffer.read(cx).buffer(excerpt.buffer_id())
23686 })?;
23687
23688 let range =
23689 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
23690
23691 project.inline_values(buffer, range, cx)
23692 })
23693 .ok()
23694 .flatten()?
23695 .await
23696 .context("refreshing debugger inlays")
23697 .log_err()?;
23698
23699 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
23700
23701 for (buffer_id, inline_value) in inline_values
23702 .into_iter()
23703 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
23704 {
23705 buffer_inline_values
23706 .entry(buffer_id)
23707 .or_default()
23708 .push(inline_value);
23709 }
23710
23711 editor
23712 .update(cx, |editor, cx| {
23713 let snapshot = editor.buffer.read(cx).snapshot(cx);
23714 let mut new_inlays = Vec::default();
23715
23716 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
23717 let buffer_id = buffer_snapshot.remote_id();
23718 buffer_inline_values
23719 .get(&buffer_id)
23720 .into_iter()
23721 .flatten()
23722 .for_each(|hint| {
23723 let inlay = Inlay::debugger(
23724 post_inc(&mut editor.next_inlay_id),
23725 Anchor::in_buffer(excerpt_id, hint.position),
23726 hint.text(),
23727 );
23728 if !inlay.text().chars().contains(&'\n') {
23729 new_inlays.push(inlay);
23730 }
23731 });
23732 }
23733
23734 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
23735 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
23736
23737 editor.splice_inlays(&inlay_ids, new_inlays, cx);
23738 })
23739 .ok()?;
23740 Some(())
23741 });
23742 }
23743
23744 fn on_buffer_event(
23745 &mut self,
23746 multibuffer: &Entity<MultiBuffer>,
23747 event: &multi_buffer::Event,
23748 window: &mut Window,
23749 cx: &mut Context<Self>,
23750 ) {
23751 match event {
23752 multi_buffer::Event::Edited { edited_buffer } => {
23753 self.scrollbar_marker_state.dirty = true;
23754 self.active_indent_guides_state.dirty = true;
23755 self.refresh_active_diagnostics(cx);
23756 self.refresh_code_actions(window, cx);
23757 self.refresh_single_line_folds(window, cx);
23758 self.refresh_matching_bracket_highlights(window, cx);
23759 if self.has_active_edit_prediction() {
23760 self.update_visible_edit_prediction(window, cx);
23761 }
23762
23763 // Clean up orphaned review comments after edits
23764 self.cleanup_orphaned_review_comments(cx);
23765
23766 if let Some(buffer) = edited_buffer {
23767 if buffer.read(cx).file().is_none() {
23768 cx.emit(EditorEvent::TitleChanged);
23769 }
23770
23771 if self.project.is_some() {
23772 let buffer_id = buffer.read(cx).remote_id();
23773 self.register_buffer(buffer_id, cx);
23774 self.update_lsp_data(Some(buffer_id), window, cx);
23775 self.refresh_inlay_hints(
23776 InlayHintRefreshReason::BufferEdited(buffer_id),
23777 cx,
23778 );
23779 }
23780 }
23781
23782 cx.emit(EditorEvent::BufferEdited);
23783 cx.emit(SearchEvent::MatchesInvalidated);
23784
23785 let Some(project) = &self.project else { return };
23786 let (telemetry, is_via_ssh) = {
23787 let project = project.read(cx);
23788 let telemetry = project.client().telemetry().clone();
23789 let is_via_ssh = project.is_via_remote_server();
23790 (telemetry, is_via_ssh)
23791 };
23792 telemetry.log_edit_event("editor", is_via_ssh);
23793 }
23794 multi_buffer::Event::ExcerptsAdded {
23795 buffer,
23796 predecessor,
23797 excerpts,
23798 } => {
23799 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
23800 let buffer_id = buffer.read(cx).remote_id();
23801 if self.buffer.read(cx).diff_for(buffer_id).is_none()
23802 && let Some(project) = &self.project
23803 {
23804 update_uncommitted_diff_for_buffer(
23805 cx.entity(),
23806 project,
23807 [buffer.clone()],
23808 self.buffer.clone(),
23809 cx,
23810 )
23811 .detach();
23812 }
23813 self.update_lsp_data(Some(buffer_id), window, cx);
23814 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
23815 self.colorize_brackets(false, cx);
23816 self.refresh_selected_text_highlights(true, window, cx);
23817 cx.emit(EditorEvent::ExcerptsAdded {
23818 buffer: buffer.clone(),
23819 predecessor: *predecessor,
23820 excerpts: excerpts.clone(),
23821 });
23822 }
23823 multi_buffer::Event::ExcerptsRemoved {
23824 ids,
23825 removed_buffer_ids,
23826 } => {
23827 if let Some(inlay_hints) = &mut self.inlay_hints {
23828 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
23829 }
23830 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
23831 for buffer_id in removed_buffer_ids {
23832 self.registered_buffers.remove(buffer_id);
23833 }
23834 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
23835 cx.emit(EditorEvent::ExcerptsRemoved {
23836 ids: ids.clone(),
23837 removed_buffer_ids: removed_buffer_ids.clone(),
23838 });
23839 }
23840 multi_buffer::Event::ExcerptsEdited {
23841 excerpt_ids,
23842 buffer_ids,
23843 } => {
23844 self.display_map.update(cx, |map, cx| {
23845 map.unfold_buffers(buffer_ids.iter().copied(), cx)
23846 });
23847 cx.emit(EditorEvent::ExcerptsEdited {
23848 ids: excerpt_ids.clone(),
23849 });
23850 }
23851 multi_buffer::Event::ExcerptsExpanded { ids } => {
23852 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
23853 self.refresh_document_highlights(cx);
23854 for id in ids {
23855 self.fetched_tree_sitter_chunks.remove(id);
23856 }
23857 self.colorize_brackets(false, cx);
23858 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
23859 }
23860 multi_buffer::Event::Reparsed(buffer_id) => {
23861 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
23862 self.refresh_selected_text_highlights(true, window, cx);
23863 self.colorize_brackets(true, cx);
23864 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
23865
23866 cx.emit(EditorEvent::Reparsed(*buffer_id));
23867 }
23868 multi_buffer::Event::DiffHunksToggled => {
23869 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
23870 }
23871 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
23872 if !is_fresh_language {
23873 self.registered_buffers.remove(&buffer_id);
23874 }
23875 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
23876 cx.emit(EditorEvent::Reparsed(*buffer_id));
23877 cx.notify();
23878 }
23879 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
23880 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
23881 multi_buffer::Event::FileHandleChanged
23882 | multi_buffer::Event::Reloaded
23883 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
23884 multi_buffer::Event::DiagnosticsUpdated => {
23885 self.update_diagnostics_state(window, cx);
23886 }
23887 _ => {}
23888 };
23889 }
23890
23891 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
23892 if !self.diagnostics_enabled() {
23893 return;
23894 }
23895 self.refresh_active_diagnostics(cx);
23896 self.refresh_inline_diagnostics(true, window, cx);
23897 self.scrollbar_marker_state.dirty = true;
23898 cx.notify();
23899 }
23900
23901 pub fn start_temporary_diff_override(&mut self) {
23902 self.load_diff_task.take();
23903 self.temporary_diff_override = true;
23904 }
23905
23906 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
23907 self.temporary_diff_override = false;
23908 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
23909 self.buffer.update(cx, |buffer, cx| {
23910 buffer.set_all_diff_hunks_collapsed(cx);
23911 });
23912
23913 if let Some(project) = self.project.clone() {
23914 self.load_diff_task = Some(
23915 update_uncommitted_diff_for_buffer(
23916 cx.entity(),
23917 &project,
23918 self.buffer.read(cx).all_buffers(),
23919 self.buffer.clone(),
23920 cx,
23921 )
23922 .shared(),
23923 );
23924 }
23925 }
23926
23927 fn on_display_map_changed(
23928 &mut self,
23929 _: Entity<DisplayMap>,
23930 _: &mut Window,
23931 cx: &mut Context<Self>,
23932 ) {
23933 cx.notify();
23934 }
23935
23936 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
23937 if !self.mode.is_full() {
23938 return None;
23939 }
23940
23941 let theme_settings = theme::ThemeSettings::get_global(cx);
23942 let theme = cx.theme();
23943 let accent_colors = theme.accents().clone();
23944
23945 let accent_overrides = theme_settings
23946 .theme_overrides
23947 .get(theme.name.as_ref())
23948 .map(|theme_style| &theme_style.accents)
23949 .into_iter()
23950 .flatten()
23951 .chain(
23952 theme_settings
23953 .experimental_theme_overrides
23954 .as_ref()
23955 .map(|overrides| &overrides.accents)
23956 .into_iter()
23957 .flatten(),
23958 )
23959 .flat_map(|accent| accent.0.clone().map(SharedString::from))
23960 .collect();
23961
23962 Some(AccentData {
23963 colors: accent_colors,
23964 overrides: accent_overrides,
23965 })
23966 }
23967
23968 fn fetch_applicable_language_settings(
23969 &self,
23970 cx: &App,
23971 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
23972 if !self.mode.is_full() {
23973 return HashMap::default();
23974 }
23975
23976 self.buffer().read(cx).all_buffers().into_iter().fold(
23977 HashMap::default(),
23978 |mut acc, buffer| {
23979 let buffer = buffer.read(cx);
23980 let language = buffer.language().map(|language| language.name());
23981 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
23982 let file = buffer.file();
23983 v.insert(language_settings(language, file, cx).into_owned());
23984 }
23985 acc
23986 },
23987 )
23988 }
23989
23990 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
23991 let new_language_settings = self.fetch_applicable_language_settings(cx);
23992 let language_settings_changed = new_language_settings != self.applicable_language_settings;
23993 self.applicable_language_settings = new_language_settings;
23994
23995 let new_accents = self.fetch_accent_data(cx);
23996 let accents_changed = new_accents != self.accent_data;
23997 self.accent_data = new_accents;
23998
23999 if self.diagnostics_enabled() {
24000 let new_severity = EditorSettings::get_global(cx)
24001 .diagnostics_max_severity
24002 .unwrap_or(DiagnosticSeverity::Hint);
24003 self.set_max_diagnostics_severity(new_severity, cx);
24004 }
24005 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
24006 self.update_edit_prediction_settings(cx);
24007 self.refresh_edit_prediction(true, false, window, cx);
24008 self.refresh_inline_values(cx);
24009 self.refresh_inlay_hints(
24010 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
24011 self.selections.newest_anchor().head(),
24012 &self.buffer.read(cx).snapshot(cx),
24013 cx,
24014 )),
24015 cx,
24016 );
24017
24018 let old_cursor_shape = self.cursor_shape;
24019 let old_show_breadcrumbs = self.show_breadcrumbs;
24020
24021 {
24022 let editor_settings = EditorSettings::get_global(cx);
24023 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
24024 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
24025 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
24026 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
24027 }
24028
24029 if old_cursor_shape != self.cursor_shape {
24030 cx.emit(EditorEvent::CursorShapeChanged);
24031 }
24032
24033 if old_show_breadcrumbs != self.show_breadcrumbs {
24034 cx.emit(EditorEvent::BreadcrumbsChanged);
24035 }
24036
24037 let project_settings = ProjectSettings::get_global(cx);
24038 self.buffer_serialization = self
24039 .should_serialize_buffer()
24040 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
24041
24042 if self.mode.is_full() {
24043 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
24044 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
24045 if self.show_inline_diagnostics != show_inline_diagnostics {
24046 self.show_inline_diagnostics = show_inline_diagnostics;
24047 self.refresh_inline_diagnostics(false, window, cx);
24048 }
24049
24050 if self.git_blame_inline_enabled != inline_blame_enabled {
24051 self.toggle_git_blame_inline_internal(false, window, cx);
24052 }
24053
24054 let minimap_settings = EditorSettings::get_global(cx).minimap;
24055 if self.minimap_visibility != MinimapVisibility::Disabled {
24056 if self.minimap_visibility.settings_visibility()
24057 != minimap_settings.minimap_enabled()
24058 {
24059 self.set_minimap_visibility(
24060 MinimapVisibility::for_mode(self.mode(), cx),
24061 window,
24062 cx,
24063 );
24064 } else if let Some(minimap_entity) = self.minimap.as_ref() {
24065 minimap_entity.update(cx, |minimap_editor, cx| {
24066 minimap_editor.update_minimap_configuration(minimap_settings, cx)
24067 })
24068 }
24069 }
24070
24071 if language_settings_changed || accents_changed {
24072 self.colorize_brackets(true, cx);
24073 }
24074
24075 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
24076 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
24077 }) {
24078 if !inlay_splice.is_empty() {
24079 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
24080 }
24081 self.refresh_colors_for_visible_range(None, window, cx);
24082 }
24083 }
24084
24085 cx.notify();
24086 }
24087
24088 fn theme_changed(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24089 if !self.mode.is_full() {
24090 return;
24091 }
24092
24093 let new_accents = self.fetch_accent_data(cx);
24094 if new_accents != self.accent_data {
24095 self.accent_data = new_accents;
24096 self.colorize_brackets(true, cx);
24097 }
24098 }
24099
24100 pub fn set_searchable(&mut self, searchable: bool) {
24101 self.searchable = searchable;
24102 }
24103
24104 pub fn searchable(&self) -> bool {
24105 self.searchable
24106 }
24107
24108 pub fn open_excerpts_in_split(
24109 &mut self,
24110 _: &OpenExcerptsSplit,
24111 window: &mut Window,
24112 cx: &mut Context<Self>,
24113 ) {
24114 self.open_excerpts_common(None, true, window, cx)
24115 }
24116
24117 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
24118 self.open_excerpts_common(None, false, window, cx)
24119 }
24120
24121 pub(crate) fn open_excerpts_common(
24122 &mut self,
24123 jump_data: Option<JumpData>,
24124 split: bool,
24125 window: &mut Window,
24126 cx: &mut Context<Self>,
24127 ) {
24128 let Some(workspace) = self.workspace() else {
24129 cx.propagate();
24130 return;
24131 };
24132
24133 if self.buffer.read(cx).is_singleton() {
24134 cx.propagate();
24135 return;
24136 }
24137
24138 let mut new_selections_by_buffer = HashMap::default();
24139 match &jump_data {
24140 Some(JumpData::MultiBufferPoint {
24141 excerpt_id,
24142 position,
24143 anchor,
24144 line_offset_from_top,
24145 }) => {
24146 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
24147 if let Some(buffer) = multi_buffer_snapshot
24148 .buffer_id_for_excerpt(*excerpt_id)
24149 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
24150 {
24151 let buffer_snapshot = buffer.read(cx).snapshot();
24152 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
24153 language::ToPoint::to_point(anchor, &buffer_snapshot)
24154 } else {
24155 buffer_snapshot.clip_point(*position, Bias::Left)
24156 };
24157 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
24158 new_selections_by_buffer.insert(
24159 buffer,
24160 (
24161 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
24162 Some(*line_offset_from_top),
24163 ),
24164 );
24165 }
24166 }
24167 Some(JumpData::MultiBufferRow {
24168 row,
24169 line_offset_from_top,
24170 }) => {
24171 let point = MultiBufferPoint::new(row.0, 0);
24172 if let Some((buffer, buffer_point, _)) =
24173 self.buffer.read(cx).point_to_buffer_point(point, cx)
24174 {
24175 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
24176 new_selections_by_buffer
24177 .entry(buffer)
24178 .or_insert((Vec::new(), Some(*line_offset_from_top)))
24179 .0
24180 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
24181 }
24182 }
24183 None => {
24184 let selections = self
24185 .selections
24186 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
24187 let multi_buffer = self.buffer.read(cx);
24188 for selection in selections {
24189 for (snapshot, range, _, anchor) in multi_buffer
24190 .snapshot(cx)
24191 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
24192 {
24193 if let Some(anchor) = anchor {
24194 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
24195 else {
24196 continue;
24197 };
24198 let offset = text::ToOffset::to_offset(
24199 &anchor.text_anchor,
24200 &buffer_handle.read(cx).snapshot(),
24201 );
24202 let range = BufferOffset(offset)..BufferOffset(offset);
24203 new_selections_by_buffer
24204 .entry(buffer_handle)
24205 .or_insert((Vec::new(), None))
24206 .0
24207 .push(range)
24208 } else {
24209 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
24210 else {
24211 continue;
24212 };
24213 new_selections_by_buffer
24214 .entry(buffer_handle)
24215 .or_insert((Vec::new(), None))
24216 .0
24217 .push(range)
24218 }
24219 }
24220 }
24221 }
24222 }
24223
24224 new_selections_by_buffer
24225 .retain(|buffer, _| buffer.read(cx).file().is_none_or(|file| file.can_open()));
24226
24227 if new_selections_by_buffer.is_empty() {
24228 return;
24229 }
24230
24231 // We defer the pane interaction because we ourselves are a workspace item
24232 // and activating a new item causes the pane to call a method on us reentrantly,
24233 // which panics if we're on the stack.
24234 window.defer(cx, move |window, cx| {
24235 workspace.update(cx, |workspace, cx| {
24236 let pane = if split {
24237 workspace.adjacent_pane(window, cx)
24238 } else {
24239 workspace.active_pane().clone()
24240 };
24241
24242 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
24243 let buffer_read = buffer.read(cx);
24244 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
24245 (true, project::File::from_dyn(Some(file)).is_some())
24246 } else {
24247 (false, false)
24248 };
24249
24250 // If project file is none workspace.open_project_item will fail to open the excerpt
24251 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
24252 // so we check if there's a tab match in that case first
24253 let editor = (!has_file || !is_project_file)
24254 .then(|| {
24255 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
24256 // so `workspace.open_project_item` will never find them, always opening a new editor.
24257 // Instead, we try to activate the existing editor in the pane first.
24258 let (editor, pane_item_index, pane_item_id) =
24259 pane.read(cx).items().enumerate().find_map(|(i, item)| {
24260 let editor = item.downcast::<Editor>()?;
24261 let singleton_buffer =
24262 editor.read(cx).buffer().read(cx).as_singleton()?;
24263 if singleton_buffer == buffer {
24264 Some((editor, i, item.item_id()))
24265 } else {
24266 None
24267 }
24268 })?;
24269 pane.update(cx, |pane, cx| {
24270 pane.activate_item(pane_item_index, true, true, window, cx);
24271 if !PreviewTabsSettings::get_global(cx)
24272 .enable_preview_from_multibuffer
24273 {
24274 pane.unpreview_item_if_preview(pane_item_id);
24275 }
24276 });
24277 Some(editor)
24278 })
24279 .flatten()
24280 .unwrap_or_else(|| {
24281 let keep_old_preview = PreviewTabsSettings::get_global(cx)
24282 .enable_keep_preview_on_code_navigation;
24283 let allow_new_preview =
24284 PreviewTabsSettings::get_global(cx).enable_preview_from_multibuffer;
24285 workspace.open_project_item::<Self>(
24286 pane.clone(),
24287 buffer,
24288 true,
24289 true,
24290 keep_old_preview,
24291 allow_new_preview,
24292 window,
24293 cx,
24294 )
24295 });
24296
24297 editor.update(cx, |editor, cx| {
24298 if has_file && !is_project_file {
24299 editor.set_read_only(true);
24300 }
24301 let autoscroll = match scroll_offset {
24302 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
24303 None => Autoscroll::newest(),
24304 };
24305 let nav_history = editor.nav_history.take();
24306 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
24307 let Some((&excerpt_id, _, buffer_snapshot)) =
24308 multibuffer_snapshot.as_singleton()
24309 else {
24310 return;
24311 };
24312 editor.change_selections(
24313 SelectionEffects::scroll(autoscroll),
24314 window,
24315 cx,
24316 |s| {
24317 s.select_ranges(ranges.into_iter().map(|range| {
24318 let range = buffer_snapshot.anchor_before(range.start)
24319 ..buffer_snapshot.anchor_after(range.end);
24320 multibuffer_snapshot
24321 .anchor_range_in_excerpt(excerpt_id, range)
24322 .unwrap()
24323 }));
24324 },
24325 );
24326 editor.nav_history = nav_history;
24327 });
24328 }
24329 })
24330 });
24331 }
24332
24333 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
24334 let snapshot = self.buffer.read(cx).read(cx);
24335 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
24336 Some(
24337 ranges
24338 .iter()
24339 .map(move |range| {
24340 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
24341 })
24342 .collect(),
24343 )
24344 }
24345
24346 fn selection_replacement_ranges(
24347 &self,
24348 range: Range<MultiBufferOffsetUtf16>,
24349 cx: &mut App,
24350 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
24351 let selections = self
24352 .selections
24353 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24354 let newest_selection = selections
24355 .iter()
24356 .max_by_key(|selection| selection.id)
24357 .unwrap();
24358 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
24359 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
24360 let snapshot = self.buffer.read(cx).read(cx);
24361 selections
24362 .into_iter()
24363 .map(|mut selection| {
24364 selection.start.0.0 =
24365 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
24366 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
24367 snapshot.clip_offset_utf16(selection.start, Bias::Left)
24368 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
24369 })
24370 .collect()
24371 }
24372
24373 fn report_editor_event(
24374 &self,
24375 reported_event: ReportEditorEvent,
24376 file_extension: Option<String>,
24377 cx: &App,
24378 ) {
24379 if cfg!(any(test, feature = "test-support")) {
24380 return;
24381 }
24382
24383 let Some(project) = &self.project else { return };
24384
24385 // If None, we are in a file without an extension
24386 let file = self
24387 .buffer
24388 .read(cx)
24389 .as_singleton()
24390 .and_then(|b| b.read(cx).file());
24391 let file_extension = file_extension.or(file
24392 .as_ref()
24393 .and_then(|file| Path::new(file.file_name(cx)).extension())
24394 .and_then(|e| e.to_str())
24395 .map(|a| a.to_string()));
24396
24397 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
24398 .map(|vim_mode| vim_mode.0)
24399 .unwrap_or(false);
24400
24401 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
24402 let copilot_enabled = edit_predictions_provider
24403 == language::language_settings::EditPredictionProvider::Copilot;
24404 let copilot_enabled_for_language = self
24405 .buffer
24406 .read(cx)
24407 .language_settings(cx)
24408 .show_edit_predictions;
24409
24410 let project = project.read(cx);
24411 let event_type = reported_event.event_type();
24412
24413 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
24414 telemetry::event!(
24415 event_type,
24416 type = if auto_saved {"autosave"} else {"manual"},
24417 file_extension,
24418 vim_mode,
24419 copilot_enabled,
24420 copilot_enabled_for_language,
24421 edit_predictions_provider,
24422 is_via_ssh = project.is_via_remote_server(),
24423 );
24424 } else {
24425 telemetry::event!(
24426 event_type,
24427 file_extension,
24428 vim_mode,
24429 copilot_enabled,
24430 copilot_enabled_for_language,
24431 edit_predictions_provider,
24432 is_via_ssh = project.is_via_remote_server(),
24433 );
24434 };
24435 }
24436
24437 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
24438 /// with each line being an array of {text, highlight} objects.
24439 fn copy_highlight_json(
24440 &mut self,
24441 _: &CopyHighlightJson,
24442 window: &mut Window,
24443 cx: &mut Context<Self>,
24444 ) {
24445 #[derive(Serialize)]
24446 struct Chunk<'a> {
24447 text: String,
24448 highlight: Option<&'a str>,
24449 }
24450
24451 let snapshot = self.buffer.read(cx).snapshot(cx);
24452 let range = self
24453 .selected_text_range(false, window, cx)
24454 .and_then(|selection| {
24455 if selection.range.is_empty() {
24456 None
24457 } else {
24458 Some(
24459 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
24460 selection.range.start,
24461 )))
24462 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
24463 selection.range.end,
24464 ))),
24465 )
24466 }
24467 })
24468 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
24469
24470 let chunks = snapshot.chunks(range, true);
24471 let mut lines = Vec::new();
24472 let mut line: VecDeque<Chunk> = VecDeque::new();
24473
24474 let Some(style) = self.style.as_ref() else {
24475 return;
24476 };
24477
24478 for chunk in chunks {
24479 let highlight = chunk
24480 .syntax_highlight_id
24481 .and_then(|id| id.name(&style.syntax));
24482 let mut chunk_lines = chunk.text.split('\n').peekable();
24483 while let Some(text) = chunk_lines.next() {
24484 let mut merged_with_last_token = false;
24485 if let Some(last_token) = line.back_mut()
24486 && last_token.highlight == highlight
24487 {
24488 last_token.text.push_str(text);
24489 merged_with_last_token = true;
24490 }
24491
24492 if !merged_with_last_token {
24493 line.push_back(Chunk {
24494 text: text.into(),
24495 highlight,
24496 });
24497 }
24498
24499 if chunk_lines.peek().is_some() {
24500 if line.len() > 1 && line.front().unwrap().text.is_empty() {
24501 line.pop_front();
24502 }
24503 if line.len() > 1 && line.back().unwrap().text.is_empty() {
24504 line.pop_back();
24505 }
24506
24507 lines.push(mem::take(&mut line));
24508 }
24509 }
24510 }
24511
24512 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
24513 return;
24514 };
24515 cx.write_to_clipboard(ClipboardItem::new_string(lines));
24516 }
24517
24518 pub fn open_context_menu(
24519 &mut self,
24520 _: &OpenContextMenu,
24521 window: &mut Window,
24522 cx: &mut Context<Self>,
24523 ) {
24524 self.request_autoscroll(Autoscroll::newest(), cx);
24525 let position = self
24526 .selections
24527 .newest_display(&self.display_snapshot(cx))
24528 .start;
24529 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
24530 }
24531
24532 pub fn replay_insert_event(
24533 &mut self,
24534 text: &str,
24535 relative_utf16_range: Option<Range<isize>>,
24536 window: &mut Window,
24537 cx: &mut Context<Self>,
24538 ) {
24539 if !self.input_enabled {
24540 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24541 return;
24542 }
24543 if let Some(relative_utf16_range) = relative_utf16_range {
24544 let selections = self
24545 .selections
24546 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24547 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24548 let new_ranges = selections.into_iter().map(|range| {
24549 let start = MultiBufferOffsetUtf16(OffsetUtf16(
24550 range
24551 .head()
24552 .0
24553 .0
24554 .saturating_add_signed(relative_utf16_range.start),
24555 ));
24556 let end = MultiBufferOffsetUtf16(OffsetUtf16(
24557 range
24558 .head()
24559 .0
24560 .0
24561 .saturating_add_signed(relative_utf16_range.end),
24562 ));
24563 start..end
24564 });
24565 s.select_ranges(new_ranges);
24566 });
24567 }
24568
24569 self.handle_input(text, window, cx);
24570 }
24571
24572 pub fn is_focused(&self, window: &Window) -> bool {
24573 self.focus_handle.is_focused(window)
24574 }
24575
24576 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24577 cx.emit(EditorEvent::Focused);
24578
24579 if let Some(descendant) = self
24580 .last_focused_descendant
24581 .take()
24582 .and_then(|descendant| descendant.upgrade())
24583 {
24584 window.focus(&descendant, cx);
24585 } else {
24586 if let Some(blame) = self.blame.as_ref() {
24587 blame.update(cx, GitBlame::focus)
24588 }
24589
24590 self.blink_manager.update(cx, BlinkManager::enable);
24591 self.show_cursor_names(window, cx);
24592 self.buffer.update(cx, |buffer, cx| {
24593 buffer.finalize_last_transaction(cx);
24594 if self.leader_id.is_none() {
24595 buffer.set_active_selections(
24596 &self.selections.disjoint_anchors_arc(),
24597 self.selections.line_mode(),
24598 self.cursor_shape,
24599 cx,
24600 );
24601 }
24602 });
24603
24604 if let Some(position_map) = self.last_position_map.clone() {
24605 EditorElement::mouse_moved(
24606 self,
24607 &MouseMoveEvent {
24608 position: window.mouse_position(),
24609 pressed_button: None,
24610 modifiers: window.modifiers(),
24611 },
24612 &position_map,
24613 None,
24614 window,
24615 cx,
24616 );
24617 }
24618 }
24619 }
24620
24621 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24622 cx.emit(EditorEvent::FocusedIn)
24623 }
24624
24625 fn handle_focus_out(
24626 &mut self,
24627 event: FocusOutEvent,
24628 _window: &mut Window,
24629 cx: &mut Context<Self>,
24630 ) {
24631 if event.blurred != self.focus_handle {
24632 self.last_focused_descendant = Some(event.blurred);
24633 }
24634 self.selection_drag_state = SelectionDragState::None;
24635 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
24636 }
24637
24638 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24639 self.blink_manager.update(cx, BlinkManager::disable);
24640 self.buffer
24641 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
24642
24643 if let Some(blame) = self.blame.as_ref() {
24644 blame.update(cx, GitBlame::blur)
24645 }
24646 if !self.hover_state.focused(window, cx) {
24647 hide_hover(self, cx);
24648 }
24649 if !self
24650 .context_menu
24651 .borrow()
24652 .as_ref()
24653 .is_some_and(|context_menu| context_menu.focused(window, cx))
24654 {
24655 self.hide_context_menu(window, cx);
24656 }
24657 self.take_active_edit_prediction(cx);
24658 cx.emit(EditorEvent::Blurred);
24659 cx.notify();
24660 }
24661
24662 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24663 let mut pending: String = window
24664 .pending_input_keystrokes()
24665 .into_iter()
24666 .flatten()
24667 .filter_map(|keystroke| keystroke.key_char.clone())
24668 .collect();
24669
24670 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
24671 pending = "".to_string();
24672 }
24673
24674 let existing_pending = self
24675 .text_highlights::<PendingInput>(cx)
24676 .map(|(_, ranges)| ranges.to_vec());
24677 if existing_pending.is_none() && pending.is_empty() {
24678 return;
24679 }
24680 let transaction =
24681 self.transact(window, cx, |this, window, cx| {
24682 let selections = this
24683 .selections
24684 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
24685 let edits = selections
24686 .iter()
24687 .map(|selection| (selection.end..selection.end, pending.clone()));
24688 this.edit(edits, cx);
24689 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24690 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
24691 sel.start + ix * pending.len()..sel.end + ix * pending.len()
24692 }));
24693 });
24694 if let Some(existing_ranges) = existing_pending {
24695 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
24696 this.edit(edits, cx);
24697 }
24698 });
24699
24700 let snapshot = self.snapshot(window, cx);
24701 let ranges = self
24702 .selections
24703 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
24704 .into_iter()
24705 .map(|selection| {
24706 snapshot.buffer_snapshot().anchor_after(selection.end)
24707 ..snapshot
24708 .buffer_snapshot()
24709 .anchor_before(selection.end + pending.len())
24710 })
24711 .collect();
24712
24713 if pending.is_empty() {
24714 self.clear_highlights::<PendingInput>(cx);
24715 } else {
24716 self.highlight_text::<PendingInput>(
24717 ranges,
24718 HighlightStyle {
24719 underline: Some(UnderlineStyle {
24720 thickness: px(1.),
24721 color: None,
24722 wavy: false,
24723 }),
24724 ..Default::default()
24725 },
24726 cx,
24727 );
24728 }
24729
24730 self.ime_transaction = self.ime_transaction.or(transaction);
24731 if let Some(transaction) = self.ime_transaction {
24732 self.buffer.update(cx, |buffer, cx| {
24733 buffer.group_until_transaction(transaction, cx);
24734 });
24735 }
24736
24737 if self.text_highlights::<PendingInput>(cx).is_none() {
24738 self.ime_transaction.take();
24739 }
24740 }
24741
24742 pub fn register_action_renderer(
24743 &mut self,
24744 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
24745 ) -> Subscription {
24746 let id = self.next_editor_action_id.post_inc();
24747 self.editor_actions
24748 .borrow_mut()
24749 .insert(id, Box::new(listener));
24750
24751 let editor_actions = self.editor_actions.clone();
24752 Subscription::new(move || {
24753 editor_actions.borrow_mut().remove(&id);
24754 })
24755 }
24756
24757 pub fn register_action<A: Action>(
24758 &mut self,
24759 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
24760 ) -> Subscription {
24761 let id = self.next_editor_action_id.post_inc();
24762 let listener = Arc::new(listener);
24763 self.editor_actions.borrow_mut().insert(
24764 id,
24765 Box::new(move |_, window, _| {
24766 let listener = listener.clone();
24767 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
24768 let action = action.downcast_ref().unwrap();
24769 if phase == DispatchPhase::Bubble {
24770 listener(action, window, cx)
24771 }
24772 })
24773 }),
24774 );
24775
24776 let editor_actions = self.editor_actions.clone();
24777 Subscription::new(move || {
24778 editor_actions.borrow_mut().remove(&id);
24779 })
24780 }
24781
24782 pub fn file_header_size(&self) -> u32 {
24783 FILE_HEADER_HEIGHT
24784 }
24785
24786 pub fn restore(
24787 &mut self,
24788 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
24789 window: &mut Window,
24790 cx: &mut Context<Self>,
24791 ) {
24792 self.buffer().update(cx, |multi_buffer, cx| {
24793 for (buffer_id, changes) in revert_changes {
24794 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
24795 buffer.update(cx, |buffer, cx| {
24796 buffer.edit(
24797 changes
24798 .into_iter()
24799 .map(|(range, text)| (range, text.to_string())),
24800 None,
24801 cx,
24802 );
24803 });
24804 }
24805 }
24806 });
24807 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24808 selections.refresh()
24809 });
24810 }
24811
24812 pub fn to_pixel_point(
24813 &mut self,
24814 source: Anchor,
24815 editor_snapshot: &EditorSnapshot,
24816 window: &mut Window,
24817 cx: &mut App,
24818 ) -> Option<gpui::Point<Pixels>> {
24819 let source_point = source.to_display_point(editor_snapshot);
24820 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
24821 }
24822
24823 pub fn display_to_pixel_point(
24824 &mut self,
24825 source: DisplayPoint,
24826 editor_snapshot: &EditorSnapshot,
24827 window: &mut Window,
24828 cx: &mut App,
24829 ) -> Option<gpui::Point<Pixels>> {
24830 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
24831 let text_layout_details = self.text_layout_details(window, cx);
24832 let scroll_top = text_layout_details
24833 .scroll_anchor
24834 .scroll_position(editor_snapshot)
24835 .y;
24836
24837 if source.row().as_f64() < scroll_top.floor() {
24838 return None;
24839 }
24840 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
24841 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
24842 Some(gpui::Point::new(source_x, source_y))
24843 }
24844
24845 pub fn has_visible_completions_menu(&self) -> bool {
24846 !self.edit_prediction_preview_is_active()
24847 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
24848 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
24849 })
24850 }
24851
24852 pub fn register_addon<T: Addon>(&mut self, instance: T) {
24853 if self.mode.is_minimap() {
24854 return;
24855 }
24856 self.addons
24857 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
24858 }
24859
24860 pub fn unregister_addon<T: Addon>(&mut self) {
24861 self.addons.remove(&std::any::TypeId::of::<T>());
24862 }
24863
24864 pub fn addon<T: Addon>(&self) -> Option<&T> {
24865 let type_id = std::any::TypeId::of::<T>();
24866 self.addons
24867 .get(&type_id)
24868 .and_then(|item| item.to_any().downcast_ref::<T>())
24869 }
24870
24871 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
24872 let type_id = std::any::TypeId::of::<T>();
24873 self.addons
24874 .get_mut(&type_id)
24875 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
24876 }
24877
24878 fn character_dimensions(&self, window: &mut Window, cx: &mut App) -> CharacterDimensions {
24879 let text_layout_details = self.text_layout_details(window, cx);
24880 let style = &text_layout_details.editor_style;
24881 let font_id = window.text_system().resolve_font(&style.text.font());
24882 let font_size = style.text.font_size.to_pixels(window.rem_size());
24883 let line_height = style.text.line_height_in_pixels(window.rem_size());
24884 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
24885 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
24886
24887 CharacterDimensions {
24888 em_width,
24889 em_advance,
24890 line_height,
24891 }
24892 }
24893
24894 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
24895 self.load_diff_task.clone()
24896 }
24897
24898 fn read_metadata_from_db(
24899 &mut self,
24900 item_id: u64,
24901 workspace_id: WorkspaceId,
24902 window: &mut Window,
24903 cx: &mut Context<Editor>,
24904 ) {
24905 if self.buffer_kind(cx) == ItemBufferKind::Singleton
24906 && !self.mode.is_minimap()
24907 && WorkspaceSettings::get(None, cx).restore_on_startup
24908 != RestoreOnStartupBehavior::EmptyTab
24909 {
24910 let buffer_snapshot = OnceCell::new();
24911
24912 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
24913 && !folds.is_empty()
24914 {
24915 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
24916 let snapshot_len = snapshot.len().0;
24917
24918 // Helper: search for fingerprint in buffer, return offset if found
24919 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
24920 // Ensure we start at a character boundary (defensive)
24921 let search_start = snapshot
24922 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
24923 .0;
24924 let search_end = snapshot_len.saturating_sub(fingerprint.len());
24925
24926 let mut byte_offset = search_start;
24927 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
24928 if byte_offset > search_end {
24929 break;
24930 }
24931 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
24932 return Some(byte_offset);
24933 }
24934 byte_offset += ch.len_utf8();
24935 }
24936 None
24937 };
24938
24939 // Track search position to handle duplicate fingerprints correctly.
24940 // Folds are stored in document order, so we advance after each match.
24941 let mut search_start = 0usize;
24942
24943 let valid_folds: Vec<_> = folds
24944 .into_iter()
24945 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
24946 // Skip folds without fingerprints (old data before migration)
24947 let sfp = start_fp?;
24948 let efp = end_fp?;
24949 let efp_len = efp.len();
24950
24951 // Fast path: check if fingerprints match at stored offsets
24952 // Note: end_fp is content BEFORE fold end, so check at (stored_end - efp_len)
24953 let start_matches = stored_start < snapshot_len
24954 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
24955 let efp_check_pos = stored_end.saturating_sub(efp_len);
24956 let end_matches = efp_check_pos >= stored_start
24957 && stored_end <= snapshot_len
24958 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
24959
24960 let (new_start, new_end) = if start_matches && end_matches {
24961 // Offsets unchanged, use stored values
24962 (stored_start, stored_end)
24963 } else if sfp == efp {
24964 // Short fold: identical fingerprints can only match once per search
24965 // Use stored fold length to compute new_end
24966 let new_start = find_fingerprint(&sfp, search_start)?;
24967 let fold_len = stored_end - stored_start;
24968 let new_end = new_start + fold_len;
24969 (new_start, new_end)
24970 } else {
24971 // Slow path: search for fingerprints in buffer
24972 let new_start = find_fingerprint(&sfp, search_start)?;
24973 // Search for end_fp after start, then add efp_len to get actual fold end
24974 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
24975 let new_end = efp_pos + efp_len;
24976 (new_start, new_end)
24977 };
24978
24979 // Advance search position for next fold
24980 search_start = new_end;
24981
24982 // Validate fold makes sense (end must be after start)
24983 if new_end <= new_start {
24984 return None;
24985 }
24986
24987 Some(
24988 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
24989 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
24990 )
24991 })
24992 .collect();
24993
24994 if !valid_folds.is_empty() {
24995 self.fold_ranges(valid_folds, false, window, cx);
24996
24997 // Migrate folds to current entity_id before workspace cleanup runs.
24998 // Entity IDs change between sessions, but workspace cleanup deletes
24999 // old editor rows (cascading to folds) based on current entity IDs.
25000 let new_editor_id = cx.entity().entity_id().as_u64() as ItemId;
25001 if new_editor_id != item_id {
25002 cx.spawn(async move |_, _| {
25003 DB.migrate_editor_folds(item_id, new_editor_id, workspace_id)
25004 .await
25005 .log_err();
25006 })
25007 .detach();
25008 }
25009 }
25010 }
25011
25012 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
25013 && !selections.is_empty()
25014 {
25015 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25016 // skip adding the initial selection to selection history
25017 self.selection_history.mode = SelectionHistoryMode::Skipping;
25018 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25019 s.select_ranges(selections.into_iter().map(|(start, end)| {
25020 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
25021 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
25022 }));
25023 });
25024 self.selection_history.mode = SelectionHistoryMode::Normal;
25025 };
25026 }
25027
25028 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
25029 }
25030
25031 fn update_lsp_data(
25032 &mut self,
25033 for_buffer: Option<BufferId>,
25034 window: &mut Window,
25035 cx: &mut Context<'_, Self>,
25036 ) {
25037 if let Some(buffer_id) = for_buffer {
25038 self.pull_diagnostics(buffer_id, window, cx);
25039 }
25040 self.refresh_colors_for_visible_range(for_buffer, window, cx);
25041 }
25042
25043 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
25044 if self.ignore_lsp_data() {
25045 return;
25046 }
25047 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
25048 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
25049 }
25050 }
25051
25052 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
25053 if self.ignore_lsp_data() {
25054 return;
25055 }
25056
25057 if !self.registered_buffers.contains_key(&buffer_id)
25058 && let Some(project) = self.project.as_ref()
25059 {
25060 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
25061 project.update(cx, |project, cx| {
25062 self.registered_buffers.insert(
25063 buffer_id,
25064 project.register_buffer_with_language_servers(&buffer, cx),
25065 );
25066 });
25067 } else {
25068 self.registered_buffers.remove(&buffer_id);
25069 }
25070 }
25071 }
25072
25073 fn ignore_lsp_data(&self) -> bool {
25074 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
25075 // skip any LSP updates for it.
25076 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
25077 }
25078
25079 pub(crate) fn create_style(&self, cx: &App) -> EditorStyle {
25080 let settings = ThemeSettings::get_global(cx);
25081
25082 let mut text_style = match self.mode {
25083 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
25084 color: cx.theme().colors().editor_foreground,
25085 font_family: settings.ui_font.family.clone(),
25086 font_features: settings.ui_font.features.clone(),
25087 font_fallbacks: settings.ui_font.fallbacks.clone(),
25088 font_size: rems(0.875).into(),
25089 font_weight: settings.ui_font.weight,
25090 line_height: relative(settings.buffer_line_height.value()),
25091 ..Default::default()
25092 },
25093 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
25094 color: cx.theme().colors().editor_foreground,
25095 font_family: settings.buffer_font.family.clone(),
25096 font_features: settings.buffer_font.features.clone(),
25097 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25098 font_size: settings.buffer_font_size(cx).into(),
25099 font_weight: settings.buffer_font.weight,
25100 line_height: relative(settings.buffer_line_height.value()),
25101 ..Default::default()
25102 },
25103 };
25104 if let Some(text_style_refinement) = &self.text_style_refinement {
25105 text_style.refine(text_style_refinement)
25106 }
25107
25108 let background = match self.mode {
25109 EditorMode::SingleLine => cx.theme().system().transparent,
25110 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
25111 EditorMode::Full { .. } => cx.theme().colors().editor_background,
25112 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
25113 };
25114
25115 EditorStyle {
25116 background,
25117 border: cx.theme().colors().border,
25118 local_player: cx.theme().players().local(),
25119 text: text_style,
25120 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
25121 syntax: cx.theme().syntax().clone(),
25122 status: cx.theme().status().clone(),
25123 inlay_hints_style: make_inlay_hints_style(cx),
25124 edit_prediction_styles: make_suggestion_styles(cx),
25125 unnecessary_code_fade: settings.unnecessary_code_fade,
25126 show_underlines: self.diagnostics_enabled(),
25127 }
25128 }
25129 fn breadcrumbs_inner(&self, variant: &Theme, cx: &App) -> Option<Vec<BreadcrumbText>> {
25130 let cursor = self.selections.newest_anchor().head();
25131 let multibuffer = self.buffer().read(cx);
25132 let is_singleton = multibuffer.is_singleton();
25133 let (buffer_id, symbols) = multibuffer
25134 .read(cx)
25135 .symbols_containing(cursor, Some(variant.syntax()))?;
25136 let buffer = multibuffer.buffer(buffer_id)?;
25137
25138 let buffer = buffer.read(cx);
25139 let settings = ThemeSettings::get_global(cx);
25140 // In a multi-buffer layout, we don't want to include the filename in the breadcrumbs
25141 let mut breadcrumbs = if is_singleton {
25142 let text = self.breadcrumb_header.clone().unwrap_or_else(|| {
25143 buffer
25144 .snapshot()
25145 .resolve_file_path(
25146 self.project
25147 .as_ref()
25148 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
25149 .unwrap_or_default(),
25150 cx,
25151 )
25152 .unwrap_or_else(|| {
25153 if multibuffer.is_singleton() {
25154 multibuffer.title(cx).to_string()
25155 } else {
25156 "untitled".to_string()
25157 }
25158 })
25159 });
25160 vec![BreadcrumbText {
25161 text,
25162 highlights: None,
25163 font: Some(settings.buffer_font.clone()),
25164 }]
25165 } else {
25166 vec![]
25167 };
25168
25169 breadcrumbs.extend(symbols.into_iter().map(|symbol| BreadcrumbText {
25170 text: symbol.text,
25171 highlights: Some(symbol.highlight_ranges),
25172 font: Some(settings.buffer_font.clone()),
25173 }));
25174 Some(breadcrumbs)
25175 }
25176}
25177
25178fn edit_for_markdown_paste<'a>(
25179 buffer: &MultiBufferSnapshot,
25180 range: Range<MultiBufferOffset>,
25181 to_insert: &'a str,
25182 url: Option<url::Url>,
25183) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
25184 if url.is_none() {
25185 return (range, Cow::Borrowed(to_insert));
25186 };
25187
25188 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
25189
25190 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
25191 Cow::Borrowed(to_insert)
25192 } else {
25193 Cow::Owned(format!("[{old_text}]({to_insert})"))
25194 };
25195 (range, new_text)
25196}
25197
25198fn process_completion_for_edit(
25199 completion: &Completion,
25200 intent: CompletionIntent,
25201 buffer: &Entity<Buffer>,
25202 cursor_position: &text::Anchor,
25203 cx: &mut Context<Editor>,
25204) -> CompletionEdit {
25205 let buffer = buffer.read(cx);
25206 let buffer_snapshot = buffer.snapshot();
25207 let (snippet, new_text) = if completion.is_snippet() {
25208 let mut snippet_source = completion.new_text.clone();
25209 // Workaround for typescript language server issues so that methods don't expand within
25210 // strings and functions with type expressions. The previous point is used because the query
25211 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
25212 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
25213 let previous_point = if previous_point.column > 0 {
25214 cursor_position.to_previous_offset(&buffer_snapshot)
25215 } else {
25216 cursor_position.to_offset(&buffer_snapshot)
25217 };
25218 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
25219 && scope.prefers_label_for_snippet_in_completion()
25220 && let Some(label) = completion.label()
25221 && matches!(
25222 completion.kind(),
25223 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
25224 )
25225 {
25226 snippet_source = label;
25227 }
25228 match Snippet::parse(&snippet_source).log_err() {
25229 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
25230 None => (None, completion.new_text.clone()),
25231 }
25232 } else {
25233 (None, completion.new_text.clone())
25234 };
25235
25236 let mut range_to_replace = {
25237 let replace_range = &completion.replace_range;
25238 if let CompletionSource::Lsp {
25239 insert_range: Some(insert_range),
25240 ..
25241 } = &completion.source
25242 {
25243 debug_assert_eq!(
25244 insert_range.start, replace_range.start,
25245 "insert_range and replace_range should start at the same position"
25246 );
25247 debug_assert!(
25248 insert_range
25249 .start
25250 .cmp(cursor_position, &buffer_snapshot)
25251 .is_le(),
25252 "insert_range should start before or at cursor position"
25253 );
25254 debug_assert!(
25255 replace_range
25256 .start
25257 .cmp(cursor_position, &buffer_snapshot)
25258 .is_le(),
25259 "replace_range should start before or at cursor position"
25260 );
25261
25262 let should_replace = match intent {
25263 CompletionIntent::CompleteWithInsert => false,
25264 CompletionIntent::CompleteWithReplace => true,
25265 CompletionIntent::Complete | CompletionIntent::Compose => {
25266 let insert_mode =
25267 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
25268 .completions
25269 .lsp_insert_mode;
25270 match insert_mode {
25271 LspInsertMode::Insert => false,
25272 LspInsertMode::Replace => true,
25273 LspInsertMode::ReplaceSubsequence => {
25274 let mut text_to_replace = buffer.chars_for_range(
25275 buffer.anchor_before(replace_range.start)
25276 ..buffer.anchor_after(replace_range.end),
25277 );
25278 let mut current_needle = text_to_replace.next();
25279 for haystack_ch in completion.label.text.chars() {
25280 if let Some(needle_ch) = current_needle
25281 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
25282 {
25283 current_needle = text_to_replace.next();
25284 }
25285 }
25286 current_needle.is_none()
25287 }
25288 LspInsertMode::ReplaceSuffix => {
25289 if replace_range
25290 .end
25291 .cmp(cursor_position, &buffer_snapshot)
25292 .is_gt()
25293 {
25294 let range_after_cursor = *cursor_position..replace_range.end;
25295 let text_after_cursor = buffer
25296 .text_for_range(
25297 buffer.anchor_before(range_after_cursor.start)
25298 ..buffer.anchor_after(range_after_cursor.end),
25299 )
25300 .collect::<String>()
25301 .to_ascii_lowercase();
25302 completion
25303 .label
25304 .text
25305 .to_ascii_lowercase()
25306 .ends_with(&text_after_cursor)
25307 } else {
25308 true
25309 }
25310 }
25311 }
25312 }
25313 };
25314
25315 if should_replace {
25316 replace_range.clone()
25317 } else {
25318 insert_range.clone()
25319 }
25320 } else {
25321 replace_range.clone()
25322 }
25323 };
25324
25325 if range_to_replace
25326 .end
25327 .cmp(cursor_position, &buffer_snapshot)
25328 .is_lt()
25329 {
25330 range_to_replace.end = *cursor_position;
25331 }
25332
25333 let replace_range = range_to_replace.to_offset(buffer);
25334 CompletionEdit {
25335 new_text,
25336 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
25337 snippet,
25338 }
25339}
25340
25341struct CompletionEdit {
25342 new_text: String,
25343 replace_range: Range<BufferOffset>,
25344 snippet: Option<Snippet>,
25345}
25346
25347fn comment_delimiter_for_newline(
25348 start_point: &Point,
25349 buffer: &MultiBufferSnapshot,
25350 language: &LanguageScope,
25351) -> Option<Arc<str>> {
25352 let delimiters = language.line_comment_prefixes();
25353 let max_len_of_delimiter = delimiters.iter().map(|delimiter| delimiter.len()).max()?;
25354 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25355
25356 let num_of_whitespaces = snapshot
25357 .chars_for_range(range.clone())
25358 .take_while(|c| c.is_whitespace())
25359 .count();
25360 let comment_candidate = snapshot
25361 .chars_for_range(range.clone())
25362 .skip(num_of_whitespaces)
25363 .take(max_len_of_delimiter)
25364 .collect::<String>();
25365 let (delimiter, trimmed_len) = delimiters
25366 .iter()
25367 .filter_map(|delimiter| {
25368 let prefix = delimiter.trim_end();
25369 if comment_candidate.starts_with(prefix) {
25370 Some((delimiter, prefix.len()))
25371 } else {
25372 None
25373 }
25374 })
25375 .max_by_key(|(_, len)| *len)?;
25376
25377 if let Some(BlockCommentConfig {
25378 start: block_start, ..
25379 }) = language.block_comment()
25380 {
25381 let block_start_trimmed = block_start.trim_end();
25382 if block_start_trimmed.starts_with(delimiter.trim_end()) {
25383 let line_content = snapshot
25384 .chars_for_range(range)
25385 .skip(num_of_whitespaces)
25386 .take(block_start_trimmed.len())
25387 .collect::<String>();
25388
25389 if line_content.starts_with(block_start_trimmed) {
25390 return None;
25391 }
25392 }
25393 }
25394
25395 let cursor_is_placed_after_comment_marker =
25396 num_of_whitespaces + trimmed_len <= start_point.column as usize;
25397 if cursor_is_placed_after_comment_marker {
25398 Some(delimiter.clone())
25399 } else {
25400 None
25401 }
25402}
25403
25404fn documentation_delimiter_for_newline(
25405 start_point: &Point,
25406 buffer: &MultiBufferSnapshot,
25407 language: &LanguageScope,
25408 newline_config: &mut NewlineConfig,
25409) -> Option<Arc<str>> {
25410 let BlockCommentConfig {
25411 start: start_tag,
25412 end: end_tag,
25413 prefix: delimiter,
25414 tab_size: len,
25415 } = language.documentation_comment()?;
25416 let is_within_block_comment = buffer
25417 .language_scope_at(*start_point)
25418 .is_some_and(|scope| scope.override_name() == Some("comment"));
25419 if !is_within_block_comment {
25420 return None;
25421 }
25422
25423 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25424
25425 let num_of_whitespaces = snapshot
25426 .chars_for_range(range.clone())
25427 .take_while(|c| c.is_whitespace())
25428 .count();
25429
25430 // 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.
25431 let column = start_point.column;
25432 let cursor_is_after_start_tag = {
25433 let start_tag_len = start_tag.len();
25434 let start_tag_line = snapshot
25435 .chars_for_range(range.clone())
25436 .skip(num_of_whitespaces)
25437 .take(start_tag_len)
25438 .collect::<String>();
25439 if start_tag_line.starts_with(start_tag.as_ref()) {
25440 num_of_whitespaces + start_tag_len <= column as usize
25441 } else {
25442 false
25443 }
25444 };
25445
25446 let cursor_is_after_delimiter = {
25447 let delimiter_trim = delimiter.trim_end();
25448 let delimiter_line = snapshot
25449 .chars_for_range(range.clone())
25450 .skip(num_of_whitespaces)
25451 .take(delimiter_trim.len())
25452 .collect::<String>();
25453 if delimiter_line.starts_with(delimiter_trim) {
25454 num_of_whitespaces + delimiter_trim.len() <= column as usize
25455 } else {
25456 false
25457 }
25458 };
25459
25460 let mut needs_extra_line = false;
25461 let mut extra_line_additional_indent = IndentSize::spaces(0);
25462
25463 let cursor_is_before_end_tag_if_exists = {
25464 let mut char_position = 0u32;
25465 let mut end_tag_offset = None;
25466
25467 'outer: for chunk in snapshot.text_for_range(range) {
25468 if let Some(byte_pos) = chunk.find(&**end_tag) {
25469 let chars_before_match = chunk[..byte_pos].chars().count() as u32;
25470 end_tag_offset = Some(char_position + chars_before_match);
25471 break 'outer;
25472 }
25473 char_position += chunk.chars().count() as u32;
25474 }
25475
25476 if let Some(end_tag_offset) = end_tag_offset {
25477 let cursor_is_before_end_tag = column <= end_tag_offset;
25478 if cursor_is_after_start_tag {
25479 if cursor_is_before_end_tag {
25480 needs_extra_line = true;
25481 }
25482 let cursor_is_at_start_of_end_tag = column == end_tag_offset;
25483 if cursor_is_at_start_of_end_tag {
25484 extra_line_additional_indent.len = *len;
25485 }
25486 }
25487 cursor_is_before_end_tag
25488 } else {
25489 true
25490 }
25491 };
25492
25493 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
25494 && cursor_is_before_end_tag_if_exists
25495 {
25496 let additional_indent = if cursor_is_after_start_tag {
25497 IndentSize::spaces(*len)
25498 } else {
25499 IndentSize::spaces(0)
25500 };
25501
25502 *newline_config = NewlineConfig::Newline {
25503 additional_indent,
25504 extra_line_additional_indent: if needs_extra_line {
25505 Some(extra_line_additional_indent)
25506 } else {
25507 None
25508 },
25509 prevent_auto_indent: true,
25510 };
25511 Some(delimiter.clone())
25512 } else {
25513 None
25514 }
25515}
25516
25517const ORDERED_LIST_MAX_MARKER_LEN: usize = 16;
25518
25519fn list_delimiter_for_newline(
25520 start_point: &Point,
25521 buffer: &MultiBufferSnapshot,
25522 language: &LanguageScope,
25523 newline_config: &mut NewlineConfig,
25524) -> Option<Arc<str>> {
25525 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25526
25527 let num_of_whitespaces = snapshot
25528 .chars_for_range(range.clone())
25529 .take_while(|c| c.is_whitespace())
25530 .count();
25531
25532 let task_list_entries: Vec<_> = language
25533 .task_list()
25534 .into_iter()
25535 .flat_map(|config| {
25536 config
25537 .prefixes
25538 .iter()
25539 .map(|prefix| (prefix.as_ref(), config.continuation.as_ref()))
25540 })
25541 .collect();
25542 let unordered_list_entries: Vec<_> = language
25543 .unordered_list()
25544 .iter()
25545 .map(|marker| (marker.as_ref(), marker.as_ref()))
25546 .collect();
25547
25548 let all_entries: Vec<_> = task_list_entries
25549 .into_iter()
25550 .chain(unordered_list_entries)
25551 .collect();
25552
25553 if let Some(max_prefix_len) = all_entries.iter().map(|(p, _)| p.len()).max() {
25554 let candidate: String = snapshot
25555 .chars_for_range(range.clone())
25556 .skip(num_of_whitespaces)
25557 .take(max_prefix_len)
25558 .collect();
25559
25560 if let Some((prefix, continuation)) = all_entries
25561 .iter()
25562 .filter(|(prefix, _)| candidate.starts_with(*prefix))
25563 .max_by_key(|(prefix, _)| prefix.len())
25564 {
25565 let end_of_prefix = num_of_whitespaces + prefix.len();
25566 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
25567 let has_content_after_marker = snapshot
25568 .chars_for_range(range)
25569 .skip(end_of_prefix)
25570 .any(|c| !c.is_whitespace());
25571
25572 if has_content_after_marker && cursor_is_after_prefix {
25573 return Some((*continuation).into());
25574 }
25575
25576 if start_point.column as usize == end_of_prefix {
25577 if num_of_whitespaces == 0 {
25578 *newline_config = NewlineConfig::ClearCurrentLine;
25579 } else {
25580 *newline_config = NewlineConfig::UnindentCurrentLine {
25581 continuation: (*continuation).into(),
25582 };
25583 }
25584 }
25585
25586 return None;
25587 }
25588 }
25589
25590 let candidate: String = snapshot
25591 .chars_for_range(range.clone())
25592 .skip(num_of_whitespaces)
25593 .take(ORDERED_LIST_MAX_MARKER_LEN)
25594 .collect();
25595
25596 for ordered_config in language.ordered_list() {
25597 let regex = match Regex::new(&ordered_config.pattern) {
25598 Ok(r) => r,
25599 Err(_) => continue,
25600 };
25601
25602 if let Some(captures) = regex.captures(&candidate) {
25603 let full_match = captures.get(0)?;
25604 let marker_len = full_match.len();
25605 let end_of_prefix = num_of_whitespaces + marker_len;
25606 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
25607
25608 let has_content_after_marker = snapshot
25609 .chars_for_range(range)
25610 .skip(end_of_prefix)
25611 .any(|c| !c.is_whitespace());
25612
25613 if has_content_after_marker && cursor_is_after_prefix {
25614 let number: u32 = captures.get(1)?.as_str().parse().ok()?;
25615 let continuation = ordered_config
25616 .format
25617 .replace("{1}", &(number + 1).to_string());
25618 return Some(continuation.into());
25619 }
25620
25621 if start_point.column as usize == end_of_prefix {
25622 let continuation = ordered_config.format.replace("{1}", "1");
25623 if num_of_whitespaces == 0 {
25624 *newline_config = NewlineConfig::ClearCurrentLine;
25625 } else {
25626 *newline_config = NewlineConfig::UnindentCurrentLine {
25627 continuation: continuation.into(),
25628 };
25629 }
25630 }
25631
25632 return None;
25633 }
25634 }
25635
25636 None
25637}
25638
25639fn is_list_prefix_row(
25640 row: MultiBufferRow,
25641 buffer: &MultiBufferSnapshot,
25642 language: &LanguageScope,
25643) -> bool {
25644 let Some((snapshot, range)) = buffer.buffer_line_for_row(row) else {
25645 return false;
25646 };
25647
25648 let num_of_whitespaces = snapshot
25649 .chars_for_range(range.clone())
25650 .take_while(|c| c.is_whitespace())
25651 .count();
25652
25653 let task_list_prefixes: Vec<_> = language
25654 .task_list()
25655 .into_iter()
25656 .flat_map(|config| {
25657 config
25658 .prefixes
25659 .iter()
25660 .map(|p| p.as_ref())
25661 .collect::<Vec<_>>()
25662 })
25663 .collect();
25664 let unordered_list_markers: Vec<_> = language
25665 .unordered_list()
25666 .iter()
25667 .map(|marker| marker.as_ref())
25668 .collect();
25669 let all_prefixes: Vec<_> = task_list_prefixes
25670 .into_iter()
25671 .chain(unordered_list_markers)
25672 .collect();
25673 if let Some(max_prefix_len) = all_prefixes.iter().map(|p| p.len()).max() {
25674 let candidate: String = snapshot
25675 .chars_for_range(range.clone())
25676 .skip(num_of_whitespaces)
25677 .take(max_prefix_len)
25678 .collect();
25679 if all_prefixes
25680 .iter()
25681 .any(|prefix| candidate.starts_with(*prefix))
25682 {
25683 return true;
25684 }
25685 }
25686
25687 let ordered_list_candidate: String = snapshot
25688 .chars_for_range(range)
25689 .skip(num_of_whitespaces)
25690 .take(ORDERED_LIST_MAX_MARKER_LEN)
25691 .collect();
25692 for ordered_config in language.ordered_list() {
25693 let regex = match Regex::new(&ordered_config.pattern) {
25694 Ok(r) => r,
25695 Err(_) => continue,
25696 };
25697 if let Some(captures) = regex.captures(&ordered_list_candidate) {
25698 return captures.get(0).is_some();
25699 }
25700 }
25701
25702 false
25703}
25704
25705#[derive(Debug)]
25706enum NewlineConfig {
25707 /// Insert newline with optional additional indent and optional extra blank line
25708 Newline {
25709 additional_indent: IndentSize,
25710 extra_line_additional_indent: Option<IndentSize>,
25711 prevent_auto_indent: bool,
25712 },
25713 /// Clear the current line
25714 ClearCurrentLine,
25715 /// Unindent the current line and add continuation
25716 UnindentCurrentLine { continuation: Arc<str> },
25717}
25718
25719impl NewlineConfig {
25720 fn has_extra_line(&self) -> bool {
25721 matches!(
25722 self,
25723 Self::Newline {
25724 extra_line_additional_indent: Some(_),
25725 ..
25726 }
25727 )
25728 }
25729
25730 fn insert_extra_newline_brackets(
25731 buffer: &MultiBufferSnapshot,
25732 range: Range<MultiBufferOffset>,
25733 language: &language::LanguageScope,
25734 ) -> bool {
25735 let leading_whitespace_len = buffer
25736 .reversed_chars_at(range.start)
25737 .take_while(|c| c.is_whitespace() && *c != '\n')
25738 .map(|c| c.len_utf8())
25739 .sum::<usize>();
25740 let trailing_whitespace_len = buffer
25741 .chars_at(range.end)
25742 .take_while(|c| c.is_whitespace() && *c != '\n')
25743 .map(|c| c.len_utf8())
25744 .sum::<usize>();
25745 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
25746
25747 language.brackets().any(|(pair, enabled)| {
25748 let pair_start = pair.start.trim_end();
25749 let pair_end = pair.end.trim_start();
25750
25751 enabled
25752 && pair.newline
25753 && buffer.contains_str_at(range.end, pair_end)
25754 && buffer.contains_str_at(
25755 range.start.saturating_sub_usize(pair_start.len()),
25756 pair_start,
25757 )
25758 })
25759 }
25760
25761 fn insert_extra_newline_tree_sitter(
25762 buffer: &MultiBufferSnapshot,
25763 range: Range<MultiBufferOffset>,
25764 ) -> bool {
25765 let (buffer, range) = match buffer
25766 .range_to_buffer_ranges(range.start..=range.end)
25767 .as_slice()
25768 {
25769 [(buffer, range, _)] => (*buffer, range.clone()),
25770 _ => return false,
25771 };
25772 let pair = {
25773 let mut result: Option<BracketMatch<usize>> = None;
25774
25775 for pair in buffer
25776 .all_bracket_ranges(range.start.0..range.end.0)
25777 .filter(move |pair| {
25778 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
25779 })
25780 {
25781 let len = pair.close_range.end - pair.open_range.start;
25782
25783 if let Some(existing) = &result {
25784 let existing_len = existing.close_range.end - existing.open_range.start;
25785 if len > existing_len {
25786 continue;
25787 }
25788 }
25789
25790 result = Some(pair);
25791 }
25792
25793 result
25794 };
25795 let Some(pair) = pair else {
25796 return false;
25797 };
25798 pair.newline_only
25799 && buffer
25800 .chars_for_range(pair.open_range.end..range.start.0)
25801 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
25802 .all(|c| c.is_whitespace() && c != '\n')
25803 }
25804}
25805
25806fn update_uncommitted_diff_for_buffer(
25807 editor: Entity<Editor>,
25808 project: &Entity<Project>,
25809 buffers: impl IntoIterator<Item = Entity<Buffer>>,
25810 buffer: Entity<MultiBuffer>,
25811 cx: &mut App,
25812) -> Task<()> {
25813 let mut tasks = Vec::new();
25814 project.update(cx, |project, cx| {
25815 for buffer in buffers {
25816 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
25817 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
25818 }
25819 }
25820 });
25821 cx.spawn(async move |cx| {
25822 let diffs = future::join_all(tasks).await;
25823 if editor.read_with(cx, |editor, _cx| editor.temporary_diff_override) {
25824 return;
25825 }
25826
25827 buffer.update(cx, |buffer, cx| {
25828 for diff in diffs.into_iter().flatten() {
25829 buffer.add_diff(diff, cx);
25830 }
25831 });
25832 })
25833}
25834
25835fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
25836 let tab_size = tab_size.get() as usize;
25837 let mut width = offset;
25838
25839 for ch in text.chars() {
25840 width += if ch == '\t' {
25841 tab_size - (width % tab_size)
25842 } else {
25843 1
25844 };
25845 }
25846
25847 width - offset
25848}
25849
25850#[cfg(test)]
25851mod tests {
25852 use super::*;
25853
25854 #[test]
25855 fn test_string_size_with_expanded_tabs() {
25856 let nz = |val| NonZeroU32::new(val).unwrap();
25857 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
25858 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
25859 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
25860 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
25861 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
25862 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
25863 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
25864 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
25865 }
25866}
25867
25868/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
25869struct WordBreakingTokenizer<'a> {
25870 input: &'a str,
25871}
25872
25873impl<'a> WordBreakingTokenizer<'a> {
25874 fn new(input: &'a str) -> Self {
25875 Self { input }
25876 }
25877}
25878
25879fn is_char_ideographic(ch: char) -> bool {
25880 use unicode_script::Script::*;
25881 use unicode_script::UnicodeScript;
25882 matches!(ch.script(), Han | Tangut | Yi)
25883}
25884
25885fn is_grapheme_ideographic(text: &str) -> bool {
25886 text.chars().any(is_char_ideographic)
25887}
25888
25889fn is_grapheme_whitespace(text: &str) -> bool {
25890 text.chars().any(|x| x.is_whitespace())
25891}
25892
25893fn should_stay_with_preceding_ideograph(text: &str) -> bool {
25894 text.chars()
25895 .next()
25896 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
25897}
25898
25899#[derive(PartialEq, Eq, Debug, Clone, Copy)]
25900enum WordBreakToken<'a> {
25901 Word { token: &'a str, grapheme_len: usize },
25902 InlineWhitespace { token: &'a str, grapheme_len: usize },
25903 Newline,
25904}
25905
25906impl<'a> Iterator for WordBreakingTokenizer<'a> {
25907 /// Yields a span, the count of graphemes in the token, and whether it was
25908 /// whitespace. Note that it also breaks at word boundaries.
25909 type Item = WordBreakToken<'a>;
25910
25911 fn next(&mut self) -> Option<Self::Item> {
25912 use unicode_segmentation::UnicodeSegmentation;
25913 if self.input.is_empty() {
25914 return None;
25915 }
25916
25917 let mut iter = self.input.graphemes(true).peekable();
25918 let mut offset = 0;
25919 let mut grapheme_len = 0;
25920 if let Some(first_grapheme) = iter.next() {
25921 let is_newline = first_grapheme == "\n";
25922 let is_whitespace = is_grapheme_whitespace(first_grapheme);
25923 offset += first_grapheme.len();
25924 grapheme_len += 1;
25925 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
25926 if let Some(grapheme) = iter.peek().copied()
25927 && should_stay_with_preceding_ideograph(grapheme)
25928 {
25929 offset += grapheme.len();
25930 grapheme_len += 1;
25931 }
25932 } else {
25933 let mut words = self.input[offset..].split_word_bound_indices().peekable();
25934 let mut next_word_bound = words.peek().copied();
25935 if next_word_bound.is_some_and(|(i, _)| i == 0) {
25936 next_word_bound = words.next();
25937 }
25938 while let Some(grapheme) = iter.peek().copied() {
25939 if next_word_bound.is_some_and(|(i, _)| i == offset) {
25940 break;
25941 };
25942 if is_grapheme_whitespace(grapheme) != is_whitespace
25943 || (grapheme == "\n") != is_newline
25944 {
25945 break;
25946 };
25947 offset += grapheme.len();
25948 grapheme_len += 1;
25949 iter.next();
25950 }
25951 }
25952 let token = &self.input[..offset];
25953 self.input = &self.input[offset..];
25954 if token == "\n" {
25955 Some(WordBreakToken::Newline)
25956 } else if is_whitespace {
25957 Some(WordBreakToken::InlineWhitespace {
25958 token,
25959 grapheme_len,
25960 })
25961 } else {
25962 Some(WordBreakToken::Word {
25963 token,
25964 grapheme_len,
25965 })
25966 }
25967 } else {
25968 None
25969 }
25970 }
25971}
25972
25973#[test]
25974fn test_word_breaking_tokenizer() {
25975 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
25976 ("", &[]),
25977 (" ", &[whitespace(" ", 2)]),
25978 ("Ʒ", &[word("Ʒ", 1)]),
25979 ("Ǽ", &[word("Ǽ", 1)]),
25980 ("⋑", &[word("⋑", 1)]),
25981 ("⋑⋑", &[word("⋑⋑", 2)]),
25982 (
25983 "原理,进而",
25984 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
25985 ),
25986 (
25987 "hello world",
25988 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
25989 ),
25990 (
25991 "hello, world",
25992 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
25993 ),
25994 (
25995 " hello world",
25996 &[
25997 whitespace(" ", 2),
25998 word("hello", 5),
25999 whitespace(" ", 1),
26000 word("world", 5),
26001 ],
26002 ),
26003 (
26004 "这是什么 \n 钢笔",
26005 &[
26006 word("这", 1),
26007 word("是", 1),
26008 word("什", 1),
26009 word("么", 1),
26010 whitespace(" ", 1),
26011 newline(),
26012 whitespace(" ", 1),
26013 word("钢", 1),
26014 word("笔", 1),
26015 ],
26016 ),
26017 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
26018 ];
26019
26020 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26021 WordBreakToken::Word {
26022 token,
26023 grapheme_len,
26024 }
26025 }
26026
26027 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26028 WordBreakToken::InlineWhitespace {
26029 token,
26030 grapheme_len,
26031 }
26032 }
26033
26034 fn newline() -> WordBreakToken<'static> {
26035 WordBreakToken::Newline
26036 }
26037
26038 for (input, result) in tests {
26039 assert_eq!(
26040 WordBreakingTokenizer::new(input)
26041 .collect::<Vec<_>>()
26042 .as_slice(),
26043 *result,
26044 );
26045 }
26046}
26047
26048fn wrap_with_prefix(
26049 first_line_prefix: String,
26050 subsequent_lines_prefix: String,
26051 unwrapped_text: String,
26052 wrap_column: usize,
26053 tab_size: NonZeroU32,
26054 preserve_existing_whitespace: bool,
26055) -> String {
26056 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
26057 let subsequent_lines_prefix_len =
26058 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
26059 let mut wrapped_text = String::new();
26060 let mut current_line = first_line_prefix;
26061 let mut is_first_line = true;
26062
26063 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
26064 let mut current_line_len = first_line_prefix_len;
26065 let mut in_whitespace = false;
26066 for token in tokenizer {
26067 let have_preceding_whitespace = in_whitespace;
26068 match token {
26069 WordBreakToken::Word {
26070 token,
26071 grapheme_len,
26072 } => {
26073 in_whitespace = false;
26074 let current_prefix_len = if is_first_line {
26075 first_line_prefix_len
26076 } else {
26077 subsequent_lines_prefix_len
26078 };
26079 if current_line_len + grapheme_len > wrap_column
26080 && current_line_len != current_prefix_len
26081 {
26082 wrapped_text.push_str(current_line.trim_end());
26083 wrapped_text.push('\n');
26084 is_first_line = false;
26085 current_line = subsequent_lines_prefix.clone();
26086 current_line_len = subsequent_lines_prefix_len;
26087 }
26088 current_line.push_str(token);
26089 current_line_len += grapheme_len;
26090 }
26091 WordBreakToken::InlineWhitespace {
26092 mut token,
26093 mut grapheme_len,
26094 } => {
26095 in_whitespace = true;
26096 if have_preceding_whitespace && !preserve_existing_whitespace {
26097 continue;
26098 }
26099 if !preserve_existing_whitespace {
26100 // Keep a single whitespace grapheme as-is
26101 if let Some(first) =
26102 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
26103 {
26104 token = first;
26105 } else {
26106 token = " ";
26107 }
26108 grapheme_len = 1;
26109 }
26110 let current_prefix_len = if is_first_line {
26111 first_line_prefix_len
26112 } else {
26113 subsequent_lines_prefix_len
26114 };
26115 if current_line_len + grapheme_len > wrap_column {
26116 wrapped_text.push_str(current_line.trim_end());
26117 wrapped_text.push('\n');
26118 is_first_line = false;
26119 current_line = subsequent_lines_prefix.clone();
26120 current_line_len = subsequent_lines_prefix_len;
26121 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
26122 current_line.push_str(token);
26123 current_line_len += grapheme_len;
26124 }
26125 }
26126 WordBreakToken::Newline => {
26127 in_whitespace = true;
26128 let current_prefix_len = if is_first_line {
26129 first_line_prefix_len
26130 } else {
26131 subsequent_lines_prefix_len
26132 };
26133 if preserve_existing_whitespace {
26134 wrapped_text.push_str(current_line.trim_end());
26135 wrapped_text.push('\n');
26136 is_first_line = false;
26137 current_line = subsequent_lines_prefix.clone();
26138 current_line_len = subsequent_lines_prefix_len;
26139 } else if have_preceding_whitespace {
26140 continue;
26141 } else if current_line_len + 1 > wrap_column
26142 && current_line_len != current_prefix_len
26143 {
26144 wrapped_text.push_str(current_line.trim_end());
26145 wrapped_text.push('\n');
26146 is_first_line = false;
26147 current_line = subsequent_lines_prefix.clone();
26148 current_line_len = subsequent_lines_prefix_len;
26149 } else if current_line_len != current_prefix_len {
26150 current_line.push(' ');
26151 current_line_len += 1;
26152 }
26153 }
26154 }
26155 }
26156
26157 if !current_line.is_empty() {
26158 wrapped_text.push_str(¤t_line);
26159 }
26160 wrapped_text
26161}
26162
26163#[test]
26164fn test_wrap_with_prefix() {
26165 assert_eq!(
26166 wrap_with_prefix(
26167 "# ".to_string(),
26168 "# ".to_string(),
26169 "abcdefg".to_string(),
26170 4,
26171 NonZeroU32::new(4).unwrap(),
26172 false,
26173 ),
26174 "# abcdefg"
26175 );
26176 assert_eq!(
26177 wrap_with_prefix(
26178 "".to_string(),
26179 "".to_string(),
26180 "\thello world".to_string(),
26181 8,
26182 NonZeroU32::new(4).unwrap(),
26183 false,
26184 ),
26185 "hello\nworld"
26186 );
26187 assert_eq!(
26188 wrap_with_prefix(
26189 "// ".to_string(),
26190 "// ".to_string(),
26191 "xx \nyy zz aa bb cc".to_string(),
26192 12,
26193 NonZeroU32::new(4).unwrap(),
26194 false,
26195 ),
26196 "// xx yy zz\n// aa bb cc"
26197 );
26198 assert_eq!(
26199 wrap_with_prefix(
26200 String::new(),
26201 String::new(),
26202 "这是什么 \n 钢笔".to_string(),
26203 3,
26204 NonZeroU32::new(4).unwrap(),
26205 false,
26206 ),
26207 "这是什\n么 钢\n笔"
26208 );
26209 assert_eq!(
26210 wrap_with_prefix(
26211 String::new(),
26212 String::new(),
26213 format!("foo{}bar", '\u{2009}'), // thin space
26214 80,
26215 NonZeroU32::new(4).unwrap(),
26216 false,
26217 ),
26218 format!("foo{}bar", '\u{2009}')
26219 );
26220}
26221
26222pub trait CollaborationHub {
26223 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
26224 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
26225 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
26226}
26227
26228impl CollaborationHub for Entity<Project> {
26229 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
26230 self.read(cx).collaborators()
26231 }
26232
26233 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
26234 self.read(cx).user_store().read(cx).participant_indices()
26235 }
26236
26237 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
26238 let this = self.read(cx);
26239 let user_ids = this.collaborators().values().map(|c| c.user_id);
26240 this.user_store().read(cx).participant_names(user_ids, cx)
26241 }
26242}
26243
26244pub trait SemanticsProvider {
26245 fn hover(
26246 &self,
26247 buffer: &Entity<Buffer>,
26248 position: text::Anchor,
26249 cx: &mut App,
26250 ) -> Option<Task<Option<Vec<project::Hover>>>>;
26251
26252 fn inline_values(
26253 &self,
26254 buffer_handle: Entity<Buffer>,
26255 range: Range<text::Anchor>,
26256 cx: &mut App,
26257 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
26258
26259 fn applicable_inlay_chunks(
26260 &self,
26261 buffer: &Entity<Buffer>,
26262 ranges: &[Range<text::Anchor>],
26263 cx: &mut App,
26264 ) -> Vec<Range<BufferRow>>;
26265
26266 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
26267
26268 fn inlay_hints(
26269 &self,
26270 invalidate: InvalidationStrategy,
26271 buffer: Entity<Buffer>,
26272 ranges: Vec<Range<text::Anchor>>,
26273 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
26274 cx: &mut App,
26275 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
26276
26277 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
26278
26279 fn document_highlights(
26280 &self,
26281 buffer: &Entity<Buffer>,
26282 position: text::Anchor,
26283 cx: &mut App,
26284 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
26285
26286 fn definitions(
26287 &self,
26288 buffer: &Entity<Buffer>,
26289 position: text::Anchor,
26290 kind: GotoDefinitionKind,
26291 cx: &mut App,
26292 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
26293
26294 fn range_for_rename(
26295 &self,
26296 buffer: &Entity<Buffer>,
26297 position: text::Anchor,
26298 cx: &mut App,
26299 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
26300
26301 fn perform_rename(
26302 &self,
26303 buffer: &Entity<Buffer>,
26304 position: text::Anchor,
26305 new_name: String,
26306 cx: &mut App,
26307 ) -> Option<Task<Result<ProjectTransaction>>>;
26308}
26309
26310pub trait CompletionProvider {
26311 fn completions(
26312 &self,
26313 excerpt_id: ExcerptId,
26314 buffer: &Entity<Buffer>,
26315 buffer_position: text::Anchor,
26316 trigger: CompletionContext,
26317 window: &mut Window,
26318 cx: &mut Context<Editor>,
26319 ) -> Task<Result<Vec<CompletionResponse>>>;
26320
26321 fn resolve_completions(
26322 &self,
26323 _buffer: Entity<Buffer>,
26324 _completion_indices: Vec<usize>,
26325 _completions: Rc<RefCell<Box<[Completion]>>>,
26326 _cx: &mut Context<Editor>,
26327 ) -> Task<Result<bool>> {
26328 Task::ready(Ok(false))
26329 }
26330
26331 fn apply_additional_edits_for_completion(
26332 &self,
26333 _buffer: Entity<Buffer>,
26334 _completions: Rc<RefCell<Box<[Completion]>>>,
26335 _completion_index: usize,
26336 _push_to_history: bool,
26337 _cx: &mut Context<Editor>,
26338 ) -> Task<Result<Option<language::Transaction>>> {
26339 Task::ready(Ok(None))
26340 }
26341
26342 fn is_completion_trigger(
26343 &self,
26344 buffer: &Entity<Buffer>,
26345 position: language::Anchor,
26346 text: &str,
26347 trigger_in_words: bool,
26348 cx: &mut Context<Editor>,
26349 ) -> bool;
26350
26351 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
26352
26353 fn sort_completions(&self) -> bool {
26354 true
26355 }
26356
26357 fn filter_completions(&self) -> bool {
26358 true
26359 }
26360
26361 fn show_snippets(&self) -> bool {
26362 false
26363 }
26364}
26365
26366pub trait CodeActionProvider {
26367 fn id(&self) -> Arc<str>;
26368
26369 fn code_actions(
26370 &self,
26371 buffer: &Entity<Buffer>,
26372 range: Range<text::Anchor>,
26373 window: &mut Window,
26374 cx: &mut App,
26375 ) -> Task<Result<Vec<CodeAction>>>;
26376
26377 fn apply_code_action(
26378 &self,
26379 buffer_handle: Entity<Buffer>,
26380 action: CodeAction,
26381 excerpt_id: ExcerptId,
26382 push_to_history: bool,
26383 window: &mut Window,
26384 cx: &mut App,
26385 ) -> Task<Result<ProjectTransaction>>;
26386}
26387
26388impl CodeActionProvider for Entity<Project> {
26389 fn id(&self) -> Arc<str> {
26390 "project".into()
26391 }
26392
26393 fn code_actions(
26394 &self,
26395 buffer: &Entity<Buffer>,
26396 range: Range<text::Anchor>,
26397 _window: &mut Window,
26398 cx: &mut App,
26399 ) -> Task<Result<Vec<CodeAction>>> {
26400 self.update(cx, |project, cx| {
26401 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
26402 let code_actions = project.code_actions(buffer, range, None, cx);
26403 cx.background_spawn(async move {
26404 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
26405 Ok(code_lens_actions
26406 .context("code lens fetch")?
26407 .into_iter()
26408 .flatten()
26409 .chain(
26410 code_actions
26411 .context("code action fetch")?
26412 .into_iter()
26413 .flatten(),
26414 )
26415 .collect())
26416 })
26417 })
26418 }
26419
26420 fn apply_code_action(
26421 &self,
26422 buffer_handle: Entity<Buffer>,
26423 action: CodeAction,
26424 _excerpt_id: ExcerptId,
26425 push_to_history: bool,
26426 _window: &mut Window,
26427 cx: &mut App,
26428 ) -> Task<Result<ProjectTransaction>> {
26429 self.update(cx, |project, cx| {
26430 project.apply_code_action(buffer_handle, action, push_to_history, cx)
26431 })
26432 }
26433}
26434
26435fn snippet_completions(
26436 project: &Project,
26437 buffer: &Entity<Buffer>,
26438 buffer_anchor: text::Anchor,
26439 classifier: CharClassifier,
26440 cx: &mut App,
26441) -> Task<Result<CompletionResponse>> {
26442 let languages = buffer.read(cx).languages_at(buffer_anchor);
26443 let snippet_store = project.snippets().read(cx);
26444
26445 let scopes: Vec<_> = languages
26446 .iter()
26447 .filter_map(|language| {
26448 let language_name = language.lsp_id();
26449 let snippets = snippet_store.snippets_for(Some(language_name), cx);
26450
26451 if snippets.is_empty() {
26452 None
26453 } else {
26454 Some((language.default_scope(), snippets))
26455 }
26456 })
26457 .collect();
26458
26459 if scopes.is_empty() {
26460 return Task::ready(Ok(CompletionResponse {
26461 completions: vec![],
26462 display_options: CompletionDisplayOptions::default(),
26463 is_incomplete: false,
26464 }));
26465 }
26466
26467 let snapshot = buffer.read(cx).text_snapshot();
26468 let executor = cx.background_executor().clone();
26469
26470 cx.background_spawn(async move {
26471 let is_word_char = |c| classifier.is_word(c);
26472
26473 let mut is_incomplete = false;
26474 let mut completions: Vec<Completion> = Vec::new();
26475
26476 const MAX_PREFIX_LEN: usize = 128;
26477 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
26478 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
26479 let window_start = snapshot.clip_offset(window_start, Bias::Left);
26480
26481 let max_buffer_window: String = snapshot
26482 .text_for_range(window_start..buffer_offset)
26483 .collect();
26484
26485 if max_buffer_window.is_empty() {
26486 return Ok(CompletionResponse {
26487 completions: vec![],
26488 display_options: CompletionDisplayOptions::default(),
26489 is_incomplete: true,
26490 });
26491 }
26492
26493 for (_scope, snippets) in scopes.into_iter() {
26494 // Sort snippets by word count to match longer snippet prefixes first.
26495 let mut sorted_snippet_candidates = snippets
26496 .iter()
26497 .enumerate()
26498 .flat_map(|(snippet_ix, snippet)| {
26499 snippet
26500 .prefix
26501 .iter()
26502 .enumerate()
26503 .map(move |(prefix_ix, prefix)| {
26504 let word_count =
26505 snippet_candidate_suffixes(prefix, is_word_char).count();
26506 ((snippet_ix, prefix_ix), prefix, word_count)
26507 })
26508 })
26509 .collect_vec();
26510 sorted_snippet_candidates
26511 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
26512
26513 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
26514
26515 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
26516 .take(
26517 sorted_snippet_candidates
26518 .first()
26519 .map(|(_, _, word_count)| *word_count)
26520 .unwrap_or_default(),
26521 )
26522 .collect_vec();
26523
26524 const MAX_RESULTS: usize = 100;
26525 // Each match also remembers how many characters from the buffer it consumed
26526 let mut matches: Vec<(StringMatch, usize)> = vec![];
26527
26528 let mut snippet_list_cutoff_index = 0;
26529 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
26530 let word_count = buffer_index + 1;
26531 // Increase `snippet_list_cutoff_index` until we have all of the
26532 // snippets with sufficiently many words.
26533 while sorted_snippet_candidates
26534 .get(snippet_list_cutoff_index)
26535 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
26536 *snippet_word_count >= word_count
26537 })
26538 {
26539 snippet_list_cutoff_index += 1;
26540 }
26541
26542 // Take only the candidates with at least `word_count` many words
26543 let snippet_candidates_at_word_len =
26544 &sorted_snippet_candidates[..snippet_list_cutoff_index];
26545
26546 let candidates = snippet_candidates_at_word_len
26547 .iter()
26548 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
26549 .enumerate() // index in `sorted_snippet_candidates`
26550 // First char must match
26551 .filter(|(_ix, prefix)| {
26552 itertools::equal(
26553 prefix
26554 .chars()
26555 .next()
26556 .into_iter()
26557 .flat_map(|c| c.to_lowercase()),
26558 buffer_window
26559 .chars()
26560 .next()
26561 .into_iter()
26562 .flat_map(|c| c.to_lowercase()),
26563 )
26564 })
26565 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
26566 .collect::<Vec<StringMatchCandidate>>();
26567
26568 matches.extend(
26569 fuzzy::match_strings(
26570 &candidates,
26571 &buffer_window,
26572 buffer_window.chars().any(|c| c.is_uppercase()),
26573 true,
26574 MAX_RESULTS - matches.len(), // always prioritize longer snippets
26575 &Default::default(),
26576 executor.clone(),
26577 )
26578 .await
26579 .into_iter()
26580 .map(|string_match| (string_match, buffer_window.len())),
26581 );
26582
26583 if matches.len() >= MAX_RESULTS {
26584 break;
26585 }
26586 }
26587
26588 let to_lsp = |point: &text::Anchor| {
26589 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
26590 point_to_lsp(end)
26591 };
26592 let lsp_end = to_lsp(&buffer_anchor);
26593
26594 if matches.len() >= MAX_RESULTS {
26595 is_incomplete = true;
26596 }
26597
26598 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
26599 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
26600 sorted_snippet_candidates[string_match.candidate_id];
26601 let snippet = &snippets[snippet_index];
26602 let start = buffer_offset - buffer_window_len;
26603 let start = snapshot.anchor_before(start);
26604 let range = start..buffer_anchor;
26605 let lsp_start = to_lsp(&start);
26606 let lsp_range = lsp::Range {
26607 start: lsp_start,
26608 end: lsp_end,
26609 };
26610 Completion {
26611 replace_range: range,
26612 new_text: snippet.body.clone(),
26613 source: CompletionSource::Lsp {
26614 insert_range: None,
26615 server_id: LanguageServerId(usize::MAX),
26616 resolved: true,
26617 lsp_completion: Box::new(lsp::CompletionItem {
26618 label: snippet.prefix.first().unwrap().clone(),
26619 kind: Some(CompletionItemKind::SNIPPET),
26620 label_details: snippet.description.as_ref().map(|description| {
26621 lsp::CompletionItemLabelDetails {
26622 detail: Some(description.clone()),
26623 description: None,
26624 }
26625 }),
26626 insert_text_format: Some(InsertTextFormat::SNIPPET),
26627 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
26628 lsp::InsertReplaceEdit {
26629 new_text: snippet.body.clone(),
26630 insert: lsp_range,
26631 replace: lsp_range,
26632 },
26633 )),
26634 filter_text: Some(snippet.body.clone()),
26635 sort_text: Some(char::MAX.to_string()),
26636 ..lsp::CompletionItem::default()
26637 }),
26638 lsp_defaults: None,
26639 },
26640 label: CodeLabel {
26641 text: matching_prefix.clone(),
26642 runs: Vec::new(),
26643 filter_range: 0..matching_prefix.len(),
26644 },
26645 icon_path: None,
26646 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
26647 single_line: snippet.name.clone().into(),
26648 plain_text: snippet
26649 .description
26650 .clone()
26651 .map(|description| description.into()),
26652 }),
26653 insert_text_mode: None,
26654 confirm: None,
26655 match_start: Some(start),
26656 snippet_deduplication_key: Some((snippet_index, prefix_index)),
26657 }
26658 }));
26659 }
26660
26661 Ok(CompletionResponse {
26662 completions,
26663 display_options: CompletionDisplayOptions::default(),
26664 is_incomplete,
26665 })
26666 })
26667}
26668
26669impl CompletionProvider for Entity<Project> {
26670 fn completions(
26671 &self,
26672 _excerpt_id: ExcerptId,
26673 buffer: &Entity<Buffer>,
26674 buffer_position: text::Anchor,
26675 options: CompletionContext,
26676 _window: &mut Window,
26677 cx: &mut Context<Editor>,
26678 ) -> Task<Result<Vec<CompletionResponse>>> {
26679 self.update(cx, |project, cx| {
26680 let task = project.completions(buffer, buffer_position, options, cx);
26681 cx.background_spawn(task)
26682 })
26683 }
26684
26685 fn resolve_completions(
26686 &self,
26687 buffer: Entity<Buffer>,
26688 completion_indices: Vec<usize>,
26689 completions: Rc<RefCell<Box<[Completion]>>>,
26690 cx: &mut Context<Editor>,
26691 ) -> Task<Result<bool>> {
26692 self.update(cx, |project, cx| {
26693 project.lsp_store().update(cx, |lsp_store, cx| {
26694 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
26695 })
26696 })
26697 }
26698
26699 fn apply_additional_edits_for_completion(
26700 &self,
26701 buffer: Entity<Buffer>,
26702 completions: Rc<RefCell<Box<[Completion]>>>,
26703 completion_index: usize,
26704 push_to_history: bool,
26705 cx: &mut Context<Editor>,
26706 ) -> Task<Result<Option<language::Transaction>>> {
26707 self.update(cx, |project, cx| {
26708 project.lsp_store().update(cx, |lsp_store, cx| {
26709 lsp_store.apply_additional_edits_for_completion(
26710 buffer,
26711 completions,
26712 completion_index,
26713 push_to_history,
26714 cx,
26715 )
26716 })
26717 })
26718 }
26719
26720 fn is_completion_trigger(
26721 &self,
26722 buffer: &Entity<Buffer>,
26723 position: language::Anchor,
26724 text: &str,
26725 trigger_in_words: bool,
26726 cx: &mut Context<Editor>,
26727 ) -> bool {
26728 let mut chars = text.chars();
26729 let char = if let Some(char) = chars.next() {
26730 char
26731 } else {
26732 return false;
26733 };
26734 if chars.next().is_some() {
26735 return false;
26736 }
26737
26738 let buffer = buffer.read(cx);
26739 let snapshot = buffer.snapshot();
26740 let classifier = snapshot
26741 .char_classifier_at(position)
26742 .scope_context(Some(CharScopeContext::Completion));
26743 if trigger_in_words && classifier.is_word(char) {
26744 return true;
26745 }
26746
26747 buffer.completion_triggers().contains(text)
26748 }
26749
26750 fn show_snippets(&self) -> bool {
26751 true
26752 }
26753}
26754
26755impl SemanticsProvider for Entity<Project> {
26756 fn hover(
26757 &self,
26758 buffer: &Entity<Buffer>,
26759 position: text::Anchor,
26760 cx: &mut App,
26761 ) -> Option<Task<Option<Vec<project::Hover>>>> {
26762 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
26763 }
26764
26765 fn document_highlights(
26766 &self,
26767 buffer: &Entity<Buffer>,
26768 position: text::Anchor,
26769 cx: &mut App,
26770 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
26771 Some(self.update(cx, |project, cx| {
26772 project.document_highlights(buffer, position, cx)
26773 }))
26774 }
26775
26776 fn definitions(
26777 &self,
26778 buffer: &Entity<Buffer>,
26779 position: text::Anchor,
26780 kind: GotoDefinitionKind,
26781 cx: &mut App,
26782 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
26783 Some(self.update(cx, |project, cx| match kind {
26784 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
26785 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
26786 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
26787 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
26788 }))
26789 }
26790
26791 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
26792 self.update(cx, |project, cx| {
26793 if project
26794 .active_debug_session(cx)
26795 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
26796 {
26797 return true;
26798 }
26799
26800 buffer.update(cx, |buffer, cx| {
26801 project.any_language_server_supports_inlay_hints(buffer, cx)
26802 })
26803 })
26804 }
26805
26806 fn inline_values(
26807 &self,
26808 buffer_handle: Entity<Buffer>,
26809 range: Range<text::Anchor>,
26810 cx: &mut App,
26811 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
26812 self.update(cx, |project, cx| {
26813 let (session, active_stack_frame) = project.active_debug_session(cx)?;
26814
26815 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
26816 })
26817 }
26818
26819 fn applicable_inlay_chunks(
26820 &self,
26821 buffer: &Entity<Buffer>,
26822 ranges: &[Range<text::Anchor>],
26823 cx: &mut App,
26824 ) -> Vec<Range<BufferRow>> {
26825 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
26826 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
26827 })
26828 }
26829
26830 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
26831 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
26832 lsp_store.invalidate_inlay_hints(for_buffers)
26833 });
26834 }
26835
26836 fn inlay_hints(
26837 &self,
26838 invalidate: InvalidationStrategy,
26839 buffer: Entity<Buffer>,
26840 ranges: Vec<Range<text::Anchor>>,
26841 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
26842 cx: &mut App,
26843 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
26844 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
26845 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
26846 }))
26847 }
26848
26849 fn range_for_rename(
26850 &self,
26851 buffer: &Entity<Buffer>,
26852 position: text::Anchor,
26853 cx: &mut App,
26854 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
26855 Some(self.update(cx, |project, cx| {
26856 let buffer = buffer.clone();
26857 let task = project.prepare_rename(buffer.clone(), position, cx);
26858 cx.spawn(async move |_, cx| {
26859 Ok(match task.await? {
26860 PrepareRenameResponse::Success(range) => Some(range),
26861 PrepareRenameResponse::InvalidPosition => None,
26862 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
26863 // Fallback on using TreeSitter info to determine identifier range
26864 buffer.read_with(cx, |buffer, _| {
26865 let snapshot = buffer.snapshot();
26866 let (range, kind) = snapshot.surrounding_word(position, None);
26867 if kind != Some(CharKind::Word) {
26868 return None;
26869 }
26870 Some(
26871 snapshot.anchor_before(range.start)
26872 ..snapshot.anchor_after(range.end),
26873 )
26874 })
26875 }
26876 })
26877 })
26878 }))
26879 }
26880
26881 fn perform_rename(
26882 &self,
26883 buffer: &Entity<Buffer>,
26884 position: text::Anchor,
26885 new_name: String,
26886 cx: &mut App,
26887 ) -> Option<Task<Result<ProjectTransaction>>> {
26888 Some(self.update(cx, |project, cx| {
26889 project.perform_rename(buffer.clone(), position, new_name, cx)
26890 }))
26891 }
26892}
26893
26894fn consume_contiguous_rows(
26895 contiguous_row_selections: &mut Vec<Selection<Point>>,
26896 selection: &Selection<Point>,
26897 display_map: &DisplaySnapshot,
26898 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
26899) -> (MultiBufferRow, MultiBufferRow) {
26900 contiguous_row_selections.push(selection.clone());
26901 let start_row = starting_row(selection, display_map);
26902 let mut end_row = ending_row(selection, display_map);
26903
26904 while let Some(next_selection) = selections.peek() {
26905 if next_selection.start.row <= end_row.0 {
26906 end_row = ending_row(next_selection, display_map);
26907 contiguous_row_selections.push(selections.next().unwrap().clone());
26908 } else {
26909 break;
26910 }
26911 }
26912 (start_row, end_row)
26913}
26914
26915fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
26916 if selection.start.column > 0 {
26917 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
26918 } else {
26919 MultiBufferRow(selection.start.row)
26920 }
26921}
26922
26923fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
26924 if next_selection.end.column > 0 || next_selection.is_empty() {
26925 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
26926 } else {
26927 MultiBufferRow(next_selection.end.row)
26928 }
26929}
26930
26931impl EditorSnapshot {
26932 pub fn remote_selections_in_range<'a>(
26933 &'a self,
26934 range: &'a Range<Anchor>,
26935 collaboration_hub: &dyn CollaborationHub,
26936 cx: &'a App,
26937 ) -> impl 'a + Iterator<Item = RemoteSelection> {
26938 let participant_names = collaboration_hub.user_names(cx);
26939 let participant_indices = collaboration_hub.user_participant_indices(cx);
26940 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
26941 let collaborators_by_replica_id = collaborators_by_peer_id
26942 .values()
26943 .map(|collaborator| (collaborator.replica_id, collaborator))
26944 .collect::<HashMap<_, _>>();
26945 self.buffer_snapshot()
26946 .selections_in_range(range, false)
26947 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
26948 if replica_id == ReplicaId::AGENT {
26949 Some(RemoteSelection {
26950 replica_id,
26951 selection,
26952 cursor_shape,
26953 line_mode,
26954 collaborator_id: CollaboratorId::Agent,
26955 user_name: Some("Agent".into()),
26956 color: cx.theme().players().agent(),
26957 })
26958 } else {
26959 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
26960 let participant_index = participant_indices.get(&collaborator.user_id).copied();
26961 let user_name = participant_names.get(&collaborator.user_id).cloned();
26962 Some(RemoteSelection {
26963 replica_id,
26964 selection,
26965 cursor_shape,
26966 line_mode,
26967 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
26968 user_name,
26969 color: if let Some(index) = participant_index {
26970 cx.theme().players().color_for_participant(index.0)
26971 } else {
26972 cx.theme().players().absent()
26973 },
26974 })
26975 }
26976 })
26977 }
26978
26979 pub fn hunks_for_ranges(
26980 &self,
26981 ranges: impl IntoIterator<Item = Range<Point>>,
26982 ) -> Vec<MultiBufferDiffHunk> {
26983 let mut hunks = Vec::new();
26984 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
26985 HashMap::default();
26986 for query_range in ranges {
26987 let query_rows =
26988 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
26989 for hunk in self.buffer_snapshot().diff_hunks_in_range(
26990 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
26991 ) {
26992 // Include deleted hunks that are adjacent to the query range, because
26993 // otherwise they would be missed.
26994 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
26995 if hunk.status().is_deleted() {
26996 intersects_range |= hunk.row_range.start == query_rows.end;
26997 intersects_range |= hunk.row_range.end == query_rows.start;
26998 }
26999 if intersects_range {
27000 if !processed_buffer_rows
27001 .entry(hunk.buffer_id)
27002 .or_default()
27003 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
27004 {
27005 continue;
27006 }
27007 hunks.push(hunk);
27008 }
27009 }
27010 }
27011
27012 hunks
27013 }
27014
27015 fn display_diff_hunks_for_rows<'a>(
27016 &'a self,
27017 display_rows: Range<DisplayRow>,
27018 folded_buffers: &'a HashSet<BufferId>,
27019 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
27020 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
27021 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
27022
27023 self.buffer_snapshot()
27024 .diff_hunks_in_range(buffer_start..buffer_end)
27025 .filter_map(|hunk| {
27026 if folded_buffers.contains(&hunk.buffer_id) {
27027 return None;
27028 }
27029
27030 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
27031 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
27032
27033 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
27034 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
27035
27036 let display_hunk = if hunk_display_start.column() != 0 {
27037 DisplayDiffHunk::Folded {
27038 display_row: hunk_display_start.row(),
27039 }
27040 } else {
27041 let mut end_row = hunk_display_end.row();
27042 if hunk_display_end.column() > 0 {
27043 end_row.0 += 1;
27044 }
27045 let is_created_file = hunk.is_created_file();
27046
27047 DisplayDiffHunk::Unfolded {
27048 status: hunk.status(),
27049 diff_base_byte_range: hunk.diff_base_byte_range.start.0
27050 ..hunk.diff_base_byte_range.end.0,
27051 word_diffs: hunk.word_diffs,
27052 display_row_range: hunk_display_start.row()..end_row,
27053 multi_buffer_range: Anchor::range_in_buffer(
27054 hunk.excerpt_id,
27055 hunk.buffer_range,
27056 ),
27057 is_created_file,
27058 }
27059 };
27060
27061 Some(display_hunk)
27062 })
27063 }
27064
27065 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
27066 self.display_snapshot
27067 .buffer_snapshot()
27068 .language_at(position)
27069 }
27070
27071 pub fn is_focused(&self) -> bool {
27072 self.is_focused
27073 }
27074
27075 pub fn placeholder_text(&self) -> Option<String> {
27076 self.placeholder_display_snapshot
27077 .as_ref()
27078 .map(|display_map| display_map.text())
27079 }
27080
27081 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
27082 self.scroll_anchor.scroll_position(&self.display_snapshot)
27083 }
27084
27085 pub fn gutter_dimensions(
27086 &self,
27087 font_id: FontId,
27088 font_size: Pixels,
27089 style: &EditorStyle,
27090 window: &mut Window,
27091 cx: &App,
27092 ) -> GutterDimensions {
27093 if self.show_gutter
27094 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
27095 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
27096 {
27097 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
27098 matches!(
27099 ProjectSettings::get_global(cx).git.git_gutter,
27100 GitGutterSetting::TrackedFiles
27101 )
27102 });
27103 let gutter_settings = EditorSettings::get_global(cx).gutter;
27104 let show_line_numbers = self
27105 .show_line_numbers
27106 .unwrap_or(gutter_settings.line_numbers);
27107 let line_gutter_width = if show_line_numbers {
27108 // Avoid flicker-like gutter resizes when the line number gains another digit by
27109 // only resizing the gutter on files with > 10**min_line_number_digits lines.
27110 let min_width_for_number_on_gutter =
27111 ch_advance * gutter_settings.min_line_number_digits as f32;
27112 self.max_line_number_width(style, window)
27113 .max(min_width_for_number_on_gutter)
27114 } else {
27115 0.0.into()
27116 };
27117
27118 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
27119 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
27120
27121 let git_blame_entries_width =
27122 self.git_blame_gutter_max_author_length
27123 .map(|max_author_length| {
27124 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
27125 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
27126
27127 /// The number of characters to dedicate to gaps and margins.
27128 const SPACING_WIDTH: usize = 4;
27129
27130 let max_char_count = max_author_length.min(renderer.max_author_length())
27131 + ::git::SHORT_SHA_LENGTH
27132 + MAX_RELATIVE_TIMESTAMP.len()
27133 + SPACING_WIDTH;
27134
27135 ch_advance * max_char_count
27136 });
27137
27138 let is_singleton = self.buffer_snapshot().is_singleton();
27139
27140 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
27141 left_padding += if !is_singleton {
27142 ch_width * 4.0
27143 } else if show_runnables || show_breakpoints {
27144 ch_width * 3.0
27145 } else if show_git_gutter && show_line_numbers {
27146 ch_width * 2.0
27147 } else if show_git_gutter || show_line_numbers {
27148 ch_width
27149 } else {
27150 px(0.)
27151 };
27152
27153 let shows_folds = is_singleton && gutter_settings.folds;
27154
27155 let right_padding = if shows_folds && show_line_numbers {
27156 ch_width * 4.0
27157 } else if shows_folds || (!is_singleton && show_line_numbers) {
27158 ch_width * 3.0
27159 } else if show_line_numbers {
27160 ch_width
27161 } else {
27162 px(0.)
27163 };
27164
27165 GutterDimensions {
27166 left_padding,
27167 right_padding,
27168 width: line_gutter_width + left_padding + right_padding,
27169 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
27170 git_blame_entries_width,
27171 }
27172 } else if self.offset_content {
27173 GutterDimensions::default_with_margin(font_id, font_size, cx)
27174 } else {
27175 GutterDimensions::default()
27176 }
27177 }
27178
27179 pub fn render_crease_toggle(
27180 &self,
27181 buffer_row: MultiBufferRow,
27182 row_contains_cursor: bool,
27183 editor: Entity<Editor>,
27184 window: &mut Window,
27185 cx: &mut App,
27186 ) -> Option<AnyElement> {
27187 let folded = self.is_line_folded(buffer_row);
27188 let mut is_foldable = false;
27189
27190 if let Some(crease) = self
27191 .crease_snapshot
27192 .query_row(buffer_row, self.buffer_snapshot())
27193 {
27194 is_foldable = true;
27195 match crease {
27196 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
27197 if let Some(render_toggle) = render_toggle {
27198 let toggle_callback =
27199 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
27200 if folded {
27201 editor.update(cx, |editor, cx| {
27202 editor.fold_at(buffer_row, window, cx)
27203 });
27204 } else {
27205 editor.update(cx, |editor, cx| {
27206 editor.unfold_at(buffer_row, window, cx)
27207 });
27208 }
27209 });
27210 return Some((render_toggle)(
27211 buffer_row,
27212 folded,
27213 toggle_callback,
27214 window,
27215 cx,
27216 ));
27217 }
27218 }
27219 }
27220 }
27221
27222 is_foldable |= self.starts_indent(buffer_row);
27223
27224 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
27225 Some(
27226 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
27227 .toggle_state(folded)
27228 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
27229 if folded {
27230 this.unfold_at(buffer_row, window, cx);
27231 } else {
27232 this.fold_at(buffer_row, window, cx);
27233 }
27234 }))
27235 .into_any_element(),
27236 )
27237 } else {
27238 None
27239 }
27240 }
27241
27242 pub fn render_crease_trailer(
27243 &self,
27244 buffer_row: MultiBufferRow,
27245 window: &mut Window,
27246 cx: &mut App,
27247 ) -> Option<AnyElement> {
27248 let folded = self.is_line_folded(buffer_row);
27249 if let Crease::Inline { render_trailer, .. } = self
27250 .crease_snapshot
27251 .query_row(buffer_row, self.buffer_snapshot())?
27252 {
27253 let render_trailer = render_trailer.as_ref()?;
27254 Some(render_trailer(buffer_row, folded, window, cx))
27255 } else {
27256 None
27257 }
27258 }
27259
27260 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
27261 let digit_count = self.widest_line_number().ilog10() + 1;
27262 column_pixels(style, digit_count as usize, window)
27263 }
27264
27265 /// Returns the line delta from `base` to `line` in the multibuffer, ignoring wrapped lines.
27266 ///
27267 /// This is positive if `base` is before `line`.
27268 fn relative_line_delta(
27269 &self,
27270 current_selection_head: DisplayRow,
27271 first_visible_row: DisplayRow,
27272 consider_wrapped_lines: bool,
27273 ) -> i64 {
27274 let current_selection_head = current_selection_head.as_display_point().to_point(self);
27275 let first_visible_row = first_visible_row.as_display_point().to_point(self);
27276
27277 if consider_wrapped_lines {
27278 let wrap_snapshot = self.wrap_snapshot();
27279 let base_wrap_row = wrap_snapshot
27280 .make_wrap_point(current_selection_head, Bias::Left)
27281 .row();
27282 let wrap_row = wrap_snapshot
27283 .make_wrap_point(first_visible_row, Bias::Left)
27284 .row();
27285
27286 wrap_row.0 as i64 - base_wrap_row.0 as i64
27287 } else {
27288 let fold_snapshot = self.fold_snapshot();
27289 let base_fold_row = fold_snapshot
27290 .to_fold_point(self.to_inlay_point(current_selection_head), Bias::Left)
27291 .row();
27292 let fold_row = fold_snapshot
27293 .to_fold_point(self.to_inlay_point(first_visible_row), Bias::Left)
27294 .row();
27295
27296 fold_row as i64 - base_fold_row as i64
27297 }
27298 }
27299
27300 /// Returns the unsigned relative line number to display for each row in `rows`.
27301 ///
27302 /// Wrapped rows are excluded from the hashmap if `count_relative_lines` is `false`.
27303 pub fn calculate_relative_line_numbers(
27304 &self,
27305 rows: &Range<DisplayRow>,
27306 current_selection_head: DisplayRow,
27307 count_wrapped_lines: bool,
27308 ) -> HashMap<DisplayRow, u32> {
27309 let initial_offset =
27310 self.relative_line_delta(current_selection_head, rows.start, count_wrapped_lines);
27311 let current_selection_point = current_selection_head.as_display_point().to_point(self);
27312
27313 self.row_infos(rows.start)
27314 .take(rows.len())
27315 .enumerate()
27316 .map(|(i, row_info)| (DisplayRow(rows.start.0 + i as u32), row_info))
27317 .filter(|(_row, row_info)| {
27318 row_info.buffer_row.is_some()
27319 || (count_wrapped_lines && row_info.wrapped_buffer_row.is_some())
27320 })
27321 .enumerate()
27322 .filter(|(_, (row, row_info))| {
27323 // We want to check here that
27324 // - the row is not the current selection head to ensure the current
27325 // line has absolute numbering
27326 // - similarly, should the selection head live in a soft-wrapped line
27327 // and we are not counting those, that the parent line keeps its
27328 // absolute number
27329 // - lastly, if we are in a deleted line, it is fine to number this
27330 // relative with 0, as otherwise it would have no line number at all
27331 (*row != current_selection_head
27332 && (count_wrapped_lines
27333 || row_info.buffer_row != Some(current_selection_point.row)))
27334 || row_info
27335 .diff_status
27336 .is_some_and(|status| status.is_deleted())
27337 })
27338 .map(|(i, (row, _))| (row, (initial_offset + i as i64).unsigned_abs() as u32))
27339 .collect()
27340 }
27341}
27342
27343pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
27344 let font_size = style.text.font_size.to_pixels(window.rem_size());
27345 let layout = window.text_system().shape_line(
27346 SharedString::from(" ".repeat(column)),
27347 font_size,
27348 &[TextRun {
27349 len: column,
27350 font: style.text.font(),
27351 color: Hsla::default(),
27352 ..Default::default()
27353 }],
27354 None,
27355 );
27356
27357 layout.width
27358}
27359
27360impl Deref for EditorSnapshot {
27361 type Target = DisplaySnapshot;
27362
27363 fn deref(&self) -> &Self::Target {
27364 &self.display_snapshot
27365 }
27366}
27367
27368#[derive(Clone, Debug, PartialEq, Eq)]
27369pub enum EditorEvent {
27370 /// Emitted when the stored review comments change (added, removed, or updated).
27371 ReviewCommentsChanged {
27372 /// The new total count of review comments.
27373 total_count: usize,
27374 },
27375 InputIgnored {
27376 text: Arc<str>,
27377 },
27378 InputHandled {
27379 utf16_range_to_replace: Option<Range<isize>>,
27380 text: Arc<str>,
27381 },
27382 ExcerptsAdded {
27383 buffer: Entity<Buffer>,
27384 predecessor: ExcerptId,
27385 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
27386 },
27387 ExcerptsRemoved {
27388 ids: Vec<ExcerptId>,
27389 removed_buffer_ids: Vec<BufferId>,
27390 },
27391 BufferFoldToggled {
27392 ids: Vec<ExcerptId>,
27393 folded: bool,
27394 },
27395 ExcerptsEdited {
27396 ids: Vec<ExcerptId>,
27397 },
27398 ExcerptsExpanded {
27399 ids: Vec<ExcerptId>,
27400 },
27401 ExpandExcerptsRequested {
27402 excerpt_ids: Vec<ExcerptId>,
27403 lines: u32,
27404 direction: ExpandExcerptDirection,
27405 },
27406 StageOrUnstageRequested {
27407 stage: bool,
27408 hunks: Vec<MultiBufferDiffHunk>,
27409 },
27410 RestoreRequested {
27411 hunks: Vec<MultiBufferDiffHunk>,
27412 },
27413 BufferEdited,
27414 Edited {
27415 transaction_id: clock::Lamport,
27416 },
27417 Reparsed(BufferId),
27418 Focused,
27419 FocusedIn,
27420 Blurred,
27421 DirtyChanged,
27422 Saved,
27423 TitleChanged,
27424 SelectionsChanged {
27425 local: bool,
27426 },
27427 ScrollPositionChanged {
27428 local: bool,
27429 autoscroll: bool,
27430 },
27431 TransactionUndone {
27432 transaction_id: clock::Lamport,
27433 },
27434 TransactionBegun {
27435 transaction_id: clock::Lamport,
27436 },
27437 CursorShapeChanged,
27438 BreadcrumbsChanged,
27439 PushedToNavHistory {
27440 anchor: Anchor,
27441 is_deactivate: bool,
27442 },
27443}
27444
27445impl EventEmitter<EditorEvent> for Editor {}
27446
27447impl Focusable for Editor {
27448 fn focus_handle(&self, _cx: &App) -> FocusHandle {
27449 self.focus_handle.clone()
27450 }
27451}
27452
27453impl Render for Editor {
27454 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
27455 EditorElement::new(&cx.entity(), self.create_style(cx))
27456 }
27457}
27458
27459impl EntityInputHandler for Editor {
27460 fn text_for_range(
27461 &mut self,
27462 range_utf16: Range<usize>,
27463 adjusted_range: &mut Option<Range<usize>>,
27464 _: &mut Window,
27465 cx: &mut Context<Self>,
27466 ) -> Option<String> {
27467 let snapshot = self.buffer.read(cx).read(cx);
27468 let start = snapshot.clip_offset_utf16(
27469 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
27470 Bias::Left,
27471 );
27472 let end = snapshot.clip_offset_utf16(
27473 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
27474 Bias::Right,
27475 );
27476 if (start.0.0..end.0.0) != range_utf16 {
27477 adjusted_range.replace(start.0.0..end.0.0);
27478 }
27479 Some(snapshot.text_for_range(start..end).collect())
27480 }
27481
27482 fn selected_text_range(
27483 &mut self,
27484 ignore_disabled_input: bool,
27485 _: &mut Window,
27486 cx: &mut Context<Self>,
27487 ) -> Option<UTF16Selection> {
27488 // Prevent the IME menu from appearing when holding down an alphabetic key
27489 // while input is disabled.
27490 if !ignore_disabled_input && !self.input_enabled {
27491 return None;
27492 }
27493
27494 let selection = self
27495 .selections
27496 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
27497 let range = selection.range();
27498
27499 Some(UTF16Selection {
27500 range: range.start.0.0..range.end.0.0,
27501 reversed: selection.reversed,
27502 })
27503 }
27504
27505 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
27506 let snapshot = self.buffer.read(cx).read(cx);
27507 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
27508 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
27509 }
27510
27511 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
27512 self.clear_highlights::<InputComposition>(cx);
27513 self.ime_transaction.take();
27514 }
27515
27516 fn replace_text_in_range(
27517 &mut self,
27518 range_utf16: Option<Range<usize>>,
27519 text: &str,
27520 window: &mut Window,
27521 cx: &mut Context<Self>,
27522 ) {
27523 if !self.input_enabled {
27524 cx.emit(EditorEvent::InputIgnored { text: text.into() });
27525 return;
27526 }
27527
27528 self.transact(window, cx, |this, window, cx| {
27529 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
27530 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
27531 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
27532 Some(this.selection_replacement_ranges(range_utf16, cx))
27533 } else {
27534 this.marked_text_ranges(cx)
27535 };
27536
27537 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
27538 let newest_selection_id = this.selections.newest_anchor().id;
27539 this.selections
27540 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
27541 .iter()
27542 .zip(ranges_to_replace.iter())
27543 .find_map(|(selection, range)| {
27544 if selection.id == newest_selection_id {
27545 Some(
27546 (range.start.0.0 as isize - selection.head().0.0 as isize)
27547 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
27548 )
27549 } else {
27550 None
27551 }
27552 })
27553 });
27554
27555 cx.emit(EditorEvent::InputHandled {
27556 utf16_range_to_replace: range_to_replace,
27557 text: text.into(),
27558 });
27559
27560 if let Some(new_selected_ranges) = new_selected_ranges {
27561 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
27562 selections.select_ranges(new_selected_ranges)
27563 });
27564 this.backspace(&Default::default(), window, cx);
27565 }
27566
27567 this.handle_input(text, window, cx);
27568 });
27569
27570 if let Some(transaction) = self.ime_transaction {
27571 self.buffer.update(cx, |buffer, cx| {
27572 buffer.group_until_transaction(transaction, cx);
27573 });
27574 }
27575
27576 self.unmark_text(window, cx);
27577 }
27578
27579 fn replace_and_mark_text_in_range(
27580 &mut self,
27581 range_utf16: Option<Range<usize>>,
27582 text: &str,
27583 new_selected_range_utf16: Option<Range<usize>>,
27584 window: &mut Window,
27585 cx: &mut Context<Self>,
27586 ) {
27587 if !self.input_enabled {
27588 return;
27589 }
27590
27591 let transaction = self.transact(window, cx, |this, window, cx| {
27592 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
27593 let snapshot = this.buffer.read(cx).read(cx);
27594 if let Some(relative_range_utf16) = range_utf16.as_ref() {
27595 for marked_range in &mut marked_ranges {
27596 marked_range.end = marked_range.start + relative_range_utf16.end;
27597 marked_range.start += relative_range_utf16.start;
27598 marked_range.start =
27599 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
27600 marked_range.end =
27601 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
27602 }
27603 }
27604 Some(marked_ranges)
27605 } else if let Some(range_utf16) = range_utf16 {
27606 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
27607 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
27608 Some(this.selection_replacement_ranges(range_utf16, cx))
27609 } else {
27610 None
27611 };
27612
27613 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
27614 let newest_selection_id = this.selections.newest_anchor().id;
27615 this.selections
27616 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
27617 .iter()
27618 .zip(ranges_to_replace.iter())
27619 .find_map(|(selection, range)| {
27620 if selection.id == newest_selection_id {
27621 Some(
27622 (range.start.0.0 as isize - selection.head().0.0 as isize)
27623 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
27624 )
27625 } else {
27626 None
27627 }
27628 })
27629 });
27630
27631 cx.emit(EditorEvent::InputHandled {
27632 utf16_range_to_replace: range_to_replace,
27633 text: text.into(),
27634 });
27635
27636 if let Some(ranges) = ranges_to_replace {
27637 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27638 s.select_ranges(ranges)
27639 });
27640 }
27641
27642 let marked_ranges = {
27643 let snapshot = this.buffer.read(cx).read(cx);
27644 this.selections
27645 .disjoint_anchors_arc()
27646 .iter()
27647 .map(|selection| {
27648 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
27649 })
27650 .collect::<Vec<_>>()
27651 };
27652
27653 if text.is_empty() {
27654 this.unmark_text(window, cx);
27655 } else {
27656 this.highlight_text::<InputComposition>(
27657 marked_ranges.clone(),
27658 HighlightStyle {
27659 underline: Some(UnderlineStyle {
27660 thickness: px(1.),
27661 color: None,
27662 wavy: false,
27663 }),
27664 ..Default::default()
27665 },
27666 cx,
27667 );
27668 }
27669
27670 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
27671 let use_autoclose = this.use_autoclose;
27672 let use_auto_surround = this.use_auto_surround;
27673 this.set_use_autoclose(false);
27674 this.set_use_auto_surround(false);
27675 this.handle_input(text, window, cx);
27676 this.set_use_autoclose(use_autoclose);
27677 this.set_use_auto_surround(use_auto_surround);
27678
27679 if let Some(new_selected_range) = new_selected_range_utf16 {
27680 let snapshot = this.buffer.read(cx).read(cx);
27681 let new_selected_ranges = marked_ranges
27682 .into_iter()
27683 .map(|marked_range| {
27684 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
27685 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
27686 insertion_start.0 + new_selected_range.start,
27687 ));
27688 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
27689 insertion_start.0 + new_selected_range.end,
27690 ));
27691 snapshot.clip_offset_utf16(new_start, Bias::Left)
27692 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
27693 })
27694 .collect::<Vec<_>>();
27695
27696 drop(snapshot);
27697 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
27698 selections.select_ranges(new_selected_ranges)
27699 });
27700 }
27701 });
27702
27703 self.ime_transaction = self.ime_transaction.or(transaction);
27704 if let Some(transaction) = self.ime_transaction {
27705 self.buffer.update(cx, |buffer, cx| {
27706 buffer.group_until_transaction(transaction, cx);
27707 });
27708 }
27709
27710 if self.text_highlights::<InputComposition>(cx).is_none() {
27711 self.ime_transaction.take();
27712 }
27713 }
27714
27715 fn bounds_for_range(
27716 &mut self,
27717 range_utf16: Range<usize>,
27718 element_bounds: gpui::Bounds<Pixels>,
27719 window: &mut Window,
27720 cx: &mut Context<Self>,
27721 ) -> Option<gpui::Bounds<Pixels>> {
27722 let text_layout_details = self.text_layout_details(window, cx);
27723 let CharacterDimensions {
27724 em_width,
27725 em_advance,
27726 line_height,
27727 } = self.character_dimensions(window, cx);
27728
27729 let snapshot = self.snapshot(window, cx);
27730 let scroll_position = snapshot.scroll_position();
27731 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
27732
27733 let start =
27734 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
27735 let x = Pixels::from(
27736 ScrollOffset::from(
27737 snapshot.x_for_display_point(start, &text_layout_details)
27738 + self.gutter_dimensions.full_width(),
27739 ) - scroll_left,
27740 );
27741 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
27742
27743 Some(Bounds {
27744 origin: element_bounds.origin + point(x, y),
27745 size: size(em_width, line_height),
27746 })
27747 }
27748
27749 fn character_index_for_point(
27750 &mut self,
27751 point: gpui::Point<Pixels>,
27752 _window: &mut Window,
27753 _cx: &mut Context<Self>,
27754 ) -> Option<usize> {
27755 let position_map = self.last_position_map.as_ref()?;
27756 if !position_map.text_hitbox.contains(&point) {
27757 return None;
27758 }
27759 let display_point = position_map.point_for_position(point).previous_valid;
27760 let anchor = position_map
27761 .snapshot
27762 .display_point_to_anchor(display_point, Bias::Left);
27763 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
27764 Some(utf16_offset.0.0)
27765 }
27766
27767 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
27768 self.input_enabled
27769 }
27770}
27771
27772trait SelectionExt {
27773 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
27774 fn spanned_rows(
27775 &self,
27776 include_end_if_at_line_start: bool,
27777 map: &DisplaySnapshot,
27778 ) -> Range<MultiBufferRow>;
27779}
27780
27781impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
27782 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
27783 let start = self
27784 .start
27785 .to_point(map.buffer_snapshot())
27786 .to_display_point(map);
27787 let end = self
27788 .end
27789 .to_point(map.buffer_snapshot())
27790 .to_display_point(map);
27791 if self.reversed {
27792 end..start
27793 } else {
27794 start..end
27795 }
27796 }
27797
27798 fn spanned_rows(
27799 &self,
27800 include_end_if_at_line_start: bool,
27801 map: &DisplaySnapshot,
27802 ) -> Range<MultiBufferRow> {
27803 let start = self.start.to_point(map.buffer_snapshot());
27804 let mut end = self.end.to_point(map.buffer_snapshot());
27805 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
27806 end.row -= 1;
27807 }
27808
27809 let buffer_start = map.prev_line_boundary(start).0;
27810 let buffer_end = map.next_line_boundary(end).0;
27811 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
27812 }
27813}
27814
27815impl<T: InvalidationRegion> InvalidationStack<T> {
27816 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
27817 where
27818 S: Clone + ToOffset,
27819 {
27820 while let Some(region) = self.last() {
27821 let all_selections_inside_invalidation_ranges =
27822 if selections.len() == region.ranges().len() {
27823 selections
27824 .iter()
27825 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
27826 .all(|(selection, invalidation_range)| {
27827 let head = selection.head().to_offset(buffer);
27828 invalidation_range.start <= head && invalidation_range.end >= head
27829 })
27830 } else {
27831 false
27832 };
27833
27834 if all_selections_inside_invalidation_ranges {
27835 break;
27836 } else {
27837 self.pop();
27838 }
27839 }
27840 }
27841}
27842
27843#[derive(Clone)]
27844struct ErasedEditorImpl(Entity<Editor>);
27845
27846impl ui_input::ErasedEditor for ErasedEditorImpl {
27847 fn text(&self, cx: &App) -> String {
27848 self.0.read(cx).text(cx)
27849 }
27850
27851 fn set_text(&self, text: &str, window: &mut Window, cx: &mut App) {
27852 self.0.update(cx, |this, cx| {
27853 this.set_text(text, window, cx);
27854 })
27855 }
27856
27857 fn clear(&self, window: &mut Window, cx: &mut App) {
27858 self.0.update(cx, |this, cx| this.clear(window, cx));
27859 }
27860
27861 fn set_placeholder_text(&self, text: &str, window: &mut Window, cx: &mut App) {
27862 self.0.update(cx, |this, cx| {
27863 this.set_placeholder_text(text, window, cx);
27864 });
27865 }
27866
27867 fn focus_handle(&self, cx: &App) -> FocusHandle {
27868 self.0.read(cx).focus_handle(cx)
27869 }
27870
27871 fn render(&self, _: &mut Window, cx: &App) -> AnyElement {
27872 let settings = ThemeSettings::get_global(cx);
27873 let theme_color = cx.theme().colors();
27874
27875 let text_style = TextStyle {
27876 font_family: settings.ui_font.family.clone(),
27877 font_features: settings.ui_font.features.clone(),
27878 font_size: rems(0.875).into(),
27879 font_weight: settings.buffer_font.weight,
27880 font_style: FontStyle::Normal,
27881 line_height: relative(1.2),
27882 color: theme_color.text,
27883 ..Default::default()
27884 };
27885 let editor_style = EditorStyle {
27886 background: theme_color.ghost_element_background,
27887 local_player: cx.theme().players().local(),
27888 syntax: cx.theme().syntax().clone(),
27889 text: text_style,
27890 ..Default::default()
27891 };
27892 EditorElement::new(&self.0, editor_style).into_any()
27893 }
27894
27895 fn as_any(&self) -> &dyn Any {
27896 &self.0
27897 }
27898
27899 fn move_selection_to_end(&self, window: &mut Window, cx: &mut App) {
27900 self.0.update(cx, |editor, cx| {
27901 let editor_offset = editor.buffer().read(cx).len(cx);
27902 editor.change_selections(
27903 SelectionEffects::scroll(Autoscroll::Next),
27904 window,
27905 cx,
27906 |s| s.select_ranges(Some(editor_offset..editor_offset)),
27907 );
27908 });
27909 }
27910
27911 fn subscribe(
27912 &self,
27913 mut callback: Box<dyn FnMut(ui_input::ErasedEditorEvent, &mut Window, &mut App) + 'static>,
27914 window: &mut Window,
27915 cx: &mut App,
27916 ) -> Subscription {
27917 window.subscribe(&self.0, cx, move |_, event: &EditorEvent, window, cx| {
27918 let event = match event {
27919 EditorEvent::BufferEdited => ui_input::ErasedEditorEvent::BufferEdited,
27920 EditorEvent::Blurred => ui_input::ErasedEditorEvent::Blurred,
27921 _ => return,
27922 };
27923 (callback)(event, window, cx);
27924 })
27925 }
27926
27927 fn set_masked(&self, masked: bool, _window: &mut Window, cx: &mut App) {
27928 self.0.update(cx, |editor, cx| {
27929 editor.set_masked(masked, cx);
27930 });
27931 }
27932}
27933impl<T> Default for InvalidationStack<T> {
27934 fn default() -> Self {
27935 Self(Default::default())
27936 }
27937}
27938
27939impl<T> Deref for InvalidationStack<T> {
27940 type Target = Vec<T>;
27941
27942 fn deref(&self) -> &Self::Target {
27943 &self.0
27944 }
27945}
27946
27947impl<T> DerefMut for InvalidationStack<T> {
27948 fn deref_mut(&mut self) -> &mut Self::Target {
27949 &mut self.0
27950 }
27951}
27952
27953impl InvalidationRegion for SnippetState {
27954 fn ranges(&self) -> &[Range<Anchor>] {
27955 &self.ranges[self.active_index]
27956 }
27957}
27958
27959fn edit_prediction_edit_text(
27960 current_snapshot: &BufferSnapshot,
27961 edits: &[(Range<Anchor>, impl AsRef<str>)],
27962 edit_preview: &EditPreview,
27963 include_deletions: bool,
27964 cx: &App,
27965) -> HighlightedText {
27966 let edits = edits
27967 .iter()
27968 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
27969 .collect::<Vec<_>>();
27970
27971 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
27972}
27973
27974fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
27975 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
27976 // Just show the raw edit text with basic styling
27977 let mut text = String::new();
27978 let mut highlights = Vec::new();
27979
27980 let insertion_highlight_style = HighlightStyle {
27981 color: Some(cx.theme().colors().text),
27982 ..Default::default()
27983 };
27984
27985 for (_, edit_text) in edits {
27986 let start_offset = text.len();
27987 text.push_str(edit_text);
27988 let end_offset = text.len();
27989
27990 if start_offset < end_offset {
27991 highlights.push((start_offset..end_offset, insertion_highlight_style));
27992 }
27993 }
27994
27995 HighlightedText {
27996 text: text.into(),
27997 highlights,
27998 }
27999}
28000
28001pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
28002 match severity {
28003 lsp::DiagnosticSeverity::ERROR => colors.error,
28004 lsp::DiagnosticSeverity::WARNING => colors.warning,
28005 lsp::DiagnosticSeverity::INFORMATION => colors.info,
28006 lsp::DiagnosticSeverity::HINT => colors.info,
28007 _ => colors.ignored,
28008 }
28009}
28010
28011pub fn styled_runs_for_code_label<'a>(
28012 label: &'a CodeLabel,
28013 syntax_theme: &'a theme::SyntaxTheme,
28014 local_player: &'a theme::PlayerColor,
28015) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
28016 let fade_out = HighlightStyle {
28017 fade_out: Some(0.35),
28018 ..Default::default()
28019 };
28020
28021 let mut prev_end = label.filter_range.end;
28022 label
28023 .runs
28024 .iter()
28025 .enumerate()
28026 .flat_map(move |(ix, (range, highlight_id))| {
28027 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
28028 HighlightStyle {
28029 color: Some(local_player.cursor),
28030 ..Default::default()
28031 }
28032 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
28033 HighlightStyle {
28034 background_color: Some(local_player.selection),
28035 ..Default::default()
28036 }
28037 } else if let Some(style) = highlight_id.style(syntax_theme) {
28038 style
28039 } else {
28040 return Default::default();
28041 };
28042 let muted_style = style.highlight(fade_out);
28043
28044 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
28045 if range.start >= label.filter_range.end {
28046 if range.start > prev_end {
28047 runs.push((prev_end..range.start, fade_out));
28048 }
28049 runs.push((range.clone(), muted_style));
28050 } else if range.end <= label.filter_range.end {
28051 runs.push((range.clone(), style));
28052 } else {
28053 runs.push((range.start..label.filter_range.end, style));
28054 runs.push((label.filter_range.end..range.end, muted_style));
28055 }
28056 prev_end = cmp::max(prev_end, range.end);
28057
28058 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
28059 runs.push((prev_end..label.text.len(), fade_out));
28060 }
28061
28062 runs
28063 })
28064}
28065
28066pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
28067 let mut prev_index = 0;
28068 let mut prev_codepoint: Option<char> = None;
28069 text.char_indices()
28070 .chain([(text.len(), '\0')])
28071 .filter_map(move |(index, codepoint)| {
28072 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28073 let is_boundary = index == text.len()
28074 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
28075 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
28076 if is_boundary {
28077 let chunk = &text[prev_index..index];
28078 prev_index = index;
28079 Some(chunk)
28080 } else {
28081 None
28082 }
28083 })
28084}
28085
28086/// Given a string of text immediately before the cursor, iterates over possible
28087/// strings a snippet could match to. More precisely: returns an iterator over
28088/// suffixes of `text` created by splitting at word boundaries (before & after
28089/// every non-word character).
28090///
28091/// Shorter suffixes are returned first.
28092pub(crate) fn snippet_candidate_suffixes(
28093 text: &str,
28094 is_word_char: impl Fn(char) -> bool,
28095) -> impl std::iter::Iterator<Item = &str> {
28096 let mut prev_index = text.len();
28097 let mut prev_codepoint = None;
28098 text.char_indices()
28099 .rev()
28100 .chain([(0, '\0')])
28101 .filter_map(move |(index, codepoint)| {
28102 let prev_index = std::mem::replace(&mut prev_index, index);
28103 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28104 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
28105 None
28106 } else {
28107 let chunk = &text[prev_index..]; // go to end of string
28108 Some(chunk)
28109 }
28110 })
28111}
28112
28113pub trait RangeToAnchorExt: Sized {
28114 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
28115
28116 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
28117 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
28118 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
28119 }
28120}
28121
28122impl<T: ToOffset> RangeToAnchorExt for Range<T> {
28123 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
28124 let start_offset = self.start.to_offset(snapshot);
28125 let end_offset = self.end.to_offset(snapshot);
28126 if start_offset == end_offset {
28127 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
28128 } else {
28129 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
28130 }
28131 }
28132}
28133
28134pub trait RowExt {
28135 fn as_f64(&self) -> f64;
28136
28137 fn next_row(&self) -> Self;
28138
28139 fn previous_row(&self) -> Self;
28140
28141 fn minus(&self, other: Self) -> u32;
28142}
28143
28144impl RowExt for DisplayRow {
28145 fn as_f64(&self) -> f64 {
28146 self.0 as _
28147 }
28148
28149 fn next_row(&self) -> Self {
28150 Self(self.0 + 1)
28151 }
28152
28153 fn previous_row(&self) -> Self {
28154 Self(self.0.saturating_sub(1))
28155 }
28156
28157 fn minus(&self, other: Self) -> u32 {
28158 self.0 - other.0
28159 }
28160}
28161
28162impl RowExt for MultiBufferRow {
28163 fn as_f64(&self) -> f64 {
28164 self.0 as _
28165 }
28166
28167 fn next_row(&self) -> Self {
28168 Self(self.0 + 1)
28169 }
28170
28171 fn previous_row(&self) -> Self {
28172 Self(self.0.saturating_sub(1))
28173 }
28174
28175 fn minus(&self, other: Self) -> u32 {
28176 self.0 - other.0
28177 }
28178}
28179
28180trait RowRangeExt {
28181 type Row;
28182
28183 fn len(&self) -> usize;
28184
28185 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
28186}
28187
28188impl RowRangeExt for Range<MultiBufferRow> {
28189 type Row = MultiBufferRow;
28190
28191 fn len(&self) -> usize {
28192 (self.end.0 - self.start.0) as usize
28193 }
28194
28195 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
28196 (self.start.0..self.end.0).map(MultiBufferRow)
28197 }
28198}
28199
28200impl RowRangeExt for Range<DisplayRow> {
28201 type Row = DisplayRow;
28202
28203 fn len(&self) -> usize {
28204 (self.end.0 - self.start.0) as usize
28205 }
28206
28207 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
28208 (self.start.0..self.end.0).map(DisplayRow)
28209 }
28210}
28211
28212/// If select range has more than one line, we
28213/// just point the cursor to range.start.
28214fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
28215 if range.start.row == range.end.row {
28216 range
28217 } else {
28218 range.start..range.start
28219 }
28220}
28221pub struct KillRing(ClipboardItem);
28222impl Global for KillRing {}
28223
28224const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
28225
28226enum BreakpointPromptEditAction {
28227 Log,
28228 Condition,
28229 HitCondition,
28230}
28231
28232struct BreakpointPromptEditor {
28233 pub(crate) prompt: Entity<Editor>,
28234 editor: WeakEntity<Editor>,
28235 breakpoint_anchor: Anchor,
28236 breakpoint: Breakpoint,
28237 edit_action: BreakpointPromptEditAction,
28238 block_ids: HashSet<CustomBlockId>,
28239 editor_margins: Arc<Mutex<EditorMargins>>,
28240 _subscriptions: Vec<Subscription>,
28241}
28242
28243impl BreakpointPromptEditor {
28244 const MAX_LINES: u8 = 4;
28245
28246 fn new(
28247 editor: WeakEntity<Editor>,
28248 breakpoint_anchor: Anchor,
28249 breakpoint: Breakpoint,
28250 edit_action: BreakpointPromptEditAction,
28251 window: &mut Window,
28252 cx: &mut Context<Self>,
28253 ) -> Self {
28254 let base_text = match edit_action {
28255 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
28256 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
28257 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
28258 }
28259 .map(|msg| msg.to_string())
28260 .unwrap_or_default();
28261
28262 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
28263 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
28264
28265 let prompt = cx.new(|cx| {
28266 let mut prompt = Editor::new(
28267 EditorMode::AutoHeight {
28268 min_lines: 1,
28269 max_lines: Some(Self::MAX_LINES as usize),
28270 },
28271 buffer,
28272 None,
28273 window,
28274 cx,
28275 );
28276 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
28277 prompt.set_show_cursor_when_unfocused(false, cx);
28278 prompt.set_placeholder_text(
28279 match edit_action {
28280 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
28281 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
28282 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
28283 },
28284 window,
28285 cx,
28286 );
28287
28288 prompt
28289 });
28290
28291 Self {
28292 prompt,
28293 editor,
28294 breakpoint_anchor,
28295 breakpoint,
28296 edit_action,
28297 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
28298 block_ids: Default::default(),
28299 _subscriptions: vec![],
28300 }
28301 }
28302
28303 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
28304 self.block_ids.extend(block_ids)
28305 }
28306
28307 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
28308 if let Some(editor) = self.editor.upgrade() {
28309 let message = self
28310 .prompt
28311 .read(cx)
28312 .buffer
28313 .read(cx)
28314 .as_singleton()
28315 .expect("A multi buffer in breakpoint prompt isn't possible")
28316 .read(cx)
28317 .as_rope()
28318 .to_string();
28319
28320 editor.update(cx, |editor, cx| {
28321 editor.edit_breakpoint_at_anchor(
28322 self.breakpoint_anchor,
28323 self.breakpoint.clone(),
28324 match self.edit_action {
28325 BreakpointPromptEditAction::Log => {
28326 BreakpointEditAction::EditLogMessage(message.into())
28327 }
28328 BreakpointPromptEditAction::Condition => {
28329 BreakpointEditAction::EditCondition(message.into())
28330 }
28331 BreakpointPromptEditAction::HitCondition => {
28332 BreakpointEditAction::EditHitCondition(message.into())
28333 }
28334 },
28335 cx,
28336 );
28337
28338 editor.remove_blocks(self.block_ids.clone(), None, cx);
28339 cx.focus_self(window);
28340 });
28341 }
28342 }
28343
28344 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
28345 self.editor
28346 .update(cx, |editor, cx| {
28347 editor.remove_blocks(self.block_ids.clone(), None, cx);
28348 window.focus(&editor.focus_handle, cx);
28349 })
28350 .log_err();
28351 }
28352
28353 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
28354 let settings = ThemeSettings::get_global(cx);
28355 let text_style = TextStyle {
28356 color: if self.prompt.read(cx).read_only(cx) {
28357 cx.theme().colors().text_disabled
28358 } else {
28359 cx.theme().colors().text
28360 },
28361 font_family: settings.buffer_font.family.clone(),
28362 font_fallbacks: settings.buffer_font.fallbacks.clone(),
28363 font_size: settings.buffer_font_size(cx).into(),
28364 font_weight: settings.buffer_font.weight,
28365 line_height: relative(settings.buffer_line_height.value()),
28366 ..Default::default()
28367 };
28368 EditorElement::new(
28369 &self.prompt,
28370 EditorStyle {
28371 background: cx.theme().colors().editor_background,
28372 local_player: cx.theme().players().local(),
28373 text: text_style,
28374 ..Default::default()
28375 },
28376 )
28377 }
28378}
28379
28380impl Render for BreakpointPromptEditor {
28381 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28382 let editor_margins = *self.editor_margins.lock();
28383 let gutter_dimensions = editor_margins.gutter;
28384 h_flex()
28385 .key_context("Editor")
28386 .bg(cx.theme().colors().editor_background)
28387 .border_y_1()
28388 .border_color(cx.theme().status().info_border)
28389 .size_full()
28390 .py(window.line_height() / 2.5)
28391 .on_action(cx.listener(Self::confirm))
28392 .on_action(cx.listener(Self::cancel))
28393 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
28394 .child(div().flex_1().child(self.render_prompt_editor(cx)))
28395 }
28396}
28397
28398impl Focusable for BreakpointPromptEditor {
28399 fn focus_handle(&self, cx: &App) -> FocusHandle {
28400 self.prompt.focus_handle(cx)
28401 }
28402}
28403
28404fn all_edits_insertions_or_deletions(
28405 edits: &Vec<(Range<Anchor>, Arc<str>)>,
28406 snapshot: &MultiBufferSnapshot,
28407) -> bool {
28408 let mut all_insertions = true;
28409 let mut all_deletions = true;
28410
28411 for (range, new_text) in edits.iter() {
28412 let range_is_empty = range.to_offset(snapshot).is_empty();
28413 let text_is_empty = new_text.is_empty();
28414
28415 if range_is_empty != text_is_empty {
28416 if range_is_empty {
28417 all_deletions = false;
28418 } else {
28419 all_insertions = false;
28420 }
28421 } else {
28422 return false;
28423 }
28424
28425 if !all_insertions && !all_deletions {
28426 return false;
28427 }
28428 }
28429 all_insertions || all_deletions
28430}
28431
28432struct MissingEditPredictionKeybindingTooltip;
28433
28434impl Render for MissingEditPredictionKeybindingTooltip {
28435 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28436 ui::tooltip_container(cx, |container, cx| {
28437 container
28438 .flex_shrink_0()
28439 .max_w_80()
28440 .min_h(rems_from_px(124.))
28441 .justify_between()
28442 .child(
28443 v_flex()
28444 .flex_1()
28445 .text_ui_sm(cx)
28446 .child(Label::new("Conflict with Accept Keybinding"))
28447 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
28448 )
28449 .child(
28450 h_flex()
28451 .pb_1()
28452 .gap_1()
28453 .items_end()
28454 .w_full()
28455 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
28456 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
28457 }))
28458 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
28459 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
28460 })),
28461 )
28462 })
28463 }
28464}
28465
28466#[derive(Debug, Clone, Copy, PartialEq)]
28467pub struct LineHighlight {
28468 pub background: Background,
28469 pub border: Option<gpui::Hsla>,
28470 pub include_gutter: bool,
28471 pub type_id: Option<TypeId>,
28472}
28473
28474struct LineManipulationResult {
28475 pub new_text: String,
28476 pub line_count_before: usize,
28477 pub line_count_after: usize,
28478}
28479
28480fn render_diff_hunk_controls(
28481 row: u32,
28482 status: &DiffHunkStatus,
28483 hunk_range: Range<Anchor>,
28484 is_created_file: bool,
28485 line_height: Pixels,
28486 editor: &Entity<Editor>,
28487 _window: &mut Window,
28488 cx: &mut App,
28489) -> AnyElement {
28490 h_flex()
28491 .h(line_height)
28492 .mr_1()
28493 .gap_1()
28494 .px_0p5()
28495 .pb_1()
28496 .border_x_1()
28497 .border_b_1()
28498 .border_color(cx.theme().colors().border_variant)
28499 .rounded_b_lg()
28500 .bg(cx.theme().colors().editor_background)
28501 .gap_1()
28502 .block_mouse_except_scroll()
28503 .shadow_md()
28504 .child(if status.has_secondary_hunk() {
28505 Button::new(("stage", row as u64), "Stage")
28506 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
28507 .tooltip({
28508 let focus_handle = editor.focus_handle(cx);
28509 move |_window, cx| {
28510 Tooltip::for_action_in(
28511 "Stage Hunk",
28512 &::git::ToggleStaged,
28513 &focus_handle,
28514 cx,
28515 )
28516 }
28517 })
28518 .on_click({
28519 let editor = editor.clone();
28520 move |_event, _window, cx| {
28521 editor.update(cx, |editor, cx| {
28522 editor.stage_or_unstage_diff_hunks(
28523 true,
28524 vec![hunk_range.start..hunk_range.start],
28525 cx,
28526 );
28527 });
28528 }
28529 })
28530 } else {
28531 Button::new(("unstage", row as u64), "Unstage")
28532 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
28533 .tooltip({
28534 let focus_handle = editor.focus_handle(cx);
28535 move |_window, cx| {
28536 Tooltip::for_action_in(
28537 "Unstage Hunk",
28538 &::git::ToggleStaged,
28539 &focus_handle,
28540 cx,
28541 )
28542 }
28543 })
28544 .on_click({
28545 let editor = editor.clone();
28546 move |_event, _window, cx| {
28547 editor.update(cx, |editor, cx| {
28548 editor.stage_or_unstage_diff_hunks(
28549 false,
28550 vec![hunk_range.start..hunk_range.start],
28551 cx,
28552 );
28553 });
28554 }
28555 })
28556 })
28557 .child(
28558 Button::new(("restore", row as u64), "Restore")
28559 .tooltip({
28560 let focus_handle = editor.focus_handle(cx);
28561 move |_window, cx| {
28562 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
28563 }
28564 })
28565 .on_click({
28566 let editor = editor.clone();
28567 move |_event, window, cx| {
28568 editor.update(cx, |editor, cx| {
28569 let snapshot = editor.snapshot(window, cx);
28570 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
28571 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
28572 });
28573 }
28574 })
28575 .disabled(is_created_file),
28576 )
28577 .when(
28578 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
28579 |el| {
28580 el.child(
28581 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
28582 .shape(IconButtonShape::Square)
28583 .icon_size(IconSize::Small)
28584 // .disabled(!has_multiple_hunks)
28585 .tooltip({
28586 let focus_handle = editor.focus_handle(cx);
28587 move |_window, cx| {
28588 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
28589 }
28590 })
28591 .on_click({
28592 let editor = editor.clone();
28593 move |_event, window, cx| {
28594 editor.update(cx, |editor, cx| {
28595 let snapshot = editor.snapshot(window, cx);
28596 let position =
28597 hunk_range.end.to_point(&snapshot.buffer_snapshot());
28598 editor.go_to_hunk_before_or_after_position(
28599 &snapshot,
28600 position,
28601 Direction::Next,
28602 window,
28603 cx,
28604 );
28605 editor.expand_selected_diff_hunks(cx);
28606 });
28607 }
28608 }),
28609 )
28610 .child(
28611 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
28612 .shape(IconButtonShape::Square)
28613 .icon_size(IconSize::Small)
28614 // .disabled(!has_multiple_hunks)
28615 .tooltip({
28616 let focus_handle = editor.focus_handle(cx);
28617 move |_window, cx| {
28618 Tooltip::for_action_in(
28619 "Previous Hunk",
28620 &GoToPreviousHunk,
28621 &focus_handle,
28622 cx,
28623 )
28624 }
28625 })
28626 .on_click({
28627 let editor = editor.clone();
28628 move |_event, window, cx| {
28629 editor.update(cx, |editor, cx| {
28630 let snapshot = editor.snapshot(window, cx);
28631 let point =
28632 hunk_range.start.to_point(&snapshot.buffer_snapshot());
28633 editor.go_to_hunk_before_or_after_position(
28634 &snapshot,
28635 point,
28636 Direction::Prev,
28637 window,
28638 cx,
28639 );
28640 editor.expand_selected_diff_hunks(cx);
28641 });
28642 }
28643 }),
28644 )
28645 },
28646 )
28647 .into_any_element()
28648}
28649
28650pub fn multibuffer_context_lines(cx: &App) -> u32 {
28651 EditorSettings::try_get(cx)
28652 .map(|settings| settings.excerpt_context_lines)
28653 .unwrap_or(2)
28654 .min(32)
28655}